Skip to content

Commit

Permalink
Add errata to w3c/wcag repo and cross-reference from informative docs (
Browse files Browse the repository at this point in the history
…#4170)

This adds the following:

- Imports the 2.1 and 2.2 errata pages into this repo
  - Reorganizes existing 2.1 errata in reverse-chronological order
- Adds date stamps for all existing errata I could trace back to a
commit
- Makes use of variables and Liquid expressions to minimize potential
for copy-paste errors between sections
- Auto-generates table of contents within `CustomLiquid.ts` to avoid
desync when adding versions
- Documents patterns/formats for authoring errata in `errata/README.md`
(with mention in top-level README)
- Adds logic to parse information from errata pages and include relevant
errata within Guideline/SC boxes and term definitions within informative
docs pages
- Errata are only included when building for a specific version (i.e.
not the editor's draft, in which case corrections are expected to
already be inlined)
- Only errata against the latest published version are included in
informative docs
- Updates `publish-w3c` script to also copy relevant errata file for
version being published
- Adds links to errata pages in top-level index used for dev server and
PR builds
  • Loading branch information
kfranqueiro authored Jan 15, 2025
1 parent b034ff5 commit 05fd019
Show file tree
Hide file tree
Showing 11 changed files with 555 additions and 53 deletions.
1 change: 1 addition & 0 deletions .eleventyignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.md
**/README.md
11ty/
acknowledgements.html
acknowledgements/
Expand Down
43 changes: 38 additions & 5 deletions 11ty/CustomLiquid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,36 @@ export class CustomLiquid extends Liquid {
super(options);
this.termsMap = options.termsMap;
}

private renderErrata(html: string) {
const $ = load(html);

const $tocList = $("#contents .toc");
let $childList: CheerioAnyNode | null = null;
$("main section[id]:has(h2:first-child, h3:first-child)").each((_, el) => {
const $el = $(el);
// Only one of the following queries will match for each section
$el.find("> h2:first-child").each((_, h2El) => {
$childList = null;
$tocList.append(`<li><a href="#${el.attribs.id}">${$(h2El).text()}</a></li>`);
});
$el.find("> h3:first-child").each((_, h3El) => {
if (!$childList) $childList = $(`<ol class="toc"></ol>`).appendTo($tocList);
$childList.append(`<li><a href="#${el.attribs.id}">${$(h3El).text()}</a></li>`);
});
});

return $.html();
}

public parse(html: string, filepath?: string) {
// Filter out Liquid calls for computed data and includes themselves
if (filepath && !filepath.includes("_includes/") && isHtmlFileContent(html)) {
if (
filepath &&
!filepath.includes("_includes/") &&
!filepath.includes("errata/") &&
isHtmlFileContent(html)
) {
const isIndex = indexPattern.test(filepath);
const isTechniques = techniquesPattern.test(filepath);
const isUnderstanding = understandingPattern.test(filepath);
Expand Down Expand Up @@ -309,6 +336,7 @@ export class CustomLiquid extends Liquid {
// html contains markup after Liquid tags/includes have been processed
const html = (await super.render(templates, scope, options)).toString();
if (!isHtmlFileContent(html) || !scope || scope.page.url === false) return html;
if (scope.page.inputPath.includes("errata/")) return this.renderErrata(html);

const $ = load(html);

Expand Down Expand Up @@ -468,10 +496,15 @@ export class CustomLiquid extends Liquid {
});
for (const name of termNames) {
const term = this.termsMap[name]; // Already verified existence in the earlier loop
$termsList.append(
`<dt id="${term.id}">${term.name}</dt>` +
`<dd><definition>${term.definition}</definition></dd>`
);
let termBody = term.definition;
if (scope.errata[term.id]) {
termBody += `
<p><strong>Errata:</strong></p>
<ul>${scope.errata[term.id].map((erratum) => `<li>${erratum}</li>`)}</ul>
<p><a href="https://www.w3.org/WAI/WCAG${scope.version}/errata/">View all errata</a></p>
`;
}
$termsList.append(`<dt id="${term.id}">${term.name}</dt><dd>${termBody}</dd>`);
}

// Iterate over non-href links once more in now-expanded document to add hrefs
Expand Down
6 changes: 6 additions & 0 deletions 11ty/cp-cvs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,9 @@ for (const [srcDir, destDir] of Object.entries(dirs)) {
await copyFile(srcPath, destPath);
}
}

await mkdirp(join(wcagBase, "errata"));
await copyFile(
join(outputBase, "errata", `${wcagVersion}.html`),
join(wcagBase, "errata", "Overview.html")
);
122 changes: 83 additions & 39 deletions 11ty/guidelines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { CheerioAPI } from "cheerio";
import { glob } from "glob";

import { readFile } from "fs/promises";
import { basename } from "path";
import { basename, join } from "path";

import { flattenDomFromFile, load, type CheerioAnyNode } from "./cheerio";
import { flattenDomFromFile, load, loadFromFile, type CheerioAnyNode } from "./cheerio";
import { generateId } from "./common";

export type WcagVersion = "20" | "21" | "22";
Expand Down Expand Up @@ -233,50 +233,52 @@ export async function getTermsMap(version?: WcagVersion) {

// Version-specific APIs

const remoteGuidelines$: Partial<Record<WcagVersion, CheerioAPI>> = {};
const guidelinesCache: Partial<Record<WcagVersion, string>> = {};

/** Loads guidelines from TR space for specific version, caching for future calls. */
const loadRemoteGuidelines = async (version: WcagVersion) => {
if (!remoteGuidelines$[version]) {
const $ = load(
(await axios.get(`https://www.w3.org/TR/WCAG${version}/`, { responseType: "text" })).data
);

// Re-collapse definition links and notes, to be processed by this build system
$("a.internalDFN").removeAttr("class data-link-type id href title");
$("[role='note'] .marker").remove();
$("[role='note']").find("> div, > p").addClass("note").unwrap();

// Convert data-plurals (present in publications) to data-lt
$("dfn[data-plurals]").each((_, el) => {
el.attribs["data-lt"] = (el.attribs["data-lt"] || "")
.split("|")
.concat(el.attribs["data-plurals"].split("|"))
.join("|");
delete el.attribs["data-plurals"];
});
const loadRemoteGuidelines = async (version: WcagVersion, stripRespec = true) => {
const html =
guidelinesCache[version] ||
(guidelinesCache[version] = (
await axios.get(`https://www.w3.org/TR/WCAG${version}/`, { responseType: "text" })
).data);

const $ = load(html);
if (!stripRespec) return $;

// Re-collapse definition links and notes, to be processed by this build system
$("a.internalDFN").removeAttr("class data-link-type id href title");
$("[role='note'] .marker").remove();
$("[role='note']").find("> div, > p").addClass("note").unwrap();

// Convert data-plurals (present in publications) to data-lt
$("dfn[data-plurals]").each((_, el) => {
el.attribs["data-lt"] = (el.attribs["data-lt"] || "")
.split("|")
.concat(el.attribs["data-plurals"].split("|"))
.join("|");
delete el.attribs["data-plurals"];
});

// Un-process bibliography references, to be processed by CustomLiquid
$("cite:has(a.bibref:only-child)").each((_, el) => {
const $el = $(el);
$el.replaceWith(`[${$el.find("a.bibref").html()}]`);
});
// Un-process bibliography references, to be processed by CustomLiquid
$("cite:has(a.bibref:only-child)").each((_, el) => {
const $el = $(el);
$el.replaceWith(`[${$el.find("a.bibref").html()}]`);
});

// Remove generated IDs and markers from examples
$(".example[id]").removeAttr("id");
$(".example > .marker").remove();
// Remove generated IDs and markers from examples
$(".example[id]").removeAttr("id");
$(".example > .marker").remove();

// Remove extra markup from headings so they can be parsed for names
$("bdi").remove();
// Remove extra markup from headings so they can be parsed for names
$("bdi").remove();

// Remove abbr elements which exist only in TR, not in informative docs
$("#acknowledgements li abbr, #glossary abbr").each((_, abbrEl) => {
$(abbrEl).replaceWith($(abbrEl).text());
});
// Remove abbr elements which exist only in TR, not in informative docs
$("#acknowledgements li abbr, #glossary abbr").each((_, abbrEl) => {
$(abbrEl).replaceWith($(abbrEl).text());
});

remoteGuidelines$[version] = $;
}
return remoteGuidelines$[version]!;
return $;
};

/**
Expand All @@ -299,3 +301,45 @@ export const getAcknowledgementsForVersion = async (version: WcagVersion) => {
*/
export const getPrinciplesForVersion = async (version: WcagVersion) =>
processPrinciples(await loadRemoteGuidelines(version));

/** Parses errata items from the errata document for the specified WCAG version. */
export const getErrataForVersion = async (version: WcagVersion) => {
const $ = await loadFromFile(join("errata", `${version}.html`));
const $guidelines = await loadRemoteGuidelines(version, false);
const aSelector = `a[href*='}}#']:first-of-type`;
const errata: Record<string, string[]> = {};

$("main > section[id]")
.first()
.find(`li:has(${aSelector})`)
.each((_, el) => {
const $el = $(el);
const erratumHtml = $el
.html()!
// Remove everything before and including the final TR link
.replace(/^[\s\S]*href="\{\{\s*\w+\s*\}\}#[\s\S]*?<\/a>,?\s*/, "")
// Remove parenthetical github references (still in Liquid syntax)
.replace(/\(\{%.*%\}\)\s*$/, "")
.replace(/^(\w)/, (_, p1) => p1.toUpperCase());

$el.find(aSelector).each((_, aEl) => {
const $aEl = $(aEl);
let hash: string | undefined = $aEl.attr("href")!.replace(/^.*#/, "");

// Check whether hash pertains to a guideline/SC section or term definition;
// if it doesn't, attempt to resolve it to one
const $hashEl = $guidelines(`#${hash}`);
if (!$hashEl.is("section.guideline, #terms dfn")) {
const $closest = $hashEl.closest("#terms dd, section.guideline");
if ($closest.is("#terms dd")) hash = $closest.prev().find("dfn[id]").attr("id");
else hash = $closest.attr("id");
}
if (!hash) return;

if (hash in errata) errata[hash].push(erratumHtml);
else errata[hash] = [erratumHtml];
});
});

return errata;
};
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,14 @@ To create a working example:
* Reference working examples from techniques using the rawgit URI to the example in its development branch, e.g., `https://rawgit.com/w3c/wcag/main/working-examples/alt-attribute/`. Editors will update links when examples are approved.
* When the example is complete and functional, submit a pull request into the main branch.

## Errata

The errata documents for WCAG 2.1 and 2.2 are now maintained in this repository.
See the [Errata README](errata/README.md) for authoring details.

**Note:** The errata for both versions are maintained on the `main` branch for use in builds.
Direct edits to the guidelines for WCAG 2.1 must be performed under `guidelines/` on the `WCAG-2.1` branch.

## Translations

WCAG 2.2 is ready for translation. To translate WCAG 2.2, follow instructions at [How to Translate WCAG 2](https://www.w3.org/WAI/about/translating/wcag/).
27 changes: 18 additions & 9 deletions _includes/understanding/about.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
{%- if guideline.type == "SC" -%}
{% sectionbox "success-criterion" "Success Criterion (SC)" -%}
{{ guideline.content }}
{%- endsectionbox %}
{%- elsif guideline.type == "Guideline" -%}
{% sectionbox "guideline" "Guideline" -%}
{{ guideline.content }}
{%- endsectionbox %}
{%- endif -%}
{%- capture section_id -%}
{%- if guideline.type == "SC" -%}success-criterion{%- else -%}guideline{%- endif -%}
{%- endcapture -%}
{%- capture section_title -%}
{%- if guideline.type == "SC" -%}Success Criterion (SC){%- else -%}Guideline{%- endif -%}
{%- endcapture -%}
{% sectionbox section_id section_title -%}
{{ guideline.content }}
{%- if errata[guideline.id] %}
<h3>Errata</h3>
<ul>
{%- for erratum in errata[guideline.id] %}
<li>{{ erratum }}</li>
{%- endfor -%}
</ul>
<p><a href="https://www.w3.org/WAI/WCAG{{ version }}/errata/">View all errata</a></p>
{% endif -%}
{%- endsectionbox %}
16 changes: 16 additions & 0 deletions eleventy.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { resolveDecimalVersion } from "11ty/common";
import {
actRules,
assertIsWcagVersion,
getErrataForVersion,
getFlatGuidelines,
getPrinciples,
getPrinciplesForVersion,
Expand Down Expand Up @@ -110,6 +111,7 @@ const termsMap = process.env.WCAG_VERSION ? await getTermsMap(version) : await g
const globalData = {
version,
versionDecimal: resolveDecimalVersion(version),
errata: process.env.WCAG_VERSION ? await getErrataForVersion(version) : {},
techniques, // Used for techniques/index.html
technologies, // Used for techniques/index.html
technologyTitles, // Used for techniques/index.html
Expand Down Expand Up @@ -277,6 +279,7 @@ export default function (eleventyConfig: any) {
root: ["_includes", "."],
jsTruthy: true,
strictFilters: true,
timezoneOffset: 0, // Avoid off-by-one YYYY-MM-DD date stamp conversions
termsMap,
})
);
Expand Down Expand Up @@ -382,6 +385,19 @@ export default function (eleventyConfig: any) {
}
);

// Renders a link to a GitHub commit or pull request
eleventyConfig.addShortcode("gh", (id: string) => {
if (/^#\d+$/.test(id)) {
const num = id.slice(1);
return `<a href="https://github.com/${GH_ORG}/${GH_REPO}/pull/${num}" aria-label="pull request ${num}">${id}</a>`
}
else if (/^[0-9a-f]{7,}$/.test(id)) {
const sha = id.slice(0, 7); // Truncate in case full SHA was passed
return `<a href="https://github.com/${GH_ORG}/${GH_REPO}/commit/${sha}" aria-label="commit ${sha}">${sha}</a>`
}
else throw new Error(`Invalid SHA or PR ID passed to gh tag: ${id}`);
});

// Renders a section box (used for About this Technique and Guideline / SC)
eleventyConfig.addPairedShortcode(
"sectionbox",
Expand Down
Loading

0 comments on commit 05fd019

Please sign in to comment.