From 1744c596e20e209777a0ffcfa9bc029682b718e7 Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Wed, 20 Nov 2024 13:03:40 -0500 Subject: [PATCH] Add build support for linking to bibliographical references (#4124) Refs #2535 This adds support to the Eleventy build system for resolving bibliographical references within paragraphs in the format `[[ref]]`, and converting them into links within single brackets instead. This resolves against both specref.org and the local bibliography configured in `biblio.js` in this repo. Note that there are some references that have no resolution in either of these sources, which is why I'm not marking this as fully resolving the related issue. Details at https://github.com/w3c/wcag/issues/2535#issuecomment-2445246302 --- 11ty/CustomLiquid.ts | 21 +++++++++- 11ty/biblio.ts | 35 +++++++++++++++++ package-lock.json | 91 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 11ty/biblio.ts diff --git a/11ty/CustomLiquid.ts b/11ty/CustomLiquid.ts index 9afe6829a3..d18d4dcf78 100644 --- a/11ty/CustomLiquid.ts +++ b/11ty/CustomLiquid.ts @@ -8,6 +8,7 @@ import { basename } from "path"; import type { GlobalData } from "eleventy.config"; +import { biblioPattern, getBiblio } from "./biblio"; import { flattenDom, load } from "./cheerio"; import { generateId } from "./common"; import { getTermsMap } from "./guidelines"; @@ -21,6 +22,7 @@ const indexPattern = /(techniques|understanding)\/(index|about)\.html$/; const techniquesPattern = /\btechniques\//; const understandingPattern = /\bunderstanding\//; +const biblio = await getBiblio(); const termsMap = await getTermsMap(); const termLinkSelector = "a:not([href])"; @@ -89,7 +91,7 @@ export class CustomLiquid extends Liquid { const isIndex = indexPattern.test(filepath); const isTechniques = techniquesPattern.test(filepath); const isUnderstanding = understandingPattern.test(filepath); - + if (!isTechniques && !isUnderstanding) return super.parse(html); const $ = flattenDom(html, filepath); @@ -507,7 +509,7 @@ export class CustomLiquid extends Liquid {

${$el.html()}

`); }); - + // Add header to example sections in Key Terms (aside) and Conformance (div) $("aside.example, div.example").each((_, el) => { const $el = $(el); @@ -540,6 +542,21 @@ export class CustomLiquid extends Liquid { }); } + // Link biblio references + if (scope.isUnderstanding) { + $("p").each((_, el) => { + const $el = $(el); + const html = $el.html(); + if (html && biblioPattern.test(html)) { + $el.html( + html.replace(biblioPattern, (substring, code) => + biblio[code]?.href ? `[${code}]` : substring + ) + ); + } + }); + } + // Allow autogenerating missing top-level section IDs in understanding docs, // but don't pick up incorrectly-nested sections in some techniques pages (e.g. H91) const sectionSelector = scope.isUnderstanding ? "section" : "section[id]:not(.obsolete)"; diff --git a/11ty/biblio.ts b/11ty/biblio.ts new file mode 100644 index 0000000000..24a809fdb8 --- /dev/null +++ b/11ty/biblio.ts @@ -0,0 +1,35 @@ +import axios from "axios"; +import { readFile } from "fs/promises"; +import { glob } from "glob"; +import uniq from "lodash-es/uniq"; + +export const biblioPattern = /\[\[\??([\w-]+)\]\]/g; + +/** Compiles URLs from local biblio + specref for linking in Understanding documents. */ +export async function getBiblio() { + const localBiblio = eval( + (await readFile("biblio.js", "utf8")) + .replace(/^respecConfig\.localBiblio\s*=\s*/, "(") + .replace("};", "})") + ); + + const refs: string[] = []; + for (const path of await glob(["guidelines/**/*.html", "understanding/*/*.html"])) { + const content = await readFile(path, "utf8"); + let match; + while ((match = biblioPattern.exec(content))) if (!localBiblio[match[1]]) refs.push(match[1]); + } + const uniqueRefs = uniq(refs); + + const response = await axios.get(`https://api.specref.org/bibrefs?refs=${uniqueRefs.join(",")}`); + const fullBiblio = { + ...response.data, + ...localBiblio, + }; + + const resolvedRefs = Object.keys(fullBiblio); + const unresolvedRefs = uniqueRefs.filter((ref) => !resolvedRefs.includes(ref)); + if (unresolvedRefs.length) console.warn(`Unresolved biblio refs: ${unresolvedRefs.join(", ")}`); + + return fullBiblio; +} diff --git a/package-lock.json b/package-lock.json index 8f37dfbe7d..a7e86f1108 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "W3C", "dependencies": { "@11ty/eleventy": "^3.0.0", + "axios": "^1.7.7", "cheerio": "^1.0.0", "glob": "^10.3.16", "gray-matter": "^4.0.3", @@ -800,6 +801,21 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1076,6 +1092,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", @@ -1216,6 +1243,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1570,6 +1605,25 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -1585,6 +1639,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -2117,6 +2184,25 @@ "node": ">=10.0.0" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", @@ -2448,6 +2534,11 @@ "asap": "~2.0.3" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",