blob: 56d33e9f666e77b9ddf9a00cd99277b5a82c8135 [file] [edit]
#!/usr/bin/env node
/*
* This content is licensed according to the W3C Software License at
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
* File: reference-tables.js
*/
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const cheerio = require('cheerio');
const HTMLParser = require('node-html-parser');
const exampleFilePath = path.join(
__dirname,
'..',
'content',
'index',
'index.html'
);
const exampleTemplatePath = path.join(__dirname, 'reference-tables.template');
let output = fs.readFileSync(exampleTemplatePath, function (err) {
console.log('Error reading aria index:', err);
});
const $ = cheerio.load(output);
const ariaRoles = [
'alert',
'alertdialog',
'application',
'article',
'banner',
'button',
'caption',
'cell',
'checkbox',
'code',
'columnheader',
'combobox',
'complementary',
'contentinfo',
'definition',
'deletion',
'dialog',
'directory',
'document',
'emphasis',
'feed',
'figure',
'form',
'generic',
'grid',
'gridcell',
'group',
'heading',
'img',
'input',
'insertion',
'link',
'list',
'listbox',
'listitem',
'log',
'main',
'marquee',
'math',
'menu',
'menubar',
'menuitem',
'menuitemcheckbox',
'menuitemradio',
'meter',
'navigation',
'none',
'note',
'option',
'paragraph',
'presentation',
'progressbar',
'radio',
'radiogroup',
'region',
'row',
'rowgroup',
'rowheader',
'scrollbar',
'search',
'searchbox',
'separator',
'slider',
'spinbutton',
'status',
'switch',
'tab',
'table',
'tablist',
'tabpanel',
'term',
'textbox',
'timer',
'toolbar',
'tooltip',
'tree',
'treegrid',
'treeitem',
];
const ariaPropertiesAndStates = [
'aria-activedescendant',
'aria-atomic',
'aria-autocomplete',
'aria-busy',
'aria-checked',
'aria-colcount',
'aria-colindex',
'aria-colspan',
'aria-controls',
'aria-current',
'aria-describedby',
'aria-details',
'aria-disabled',
'aria-dropeffect',
'aria-errormessage',
'aria-expanded',
'aria-flowto',
'aria-grabbed',
'aria-haspopup',
'aria-hidden',
'aria-invalid',
'aria-keyshortcuts',
'aria-label',
'aria-labelledby',
'aria-level',
'aria-live',
'aria-modal',
'aria-multiline',
'aria-multiselectable',
'aria-orientation',
'aria-owns',
'aria-placeholder',
'aria-posinset',
'aria-pressed',
'aria-readonly',
'aria-relevant',
'aria-required',
'aria-roledescription',
'aria-rowcount',
'aria-rowindex',
'aria-rowspan',
'aria-selected',
'aria-setsize',
'aria-sort',
'aria-valuemax',
'aria-valuemin',
'aria-valuenow',
'aria-valuetext',
];
let indexOfRoles = {};
let indexOfPropertiesAndStates = {};
const indexOfExperimentalContent = [];
console.log('Generating index...');
function getRoles(html) {
let roles = [];
let exampleRoles = html.querySelectorAll(
'table.data.attributes tbody tr > th:first-child code'
);
for (let i = 0; i < exampleRoles.length; i++) {
let code = exampleRoles[i].textContent.toLowerCase().trim();
for (let j = 0; j < ariaRoles.length; j++) {
const hasRole = RegExp('\\b' + ariaRoles[j] + '\\b');
if (hasRole.test(code) && roles.indexOf(ariaRoles[j]) < 0) {
console.log(' [role]: ' + code);
roles.push(ariaRoles[j]);
}
}
}
return roles;
}
function getPropertiesAndStates(html) {
let propertiesAndStates = [];
let exampleProps = html.querySelectorAll(
'table.data.attributes tbody tr > th:nth-child(2) code'
);
for (let i = 0; i < exampleProps.length; i++) {
let code = exampleProps[i].textContent.toLowerCase().trim().split('=')[0];
for (let j = 0; j < ariaPropertiesAndStates.length; j++) {
const hasPropOrState = RegExp('\\b' + ariaPropertiesAndStates[j] + '\\b');
if (
hasPropOrState.test(code) &&
propertiesAndStates.indexOf(ariaPropertiesAndStates[j]) < 0
) {
console.log(' [propertyOrState]: ' + code);
propertiesAndStates.push(ariaPropertiesAndStates[j]);
}
}
}
return propertiesAndStates;
}
function addExampleToExperimentalContent(example) {
indexOfExperimentalContent.push(example);
}
function addExampleToRoles(roles, example) {
for (let i = 0; i < roles.length; i++) {
let role = roles[i];
if (role === '') {
continue;
}
if (!indexOfRoles[role]) {
indexOfRoles[role] = [];
}
indexOfRoles[role].push(example);
}
}
function addExampleToPropertiesAndStates(props, example) {
for (let i = 0; i < props.length; i++) {
let prop = props[i];
if (prop === '') {
continue;
}
if (!indexOfPropertiesAndStates[prop]) {
indexOfPropertiesAndStates[prop] = [];
}
indexOfPropertiesAndStates[prop].push(example);
}
}
function addLandmarkRole(landmark, hasLabel, title, ref) {
let example = {
title: title,
ref: ref,
};
addExampleToRoles(landmark, example);
if (hasLabel) {
addExampleToPropertiesAndStates(['aria-labelledby'], example);
}
}
glob
.sync('content/patterns/!(landmarks)/examples/!(index).html', {
cwd: path.join(__dirname, '..'),
nodir: true,
})
.forEach(function (file) {
console.log('[file]: ' + file);
if (file.toLowerCase().indexOf('deprecated') >= 0) {
console.log(' [ignored]');
return;
}
let data = fs.readFileSync(file, 'utf8');
let html = HTMLParser.parse(data);
const isExperimental =
html.querySelector('main')?.getAttribute('data-content-phase') ===
'experimental';
let ref = file.replace('content', '..');
let title = html
.querySelector('title')
.textContent.split('|')[0]
.replace('Examples', '')
.replace('Example of', '')
.replace('Example', '')
.trim();
let example = {
title: title,
ref: ref,
highContrast: data.toLowerCase().indexOf('high contrast') > 0,
};
if (isExperimental) {
addExampleToExperimentalContent(example);
} else {
addExampleToRoles(getRoles(html), example);
addExampleToPropertiesAndStates(getPropertiesAndStates(html), example);
}
});
// Add landmark examples, since they are a different format
addLandmarkRole(
['banner'],
false,
'Banner Landmark',
'../patterns/landmarks/examples/banner.html'
);
addLandmarkRole(
['complementary'],
true,
'Complementary Landmark',
'../patterns/landmarks/examples/complementary.html'
);
addLandmarkRole(
['contentinfo'],
false,
'Contentinfo Landmark',
'../patterns/landmarks/examples/contentinfo.html'
);
addLandmarkRole(
['form'],
true,
'Form Landmark',
'../patterns/landmarks/examples/form.html'
);
addLandmarkRole(
['main'],
true,
'Main Landmark',
'../patterns/landmarks/examples/main.html'
);
addLandmarkRole(
['navigation'],
true,
'Navigation Landmark',
'../patterns/landmarks/examples/navigation.html'
);
addLandmarkRole(
['region'],
true,
'Region Landmark',
'../patterns/landmarks/examples/region.html'
);
addLandmarkRole(
['search'],
true,
'Search Landmark',
'../patterns/landmarks/examples/search.html'
);
function exampleListItem(item) {
let highContrast = '';
if (item.highContrast) {
highContrast = ' (<abbr title="High Contrast Support">HC</abbr>)';
}
return `
<li><a href="${item.ref}">${item.title}</a>${highContrast}</li>`;
}
let sortedRoles = Object.getOwnPropertyNames(indexOfRoles).sort();
let examplesByRole = sortedRoles.reduce(function (set, role) {
let examples = indexOfRoles[role];
let examplesHTML = '';
if (examples.length === 1) {
examplesHTML = `<a href="${examples[0].ref}">${examples[0].title}</a>`;
} else {
examplesHTML = `
<ul>${examples.map(exampleListItem).join('')}
</ul>\n `;
}
return `${set}
<tr>
<td><code>${role}</code></td>
<td>${examplesHTML}</td>
</tr>`;
}, '');
$('#examples_by_role_tbody').html(examplesByRole);
let sortedPropertiesAndStates = Object.getOwnPropertyNames(
indexOfPropertiesAndStates
).sort();
let examplesByProps = sortedPropertiesAndStates.reduce(function (set, prop) {
let examples = indexOfPropertiesAndStates[prop];
let examplesHTML = '';
if (examples.length === 1) {
examplesHTML = `<a href="${examples[0].ref}">${examples[0].title}</a>`;
} else {
examplesHTML = `
<ul>${examples.map(exampleListItem).join('')}
</ul>\n `;
}
return `${set}
<tr>
<td><code>${prop}</code></td>
<td>${examplesHTML}</td>
</tr>`;
}, '');
const examplesExperimental = indexOfExperimentalContent
.map(exampleListItem)
.join('');
if (examplesExperimental.length === 0) {
// Do no display the experimental section if there are no experimental examples
$('#examples_experimental').remove();
// Remove the <li> element containing the link to Experimental Examples
$('a[href="#examples_experimental_label"]').parent().remove();
} else {
$('#examples_experimental_ul').html(examplesExperimental);
}
$('#examples_by_props_tbody').html(examplesByProps);
// cheerio seems to fold the doctype lines despite the template
const result = $.html()
.replace('<!DOCTYPE html>', '<!DOCTYPE html>\n')
.replace('</body></html>', '</body></html>\n')
.replace(
'<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">',
'<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">\n'
);
fs.writeFile(exampleFilePath, result, function (err) {
if (err) {
console.log('Error saving updated aria practices:', err);
}
});