Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address remaining unresolved biblio references #4174

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions 11ty/CustomLiquid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { basename } from "path";

import type { GlobalData } from "eleventy.config";

import { biblioPattern, getBiblio } from "./biblio";
import { biblioPattern, getBiblio, getXmlBiblio } from "./biblio";
import { flattenDom, load, type CheerioAnyNode } from "./cheerio";
import { generateId } from "./common";
import { getAcknowledgementsForVersion, type TermsMap } from "./guidelines";
Expand All @@ -22,12 +22,22 @@ const techniquesPattern = /\btechniques\//;
const understandingPattern = /\bunderstanding\//;

const biblio = await getBiblio();
const xmlBiblio = await getXmlBiblio();
const termLinkSelector = "a:not([href])";

/** Generates {% include "foo.html" %} directives from 1 or more basenames */
const generateIncludes = (...basenames: string[]) =>
`\n${basenames.map((basename) => `{% include "${basename}.html" %}`).join("\n")}\n`;

/** Version of generateIncludes for a single include with parameters */
const generateIncludeWithParams = (basename: string, params: Record<string, string>) => {
const strParams = Object.entries(params).reduce(
(str, [key, value]) => `${str}, ${key}: ${JSON.stringify(value)}`,
""
);
return `\n{% include "${basename}.html"${strParams} %}\n`;
};

/**
* Determines whether a given string is actually HTML,
* not e.g. a data value Eleventy sent to the templating engine.
Expand Down Expand Up @@ -278,8 +288,9 @@ export class CustomLiquid extends Liquid {
// Expand techniques links to always include title
$(understandingToTechniqueLinkSelector).each((_, el) => expandTechniqueLink($(el)));

// Add key terms by default, to be removed in #parse if there are no terms
$("body").append(generateIncludes("understanding/key-terms"));
// Add key terms and references by default, to be removed in #parse if not needed
$("body").append(generateIncludeWithParams("dl-section", { title: "Key Terms" }));
$("body").append(generateIncludeWithParams("dl-section", { title: "References" }));
}

// Remove h2-level sections with no content other than heading
Expand Down Expand Up @@ -469,7 +480,7 @@ 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>` +
`\n <dt id="${term.id}">${term.name}</dt>` +
`<dd><definition>${term.definition}</definition></dd>`
);
}
Expand Down Expand Up @@ -567,17 +578,34 @@ export class CustomLiquid extends Liquid {

// Link biblio references
if (scope.isUnderstanding) {
const xmlBiblioReferences: string[] = [];
$("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 ? `[<a href="${biblio[code].href}">${code}</a>]` : substring
)
html.replace(biblioPattern, (substring, code) => {
if (biblio[code]?.href) return `[<a href="${biblio[code].href}">${code}</a>]`;
if (code in xmlBiblio) {
xmlBiblioReferences.push(code);
return `[<a href="#${code}">${code}</a>]`;
}
console.warn(`${scope.page.inputPath}: Unresolved biblio ref: ${code}`);
return substring;
})
);
}
});

// Populate references section, or remove if unused
if (xmlBiblioReferences.length) {
for (const ref of uniq(xmlBiblioReferences).sort()) {
$("section#references dl").append(
`\n <dt id="${ref}">${ref}</dt>` +
`<dd><definition>${xmlBiblio[ref]}</definition></dd>`
);
}
} else $("section#references").remove();
}

// Allow autogenerating missing top-level section IDs in understanding docs,
Expand Down
35 changes: 28 additions & 7 deletions 11ty/biblio.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import axios from "axios";
import { readFile } from "fs/promises";
import { glob } from "glob";
import invert from "lodash-es/invert";
import uniq from "lodash-es/uniq";
import { join } from "path";

import { loadFromFile } from "./cheerio";
import { wrapAxiosRequest } from "./common";

export const biblioPattern = /\[\[\??([\w-]+)\]\]/g;

// Resolve necessary aliases locally rather than requiring an extra round trip to specref
const aliases = {
"css3-values": "css-values-3",
};

/** Compiles URLs from local biblio + specref for linking in Understanding documents. */
export async function getBiblio() {
const localBiblio = eval(
Expand All @@ -21,19 +29,32 @@ export async function getBiblio() {
let match;
while ((match = biblioPattern.exec(content))) if (!localBiblio[match[1]]) refs.push(match[1]);
}
const uniqueRefs = uniq(refs);
const uniqueRefs = uniq(refs).map((ref) =>
ref in aliases ? aliases[ref as keyof typeof aliases] : ref
);

const response = await wrapAxiosRequest(
axios.get(`https://api.specref.org/bibrefs?refs=${uniqueRefs.join(",")}`)
);
const fullBiblio = {
for (const [from, to] of Object.entries(invert(aliases)))
response.data[to] = response.data[from];

return {
...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;
/** Returns mapping of references included in WCAG 2.0, which largely lack URLs */
export async function getXmlBiblio() {
const xmlRefs: Record<string, string> = {};
const $ = await loadFromFile(join("wcag20", "sources", "guide-to-wcag2-src.xml"));
$("bibl[id]").each((_, el) => {
const $ref = $(`#${el.attribs.id}`);
if (!$ref.length) return;
// Note: Using text() drops links (<loc> tags in the XML),
// but the only link within refs that are actually used seems to be broken
xmlRefs[el.attribs.id] = $ref.text();
});
return xmlRefs;
}
6 changes: 6 additions & 0 deletions _includes/dl-section.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<section id="{{ title | slugify }}">
<details>
<summary><h2>{{ title }}</h2></summary>
<dl></dl>
</details>
</section>
6 changes: 0 additions & 6 deletions _includes/understanding/key-terms.html

This file was deleted.