@openuji/speculator 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/bin/speculator.mjs +76 -0
- package/dist/browser.cjs +982 -366
- package/dist/browser.d.cts +188 -29
- package/dist/browser.d.ts +188 -29
- package/dist/browser.js +9 -1
- package/dist/chunk-H7C7MOU6.js +28 -0
- package/dist/chunk-V4GSFQE7.js +1646 -0
- package/dist/cli/export-assertions.cjs +1710 -0
- package/dist/cli/export-assertions.d.cts +25 -0
- package/dist/cli/export-assertions.d.ts +25 -0
- package/dist/cli/export-assertions.js +107 -0
- package/dist/node.cjs +982 -366
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +14 -23
- package/package.json +13 -4
- package/dist/chunk-U4GQ6EOV.js +0 -1027
package/dist/node.cjs
CHANGED
|
@@ -32,21 +32,25 @@ var node_exports = {};
|
|
|
32
32
|
__export(node_exports, {
|
|
33
33
|
DOMHtmlRenderer: () => DOMHtmlRenderer,
|
|
34
34
|
FormatProcessor: () => FormatProcessor,
|
|
35
|
+
FormatRegistry: () => FormatRegistry,
|
|
35
36
|
IncludeProcessor: () => IncludeProcessor,
|
|
36
37
|
LinkedomHtmlRenderer: () => LinkedomHtmlRenderer,
|
|
37
38
|
RespecXrefResolver: () => RespecXrefResolver,
|
|
38
39
|
Speculator: () => Speculator2,
|
|
39
40
|
SpeculatorError: () => SpeculatorError,
|
|
41
|
+
StatsTracker: () => StatsTracker,
|
|
40
42
|
browserFileLoader: () => browserFileLoader,
|
|
41
43
|
createFallbackFileLoader: () => createFallbackFileLoader,
|
|
42
44
|
createMarkdownRenderer: () => createMarkdownRenderer,
|
|
45
|
+
fromRespecConfig: () => fromRespecConfig,
|
|
46
|
+
getChangedOutputAreas: () => getChangedOutputAreas,
|
|
43
47
|
getDefaultFileLoader: () => getDefaultFileLoader,
|
|
44
48
|
nodeFileLoader: () => nodeFileLoader,
|
|
45
49
|
parseMarkdown: () => parseMarkdown
|
|
46
50
|
});
|
|
47
51
|
module.exports = __toCommonJS(node_exports);
|
|
48
52
|
|
|
49
|
-
// src/utils/file-loader.ts
|
|
53
|
+
// src/utils/file-loader/node.ts
|
|
50
54
|
var nodeFileLoader = async (path) => {
|
|
51
55
|
const fs = await import("fs/promises");
|
|
52
56
|
const { fileURLToPath } = await import("url");
|
|
@@ -57,6 +61,8 @@ var nodeFileLoader = async (path) => {
|
|
|
57
61
|
throw new Error(`Failed to load file: ${path}. ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
58
62
|
}
|
|
59
63
|
};
|
|
64
|
+
|
|
65
|
+
// src/utils/file-loader/browser.ts
|
|
60
66
|
var browserFileLoader = async (path) => {
|
|
61
67
|
try {
|
|
62
68
|
const response = await fetch(path);
|
|
@@ -68,6 +74,8 @@ var browserFileLoader = async (path) => {
|
|
|
68
74
|
throw new Error(`Failed to fetch file: ${path}. ${error instanceof Error ? error.message : "Network error"}`);
|
|
69
75
|
}
|
|
70
76
|
};
|
|
77
|
+
|
|
78
|
+
// src/utils/file-loader/index.ts
|
|
71
79
|
function createFallbackFileLoader(loaders) {
|
|
72
80
|
return async (path) => {
|
|
73
81
|
let lastError;
|
|
@@ -101,6 +109,138 @@ var SpeculatorError = class extends Error {
|
|
|
101
109
|
this.name = "SpeculatorError";
|
|
102
110
|
}
|
|
103
111
|
};
|
|
112
|
+
function fromRespecConfig(respec) {
|
|
113
|
+
return { ...respec };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/utils/stats-tracker.ts
|
|
117
|
+
var StatsTracker = class {
|
|
118
|
+
constructor() {
|
|
119
|
+
this._stats = {
|
|
120
|
+
elementsProcessed: 0,
|
|
121
|
+
filesIncluded: 0,
|
|
122
|
+
markdownBlocks: 0,
|
|
123
|
+
processingTime: 0
|
|
124
|
+
};
|
|
125
|
+
this.startTime = null;
|
|
126
|
+
}
|
|
127
|
+
start() {
|
|
128
|
+
this.startTime = performance.now();
|
|
129
|
+
}
|
|
130
|
+
stop() {
|
|
131
|
+
if (this.startTime !== null) {
|
|
132
|
+
this._stats.processingTime += performance.now() - this.startTime;
|
|
133
|
+
this.startTime = null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
incrementElements(count = 1) {
|
|
137
|
+
this._stats.elementsProcessed += count;
|
|
138
|
+
}
|
|
139
|
+
incrementFiles(count = 1) {
|
|
140
|
+
this._stats.filesIncluded += count;
|
|
141
|
+
}
|
|
142
|
+
incrementMarkdownBlocks(count = 1) {
|
|
143
|
+
this._stats.markdownBlocks += count;
|
|
144
|
+
}
|
|
145
|
+
toJSON() {
|
|
146
|
+
return { ...this._stats };
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/renderers/sections-renderer.ts
|
|
151
|
+
var SectionsRenderer = class {
|
|
152
|
+
constructor(speculator) {
|
|
153
|
+
this.speculator = speculator;
|
|
154
|
+
}
|
|
155
|
+
async render(sections = [], tracker = new StatsTracker()) {
|
|
156
|
+
const warnings = [];
|
|
157
|
+
const processed = [];
|
|
158
|
+
for (const section of sections) {
|
|
159
|
+
try {
|
|
160
|
+
if (section.hasAttribute("data-include") || section.hasAttribute("data-format") || section.querySelector("[data-include],[data-format]")) {
|
|
161
|
+
const res = await this.speculator.processElement(section, tracker);
|
|
162
|
+
processed.push(res.element);
|
|
163
|
+
warnings.push(...res.warnings);
|
|
164
|
+
} else {
|
|
165
|
+
processed.push(section);
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
warnings.push(
|
|
169
|
+
`Failed to process element: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
170
|
+
);
|
|
171
|
+
processed.push(section);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { sections: processed, warnings, stats: tracker.toJSON() };
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/renderers/header-renderer.ts
|
|
179
|
+
var HeaderRenderer = class {
|
|
180
|
+
render(header) {
|
|
181
|
+
const result = {};
|
|
182
|
+
if (header) result.header = header;
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// src/renderers/sotd-renderer.ts
|
|
188
|
+
var SotdRenderer = class {
|
|
189
|
+
render(sotd) {
|
|
190
|
+
const result = {};
|
|
191
|
+
if (sotd) result.sotd = sotd;
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/document-builder.ts
|
|
197
|
+
var DocumentBuilder = class {
|
|
198
|
+
constructor(speculator, htmlRenderer) {
|
|
199
|
+
this.htmlRenderer = htmlRenderer;
|
|
200
|
+
this.headerRenderer = new HeaderRenderer();
|
|
201
|
+
this.sotdRenderer = new SotdRenderer();
|
|
202
|
+
this.sectionsRenderer = new SectionsRenderer(speculator);
|
|
203
|
+
}
|
|
204
|
+
async build(config) {
|
|
205
|
+
const tracker = new StatsTracker();
|
|
206
|
+
const sections = config.sections || [];
|
|
207
|
+
const header = config.header;
|
|
208
|
+
const sotd = config.sotd;
|
|
209
|
+
const baseDoc = (header || sotd || sections[0])?.ownerDocument;
|
|
210
|
+
const preContainer = baseDoc ? baseDoc.createElement("div") : this.htmlRenderer.parse("");
|
|
211
|
+
if (header) preContainer.appendChild(header);
|
|
212
|
+
if (sotd) preContainer.appendChild(sotd);
|
|
213
|
+
for (const section of sections) preContainer.appendChild(section);
|
|
214
|
+
const hooks = config.preProcess;
|
|
215
|
+
if (hooks) {
|
|
216
|
+
for (const hook of hooks) {
|
|
217
|
+
await hook(preContainer);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const children = Array.from(preContainer.children);
|
|
221
|
+
const updatedSections = [];
|
|
222
|
+
for (const child of children) {
|
|
223
|
+
if (child === header || child === sotd) continue;
|
|
224
|
+
updatedSections.push(child);
|
|
225
|
+
}
|
|
226
|
+
const { sections: processedSections, warnings, stats } = await this.sectionsRenderer.render(updatedSections, tracker);
|
|
227
|
+
const { header: renderedHeader } = this.headerRenderer.render(
|
|
228
|
+
header?.cloneNode(true)
|
|
229
|
+
);
|
|
230
|
+
const { sotd: renderedSotd } = this.sotdRenderer.render(
|
|
231
|
+
sotd?.cloneNode(true)
|
|
232
|
+
);
|
|
233
|
+
const finalBaseDoc = (renderedHeader || renderedSotd || processedSections[0])?.ownerDocument;
|
|
234
|
+
const container = finalBaseDoc ? finalBaseDoc.createElement("div") : this.htmlRenderer.parse("");
|
|
235
|
+
if (renderedHeader) container.appendChild(renderedHeader);
|
|
236
|
+
if (renderedSotd) container.appendChild(renderedSotd);
|
|
237
|
+
for (const section of processedSections) container.appendChild(section);
|
|
238
|
+
const result = { container, stats, warnings };
|
|
239
|
+
if (renderedHeader) result.header = renderedHeader;
|
|
240
|
+
if (renderedSotd) result.sotd = renderedSotd;
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
104
244
|
|
|
105
245
|
// src/pipeline/postprocess.ts
|
|
106
246
|
var Postprocessor = class {
|
|
@@ -108,70 +248,106 @@ var Postprocessor = class {
|
|
|
108
248
|
this.passes = passes;
|
|
109
249
|
}
|
|
110
250
|
/**
|
|
111
|
-
* Run the configured passes
|
|
112
|
-
*
|
|
113
|
-
* @param root The document root to process.
|
|
251
|
+
* Run the configured passes.
|
|
114
252
|
* @param areas Optional list of output areas to run. If omitted, all passes
|
|
115
253
|
* are executed.
|
|
116
254
|
* @param options Configuration options for the passes.
|
|
117
255
|
*/
|
|
118
|
-
async run(
|
|
119
|
-
const
|
|
120
|
-
const outputs = {};
|
|
256
|
+
async run(config, areas) {
|
|
257
|
+
const ctx = { outputs: {}, warnings: [], config };
|
|
121
258
|
const active = areas ? this.passes.filter((p) => areas.includes(p.area)) : this.passes;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
259
|
+
const composed = compose(active);
|
|
260
|
+
await composed(ctx);
|
|
261
|
+
return { outputs: ctx.outputs, warnings: ctx.warnings };
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
function compose(passes) {
|
|
265
|
+
return function run(ctx) {
|
|
266
|
+
let index = -1;
|
|
267
|
+
async function dispatch(i) {
|
|
268
|
+
if (i <= index) return;
|
|
269
|
+
index = i;
|
|
270
|
+
const pass = passes[i];
|
|
271
|
+
if (!pass) return;
|
|
272
|
+
await pass.run(ctx, () => dispatch(i + 1));
|
|
273
|
+
}
|
|
274
|
+
return dispatch(0);
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/pipeline/pipeline-runner.ts
|
|
279
|
+
var PipelineRunner = class {
|
|
280
|
+
constructor(passFactory) {
|
|
281
|
+
this.passFactory = passFactory;
|
|
282
|
+
}
|
|
283
|
+
run(container, config, areas) {
|
|
284
|
+
const passes = this.passFactory(container);
|
|
285
|
+
const processor = new Postprocessor(passes);
|
|
286
|
+
return processor.run(config, areas);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// src/utils/logger.ts
|
|
291
|
+
var logger = {
|
|
292
|
+
debug: (...args) => {
|
|
293
|
+
if (process.env.NODE_ENV !== "production") {
|
|
294
|
+
console.debug(...args);
|
|
131
295
|
}
|
|
132
|
-
return { outputs, warnings };
|
|
133
296
|
}
|
|
134
297
|
};
|
|
135
298
|
|
|
136
299
|
// src/processors/include-processor.ts
|
|
137
300
|
var IncludeProcessor = class {
|
|
138
|
-
constructor(baseUrl, fileLoader,
|
|
301
|
+
constructor(baseUrl, fileLoader, formatRegistry) {
|
|
139
302
|
this.baseUrl = baseUrl;
|
|
140
303
|
this.fileLoader = fileLoader;
|
|
141
|
-
this.
|
|
304
|
+
this.formatRegistry = formatRegistry;
|
|
305
|
+
}
|
|
306
|
+
matches(element) {
|
|
307
|
+
return element.hasAttribute("data-include");
|
|
142
308
|
}
|
|
143
|
-
async process(element,
|
|
309
|
+
async process(element, tracker, warnings) {
|
|
144
310
|
const includePath = element.getAttribute("data-include");
|
|
145
311
|
const includeFormat = element.getAttribute("data-include-format") || "text";
|
|
146
312
|
if (!includePath) {
|
|
147
313
|
warnings.push("data-include attribute is empty");
|
|
148
|
-
|
|
314
|
+
element.removeAttribute("data-include");
|
|
315
|
+
element.removeAttribute("data-include-format");
|
|
316
|
+
return { content: null };
|
|
149
317
|
}
|
|
150
318
|
try {
|
|
151
319
|
const fullPath = this.resolveFilePath(includePath);
|
|
152
320
|
const content = await this.fileLoader(fullPath);
|
|
153
|
-
const processedContent = this.
|
|
154
|
-
|
|
155
|
-
stats.filesIncluded++;
|
|
321
|
+
const processedContent = this.formatRegistry.processContent(content, includeFormat);
|
|
322
|
+
tracker.incrementFiles();
|
|
156
323
|
if (includeFormat === "markdown") {
|
|
157
|
-
|
|
324
|
+
tracker.incrementMarkdownBlocks();
|
|
158
325
|
}
|
|
326
|
+
element.removeAttribute("data-include");
|
|
327
|
+
element.removeAttribute("data-include-format");
|
|
328
|
+
return { content: processedContent };
|
|
159
329
|
} catch (error) {
|
|
160
330
|
const errorMsg = `Failed to load: ${includePath}`;
|
|
161
|
-
|
|
162
|
-
element.
|
|
163
|
-
|
|
331
|
+
element.removeAttribute("data-include");
|
|
332
|
+
element.removeAttribute("data-include-format");
|
|
333
|
+
return { content: null, error: errorMsg };
|
|
164
334
|
}
|
|
165
|
-
element.removeAttribute("data-include");
|
|
166
|
-
element.removeAttribute("data-include-format");
|
|
167
335
|
}
|
|
168
336
|
resolveFilePath(path) {
|
|
169
337
|
const filePath = new URL(path, this.baseUrl || "file:///").toString();
|
|
170
|
-
|
|
338
|
+
logger.debug(`Resolved file path: ${filePath}`);
|
|
171
339
|
return filePath;
|
|
172
340
|
}
|
|
173
341
|
};
|
|
174
342
|
|
|
343
|
+
// src/utils/strip-ident.ts
|
|
344
|
+
function stripIndent(content) {
|
|
345
|
+
const lines = content.replace(/\r\n?/g, "\n").split("\n");
|
|
346
|
+
const indents = lines.filter((line) => line.trim().length > 0).map((line) => line.match(/^\s*/)?.[0].length || 0);
|
|
347
|
+
const minIndent = indents.length > 0 ? Math.min(...indents) : 0;
|
|
348
|
+
return lines.map((line) => line.slice(minIndent)).join("\n");
|
|
349
|
+
}
|
|
350
|
+
|
|
175
351
|
// src/markdown/index.ts
|
|
176
352
|
var import_markdown_it = __toESM(require("markdown-it"), 1);
|
|
177
353
|
|
|
@@ -252,6 +428,72 @@ function respecCitePlugin(md) {
|
|
|
252
428
|
md.inline.ruler.before("link", NAME, tokenize);
|
|
253
429
|
}
|
|
254
430
|
|
|
431
|
+
// src/markdown/plugins/mermaid.ts
|
|
432
|
+
function ensureDom() {
|
|
433
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (typeof require !== "function") {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const { DOMParser: DOMParser3 } = require("linkedom");
|
|
440
|
+
const { document: doc } = new DOMParser3().parseFromString("<html></html>", "text/html");
|
|
441
|
+
globalThis.window = doc.defaultView;
|
|
442
|
+
globalThis.document = doc;
|
|
443
|
+
}
|
|
444
|
+
function getMermaidAPI() {
|
|
445
|
+
const globalMermaid = globalThis.mermaid;
|
|
446
|
+
if (globalMermaid?.mermaidAPI) {
|
|
447
|
+
return globalMermaid.mermaidAPI;
|
|
448
|
+
}
|
|
449
|
+
if (globalThis.mermaidAPI) {
|
|
450
|
+
return globalThis.mermaidAPI;
|
|
451
|
+
}
|
|
452
|
+
if (typeof require === "function") {
|
|
453
|
+
try {
|
|
454
|
+
return require("mermaid").mermaidAPI;
|
|
455
|
+
} catch {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
function respecMermaidPlugin(md, config = {}) {
|
|
462
|
+
const defaultFence = md.renderer.rules.fence ?? ((tokens, idx) => `<pre><code>${md.utils.escapeHtml(tokens[idx].content)}</code></pre>
|
|
463
|
+
`);
|
|
464
|
+
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
|
465
|
+
const token = tokens[idx];
|
|
466
|
+
if (token.info.trim() !== "mermaid") {
|
|
467
|
+
return defaultFence(tokens, idx, options, env, self);
|
|
468
|
+
}
|
|
469
|
+
const mermaidAPI = getMermaidAPI();
|
|
470
|
+
if (!mermaidAPI) {
|
|
471
|
+
const code = md.utils.escapeHtml(token.content);
|
|
472
|
+
return `<pre><code>${code}</code></pre>
|
|
473
|
+
`;
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
ensureDom();
|
|
477
|
+
mermaidAPI.initialize(config);
|
|
478
|
+
const id = `mermaid-${idx}`;
|
|
479
|
+
const svg = mermaidAPI.render(id, token.content);
|
|
480
|
+
return `<div class="mermaid">${svg}</div>`;
|
|
481
|
+
} catch {
|
|
482
|
+
const code = md.utils.escapeHtml(token.content);
|
|
483
|
+
return `<pre><code>${code}</code></pre>
|
|
484
|
+
`;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/utils/render.ts
|
|
490
|
+
function insertContent(element, content) {
|
|
491
|
+
element.innerHTML = content;
|
|
492
|
+
}
|
|
493
|
+
function renderError(message) {
|
|
494
|
+
return `<p class="error">${message}</p>`;
|
|
495
|
+
}
|
|
496
|
+
|
|
255
497
|
// src/markdown/index.ts
|
|
256
498
|
function createMarkdownRenderer(options = {}) {
|
|
257
499
|
const md = new import_markdown_it.default({
|
|
@@ -289,6 +531,10 @@ function createMarkdownRenderer(options = {}) {
|
|
|
289
531
|
md.use(respecConceptPlugin);
|
|
290
532
|
md.use(respecIdlPlugin);
|
|
291
533
|
md.use(respecCitePlugin);
|
|
534
|
+
if (options.mermaid) {
|
|
535
|
+
const config = options.mermaid === true ? {} : options.mermaid;
|
|
536
|
+
md.use(respecMermaidPlugin, config);
|
|
537
|
+
}
|
|
292
538
|
for (const extension of options.extensions ?? []) {
|
|
293
539
|
if (Array.isArray(extension)) {
|
|
294
540
|
const [plugin, pluginOptions] = extension;
|
|
@@ -305,56 +551,88 @@ function parseMarkdown(markdown, options = {}, env) {
|
|
|
305
551
|
return md.render(markdown, env);
|
|
306
552
|
} catch (error) {
|
|
307
553
|
console.error("Markdown parsing error:", error);
|
|
308
|
-
return
|
|
554
|
+
return renderError(
|
|
555
|
+
`Error parsing markdown: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
556
|
+
);
|
|
309
557
|
}
|
|
310
558
|
}
|
|
311
559
|
function slugify(content) {
|
|
312
560
|
return content.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-");
|
|
313
561
|
}
|
|
314
562
|
|
|
315
|
-
// src/
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
var
|
|
325
|
-
|
|
326
|
-
|
|
563
|
+
// src/format-registry.ts
|
|
564
|
+
var MarkdownStrategy = class {
|
|
565
|
+
constructor(options = {}) {
|
|
566
|
+
this.options = options;
|
|
567
|
+
}
|
|
568
|
+
convert(content) {
|
|
569
|
+
return parseMarkdown(content, this.options);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var PassthroughStrategy = class {
|
|
573
|
+
convert(content) {
|
|
574
|
+
return content;
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
var FormatRegistry = class {
|
|
578
|
+
constructor(markdownOptions = {}, customStrategies = {}) {
|
|
579
|
+
const markdown = new MarkdownStrategy(markdownOptions);
|
|
580
|
+
const passthrough = new PassthroughStrategy();
|
|
581
|
+
this.strategies = /* @__PURE__ */ new Map([
|
|
582
|
+
["markdown", markdown],
|
|
583
|
+
["text", passthrough],
|
|
584
|
+
["html", passthrough]
|
|
585
|
+
]);
|
|
586
|
+
for (const [format, strategy] of Object.entries(customStrategies)) {
|
|
587
|
+
this.strategies.set(format, strategy);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Register a new strategy for a given format.
|
|
592
|
+
*/
|
|
593
|
+
register(format, strategy) {
|
|
594
|
+
this.strategies.set(format, strategy);
|
|
327
595
|
}
|
|
328
596
|
/**
|
|
329
597
|
* Convert content based on the specified format.
|
|
330
598
|
*/
|
|
331
599
|
processContent(content, format) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
case "text":
|
|
336
|
-
case "html":
|
|
337
|
-
default:
|
|
338
|
-
return content;
|
|
600
|
+
const strategy = this.strategies.get(format);
|
|
601
|
+
if (!strategy) {
|
|
602
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
339
603
|
}
|
|
604
|
+
return strategy.convert(content);
|
|
340
605
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// src/processors/format-processor.ts
|
|
609
|
+
var FormatProcessor = class {
|
|
610
|
+
constructor(registry = new FormatRegistry()) {
|
|
611
|
+
this.registry = registry;
|
|
612
|
+
}
|
|
613
|
+
matches(element) {
|
|
614
|
+
return element.hasAttribute("data-format");
|
|
615
|
+
}
|
|
616
|
+
process(element, tracker, _warnings) {
|
|
345
617
|
const format = element.getAttribute("data-format");
|
|
618
|
+
let result = {};
|
|
346
619
|
if (format === "markdown" && element.innerHTML.trim()) {
|
|
347
620
|
try {
|
|
348
621
|
const markdownContent = stripIndent(element.innerHTML).trim();
|
|
349
|
-
|
|
350
|
-
|
|
622
|
+
result.content = this.registry.processContent(markdownContent, format);
|
|
623
|
+
tracker.incrementMarkdownBlocks();
|
|
624
|
+
} catch (error) {
|
|
625
|
+
result.error = `Failed to process markdown: ${error instanceof Error ? error.message : "Unknown error"}`;
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
try {
|
|
629
|
+
result.content = this.registry.processContent(element.innerHTML, format);
|
|
351
630
|
} catch (error) {
|
|
352
|
-
|
|
353
|
-
warnings.push(errorMsg);
|
|
354
|
-
element.innerHTML = `<p class="error">${errorMsg}</p>`;
|
|
631
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
355
632
|
}
|
|
356
633
|
}
|
|
357
634
|
element.removeAttribute("data-format");
|
|
635
|
+
return result;
|
|
358
636
|
}
|
|
359
637
|
};
|
|
360
638
|
|
|
@@ -498,18 +776,27 @@ function resolveIdlLinks(root, index, warnings, suppressClass) {
|
|
|
498
776
|
}
|
|
499
777
|
});
|
|
500
778
|
}
|
|
501
|
-
var
|
|
502
|
-
|
|
503
|
-
|
|
779
|
+
var IdlPass = class {
|
|
780
|
+
constructor(root) {
|
|
781
|
+
this.root = root;
|
|
782
|
+
this.area = "idl";
|
|
783
|
+
}
|
|
784
|
+
async execute(_data, config) {
|
|
504
785
|
const warnings = [];
|
|
505
|
-
const suppressClass =
|
|
506
|
-
const index = buildIdlIndex(root, warnings);
|
|
507
|
-
resolveIdlLinks(root, index, warnings, suppressClass);
|
|
786
|
+
const suppressClass = config.postprocess?.diagnostics?.suppressClass ?? "no-link-warnings";
|
|
787
|
+
const index = buildIdlIndex(this.root, warnings);
|
|
788
|
+
resolveIdlLinks(this.root, index, warnings, suppressClass);
|
|
508
789
|
return { warnings };
|
|
509
790
|
}
|
|
791
|
+
async run(ctx, next) {
|
|
792
|
+
const current = ctx.outputs[this.area];
|
|
793
|
+
const { warnings } = await this.execute(current, ctx.config);
|
|
794
|
+
if (warnings && warnings.length) ctx.warnings.push(...warnings);
|
|
795
|
+
await next();
|
|
796
|
+
}
|
|
510
797
|
};
|
|
511
798
|
|
|
512
|
-
// src/
|
|
799
|
+
// src/xref/local-map.ts
|
|
513
800
|
function uniqueId2(doc, base) {
|
|
514
801
|
let id = base;
|
|
515
802
|
let i = 2;
|
|
@@ -522,20 +809,6 @@ function norm2(term) {
|
|
|
522
809
|
function slugify2(text) {
|
|
523
810
|
return text.trim().toLowerCase().replace(/[^\w]+/g, "-").replace(/^-+|-+$/g, "");
|
|
524
811
|
}
|
|
525
|
-
function isSuppressed(node, suppressClass) {
|
|
526
|
-
return !!node.closest(`.${suppressClass}`);
|
|
527
|
-
}
|
|
528
|
-
function getCiteSpecs(node) {
|
|
529
|
-
let el = node;
|
|
530
|
-
while (el) {
|
|
531
|
-
const cite = el.getAttribute("data-cite");
|
|
532
|
-
if (cite) {
|
|
533
|
-
return cite.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean);
|
|
534
|
-
}
|
|
535
|
-
el = el.parentElement;
|
|
536
|
-
}
|
|
537
|
-
return void 0;
|
|
538
|
-
}
|
|
539
812
|
function buildLocalMap(root) {
|
|
540
813
|
const map = /* @__PURE__ */ new Map();
|
|
541
814
|
const doc = root.ownerDocument;
|
|
@@ -580,136 +853,159 @@ function buildLocalMap(root) {
|
|
|
580
853
|
});
|
|
581
854
|
return map;
|
|
582
855
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const
|
|
589
|
-
const
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (hit) {
|
|
598
|
-
a.setAttribute("href", hit.href);
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
601
|
-
const specsOverride = getCiteSpecs(a);
|
|
602
|
-
const mapKey = `${key}|${(specsOverride || []).join(",")}`;
|
|
603
|
-
let entry = unresolved.get(mapKey);
|
|
604
|
-
if (!entry) {
|
|
605
|
-
entry = { term, anchors: [], results: [] };
|
|
606
|
-
if (specsOverride) entry.specsOverride = specsOverride;
|
|
607
|
-
unresolved.set(mapKey, entry);
|
|
856
|
+
|
|
857
|
+
// src/xref/resolve.ts
|
|
858
|
+
async function resolveQueries(resolverConfigs, unresolved) {
|
|
859
|
+
const warnings = [];
|
|
860
|
+
for (const cfg of resolverConfigs) {
|
|
861
|
+
const queries = [];
|
|
862
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
863
|
+
for (const [key, entry] of unresolved.entries()) {
|
|
864
|
+
let specs;
|
|
865
|
+
if (entry.specsOverride) {
|
|
866
|
+
const allowed = cfg.specs ? entry.specsOverride.filter((s) => cfg.specs.includes(s)) : entry.specsOverride;
|
|
867
|
+
specs = allowed;
|
|
868
|
+
} else {
|
|
869
|
+
specs = cfg.specs;
|
|
608
870
|
}
|
|
609
|
-
|
|
871
|
+
if (specs && specs.length === 0) continue;
|
|
872
|
+
const id = `${key}|${queries.length}`;
|
|
873
|
+
const q = { id, term: entry.term };
|
|
874
|
+
if (specs && specs.length) q.specs = specs;
|
|
875
|
+
queries.push(q);
|
|
876
|
+
idMap.set(id, entry);
|
|
610
877
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const
|
|
614
|
-
for (const [
|
|
615
|
-
|
|
616
|
-
if (entry.
|
|
617
|
-
const allowed = cfg.specs ? entry.specsOverride.filter((s) => cfg.specs.includes(s)) : entry.specsOverride;
|
|
618
|
-
specs = allowed;
|
|
619
|
-
} else {
|
|
620
|
-
specs = cfg.specs;
|
|
621
|
-
}
|
|
622
|
-
if (specs && specs.length === 0) continue;
|
|
623
|
-
const id = `${key}|${queries.length}`;
|
|
624
|
-
const q = { id, term: entry.term };
|
|
625
|
-
if (specs && specs.length) q.specs = specs;
|
|
626
|
-
queries.push(q);
|
|
627
|
-
idMap.set(id, entry);
|
|
628
|
-
}
|
|
629
|
-
if (!queries.length) continue;
|
|
630
|
-
try {
|
|
631
|
-
const resMap = await cfg.resolver.resolveBatch(queries);
|
|
632
|
-
for (const [id, hits] of resMap.entries()) {
|
|
633
|
-
const entry = idMap.get(id);
|
|
634
|
-
if (entry) entry.results.push(...hits);
|
|
635
|
-
}
|
|
636
|
-
} catch (err) {
|
|
637
|
-
warnings.push(
|
|
638
|
-
`Xref resolver failed: ${err instanceof Error ? err.message : String(err)}`
|
|
639
|
-
);
|
|
878
|
+
if (!queries.length) continue;
|
|
879
|
+
try {
|
|
880
|
+
const resMap = await cfg.resolver.resolveBatch(queries);
|
|
881
|
+
for (const [id, hits] of resMap.entries()) {
|
|
882
|
+
const entry = idMap.get(id);
|
|
883
|
+
if (entry) entry.results.push(...hits);
|
|
640
884
|
}
|
|
885
|
+
} catch (err) {
|
|
886
|
+
warnings.push(
|
|
887
|
+
`Xref resolver failed: ${err instanceof Error ? err.message : String(err)}`
|
|
888
|
+
);
|
|
641
889
|
}
|
|
890
|
+
}
|
|
891
|
+
return { unresolved, warnings };
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// src/pipeline/passes/xref.ts
|
|
895
|
+
function isSuppressed(node, suppressClass) {
|
|
896
|
+
return !!node.closest(`.${suppressClass}`);
|
|
897
|
+
}
|
|
898
|
+
function getCiteSpecs(node) {
|
|
899
|
+
let el = node;
|
|
900
|
+
while (el) {
|
|
901
|
+
const cite = el.getAttribute("data-cite");
|
|
902
|
+
if (cite) {
|
|
903
|
+
return cite.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean);
|
|
904
|
+
}
|
|
905
|
+
el = el.parentElement;
|
|
906
|
+
}
|
|
907
|
+
return void 0;
|
|
908
|
+
}
|
|
909
|
+
var XrefPass = class {
|
|
910
|
+
constructor(root) {
|
|
911
|
+
this.root = root;
|
|
912
|
+
this.area = "xref";
|
|
913
|
+
}
|
|
914
|
+
async execute(_data, config) {
|
|
915
|
+
const suppressClass = config.postprocess?.diagnostics?.suppressClass ?? "no-link-warnings";
|
|
916
|
+
const localMap = buildLocalMap(this.root);
|
|
917
|
+
const xrefAnchors = Array.from(this.root.querySelectorAll("a[data-xref]"));
|
|
918
|
+
const resolverConfigs = Array.isArray(config.postprocess?.xref) ? config.postprocess.xref : config.postprocess?.xref ? [config.postprocess.xref] : [];
|
|
919
|
+
const unresolved = collectUnresolvedAnchors(xrefAnchors, localMap, suppressClass);
|
|
920
|
+
const { unresolved: resolvedEntries, warnings: resolveWarnings } = await resolveQueries(
|
|
921
|
+
resolverConfigs,
|
|
922
|
+
unresolved
|
|
923
|
+
);
|
|
642
924
|
const defaultPriority = resolverConfigs.flatMap((cfg) => cfg.specs || []);
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
925
|
+
const applyWarnings = applyXrefResults(resolvedEntries, defaultPriority);
|
|
926
|
+
return { warnings: [...resolveWarnings, ...applyWarnings] };
|
|
927
|
+
}
|
|
928
|
+
async run(ctx, next) {
|
|
929
|
+
const current = ctx.outputs[this.area];
|
|
930
|
+
const { warnings } = await this.execute(current, ctx.config);
|
|
931
|
+
if (warnings && warnings.length) ctx.warnings.push(...warnings);
|
|
932
|
+
await next();
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
function collectUnresolvedAnchors(anchors, localMap, suppressClass) {
|
|
936
|
+
const unresolved = /* @__PURE__ */ new Map();
|
|
937
|
+
for (const a of anchors) {
|
|
938
|
+
if (isSuppressed(a, suppressClass)) continue;
|
|
939
|
+
const term = a.getAttribute("data-xref") || "";
|
|
940
|
+
const key = norm2(term);
|
|
941
|
+
const hit = localMap.get(key);
|
|
942
|
+
if (hit) {
|
|
943
|
+
a.setAttribute("href", hit.href);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
const specsOverride = getCiteSpecs(a);
|
|
947
|
+
const mapKey = `${key}|${(specsOverride || []).join(",")}`;
|
|
948
|
+
let entry = unresolved.get(mapKey);
|
|
949
|
+
if (!entry) {
|
|
950
|
+
entry = { term, anchors: [], results: [] };
|
|
951
|
+
if (specsOverride) entry.specsOverride = specsOverride;
|
|
952
|
+
unresolved.set(mapKey, entry);
|
|
953
|
+
}
|
|
954
|
+
entry.anchors.push(a);
|
|
955
|
+
}
|
|
956
|
+
return unresolved;
|
|
957
|
+
}
|
|
958
|
+
function applyXrefResults(unresolved, defaultPriority) {
|
|
959
|
+
const warnings = [];
|
|
960
|
+
for (const entry of unresolved.values()) {
|
|
961
|
+
const hits = entry.results;
|
|
962
|
+
let chosen;
|
|
963
|
+
let ambiguous = false;
|
|
964
|
+
const preferred = entry.specsOverride && entry.specsOverride.length ? entry.specsOverride : defaultPriority;
|
|
965
|
+
if (hits.length > 0) {
|
|
966
|
+
if (preferred && preferred.length) {
|
|
967
|
+
const remaining = new Set(hits);
|
|
968
|
+
for (const spec of preferred) {
|
|
969
|
+
const matches = hits.filter((h) => h.cite === spec);
|
|
970
|
+
matches.forEach((m) => remaining.delete(m));
|
|
971
|
+
if (matches.length === 1) {
|
|
972
|
+
chosen = matches[0];
|
|
973
|
+
break;
|
|
662
974
|
}
|
|
663
|
-
if (
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
chosen = leftovers[0];
|
|
667
|
-
} else if (leftovers.length > 1) {
|
|
668
|
-
ambiguous = true;
|
|
669
|
-
}
|
|
975
|
+
if (matches.length > 1) {
|
|
976
|
+
ambiguous = true;
|
|
977
|
+
break;
|
|
670
978
|
}
|
|
671
|
-
} else if (hits.length === 1) {
|
|
672
|
-
chosen = hits[0];
|
|
673
|
-
} else {
|
|
674
|
-
ambiguous = true;
|
|
675
979
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (
|
|
980
|
+
if (!chosen && !ambiguous) {
|
|
981
|
+
const leftovers = Array.from(remaining);
|
|
982
|
+
if (leftovers.length === 1) {
|
|
983
|
+
chosen = leftovers[0];
|
|
984
|
+
} else if (leftovers.length > 1) {
|
|
985
|
+
ambiguous = true;
|
|
986
|
+
}
|
|
681
987
|
}
|
|
682
|
-
} else if (
|
|
683
|
-
|
|
988
|
+
} else if (hits.length === 1) {
|
|
989
|
+
chosen = hits[0];
|
|
684
990
|
} else {
|
|
685
|
-
|
|
991
|
+
ambiguous = true;
|
|
686
992
|
}
|
|
687
993
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
section.innerHTML = `<h2>${title}</h2>`;
|
|
699
|
-
root.appendChild(section);
|
|
700
|
-
}
|
|
701
|
-
return section;
|
|
702
|
-
}
|
|
703
|
-
function ensureSubsection(parent, id, title) {
|
|
704
|
-
let sec = parent.querySelector(`#${id}`);
|
|
705
|
-
if (!sec) {
|
|
706
|
-
sec = parent.ownerDocument.createElement("section");
|
|
707
|
-
sec.id = id;
|
|
708
|
-
sec.innerHTML = `<h3>${title}</h3><ul></ul>`;
|
|
709
|
-
parent.appendChild(sec);
|
|
994
|
+
if (chosen) {
|
|
995
|
+
for (const a of entry.anchors) {
|
|
996
|
+
a.setAttribute("href", chosen.href);
|
|
997
|
+
if (chosen.cite) a.setAttribute("data-cite", chosen.cite);
|
|
998
|
+
}
|
|
999
|
+
} else if (ambiguous) {
|
|
1000
|
+
warnings.push(`Ambiguous xref: "${entry.term}"`);
|
|
1001
|
+
} else {
|
|
1002
|
+
warnings.push(`No matching xref: "${entry.term}"`);
|
|
1003
|
+
}
|
|
710
1004
|
}
|
|
711
|
-
return
|
|
1005
|
+
return warnings;
|
|
712
1006
|
}
|
|
1007
|
+
|
|
1008
|
+
// src/renderers/references-renderer.ts
|
|
713
1009
|
function formatEntry(id, e) {
|
|
714
1010
|
const parts = [];
|
|
715
1011
|
parts.push(`<span class="ref-id">[${id}]</span>`);
|
|
@@ -725,88 +1021,111 @@ function formatEntry(id, e) {
|
|
|
725
1021
|
function idForRef(id) {
|
|
726
1022
|
return `bib-${id.toLowerCase()}`;
|
|
727
1023
|
}
|
|
728
|
-
var
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const
|
|
745
|
-
const infoSec = ensureSubsection(refs, "informative-references", "Informative references");
|
|
746
|
-
const renderList = (sec, ids) => {
|
|
1024
|
+
var ReferencesRenderer = class {
|
|
1025
|
+
constructor(doc) {
|
|
1026
|
+
this.doc = doc;
|
|
1027
|
+
}
|
|
1028
|
+
render(data) {
|
|
1029
|
+
const section = this.doc.createElement("section");
|
|
1030
|
+
section.id = "references";
|
|
1031
|
+
const normSec = this.doc.createElement("section");
|
|
1032
|
+
normSec.id = "normative-references";
|
|
1033
|
+
normSec.innerHTML = "<h3>Normative references</h3><ul></ul>";
|
|
1034
|
+
const infoSec = this.doc.createElement("section");
|
|
1035
|
+
infoSec.id = "informative-references";
|
|
1036
|
+
infoSec.innerHTML = "<h3>Informative references</h3><ul></ul>";
|
|
1037
|
+
section.innerHTML = "<h2>References</h2>";
|
|
1038
|
+
section.appendChild(normSec);
|
|
1039
|
+
section.appendChild(infoSec);
|
|
1040
|
+
const renderList = (sec, items) => {
|
|
747
1041
|
const ul = sec.querySelector("ul");
|
|
748
1042
|
ul.innerHTML = "";
|
|
749
|
-
|
|
750
|
-
const li =
|
|
1043
|
+
items.sort((a, b) => a.id.localeCompare(b.id)).forEach(({ id, entry }) => {
|
|
1044
|
+
const li = this.doc.createElement("li");
|
|
751
1045
|
li.id = idForRef(id);
|
|
752
|
-
const entry = biblio[id];
|
|
753
1046
|
if (entry) {
|
|
754
1047
|
li.innerHTML = formatEntry(id, entry);
|
|
755
1048
|
} else {
|
|
756
1049
|
li.setAttribute("data-spec", id);
|
|
757
1050
|
li.innerHTML = `<span class="ref-id">[${id}]</span> <span class="ref-missing">\u2014 unresolved reference</span>`;
|
|
758
|
-
warnings.push(`Unresolved reference: "${id}"`);
|
|
759
1051
|
}
|
|
760
1052
|
ul.appendChild(li);
|
|
761
1053
|
});
|
|
762
1054
|
};
|
|
763
|
-
renderList(normSec, normative);
|
|
764
|
-
renderList(infoSec, informative);
|
|
1055
|
+
renderList(normSec, data.normative);
|
|
1056
|
+
renderList(infoSec, data.informative);
|
|
1057
|
+
return section.outerHTML;
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// src/pipeline/passes/references.ts
|
|
1062
|
+
var ReferencesPass = class {
|
|
1063
|
+
constructor(root) {
|
|
1064
|
+
this.root = root;
|
|
1065
|
+
this.area = "references";
|
|
1066
|
+
}
|
|
1067
|
+
async execute(_data, config) {
|
|
1068
|
+
const warnings = [];
|
|
1069
|
+
const biblio = config.postprocess?.biblio?.entries ?? {};
|
|
1070
|
+
const cites = Array.from(this.root.querySelectorAll("a[data-spec]"));
|
|
1071
|
+
if (!cites.length) return { data: { html: "", citeUpdates: [] }, warnings };
|
|
1072
|
+
const normativeIds = /* @__PURE__ */ new Set();
|
|
1073
|
+
const informativeIds = /* @__PURE__ */ new Set();
|
|
1074
|
+
for (const a of cites) {
|
|
1075
|
+
const id = (a.getAttribute("data-spec") || "").trim();
|
|
1076
|
+
const norm3 = (a.getAttribute("data-normative") || "false") === "true";
|
|
1077
|
+
(norm3 ? normativeIds : informativeIds).add(id);
|
|
1078
|
+
}
|
|
1079
|
+
for (const id of normativeIds) informativeIds.delete(id);
|
|
1080
|
+
const normative = [];
|
|
1081
|
+
const informative = [];
|
|
1082
|
+
Array.from(normativeIds).sort((a, b) => a.localeCompare(b)).forEach((id) => {
|
|
1083
|
+
const entry = biblio[id];
|
|
1084
|
+
if (!entry) warnings.push(`Unresolved reference: "${id}"`);
|
|
1085
|
+
normative.push({ id, entry });
|
|
1086
|
+
});
|
|
1087
|
+
Array.from(informativeIds).sort((a, b) => a.localeCompare(b)).forEach((id) => {
|
|
1088
|
+
const entry = biblio[id];
|
|
1089
|
+
if (!entry) warnings.push(`Unresolved reference: "${id}"`);
|
|
1090
|
+
informative.push({ id, entry });
|
|
1091
|
+
});
|
|
1092
|
+
const renderer = new ReferencesRenderer(this.root.ownerDocument);
|
|
1093
|
+
const html = renderer.render({ normative, informative });
|
|
1094
|
+
const citeUpdates = [];
|
|
765
1095
|
for (const a of cites) {
|
|
766
1096
|
const id = (a.getAttribute("data-spec") || "").trim();
|
|
767
1097
|
const targetId = idForRef(id);
|
|
768
|
-
|
|
769
|
-
a.setAttribute("data-cite-ref", targetId);
|
|
1098
|
+
citeUpdates.push({ element: a, href: `#${targetId}` });
|
|
770
1099
|
}
|
|
771
|
-
return { warnings };
|
|
1100
|
+
return { data: { html, citeUpdates }, warnings };
|
|
1101
|
+
}
|
|
1102
|
+
async run(ctx, next) {
|
|
1103
|
+
const current = ctx.outputs[this.area];
|
|
1104
|
+
const { data, warnings } = await this.execute(current, ctx.config);
|
|
1105
|
+
if (data !== void 0) ctx.outputs[this.area] = data;
|
|
1106
|
+
if (warnings && warnings.length) ctx.warnings.push(...warnings);
|
|
1107
|
+
await next();
|
|
772
1108
|
}
|
|
773
1109
|
};
|
|
774
1110
|
|
|
775
1111
|
// src/pipeline/passes/boilerplate.ts
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
h2.textContent = title;
|
|
781
|
-
sec.appendChild(h2);
|
|
782
|
-
if (content) {
|
|
783
|
-
const p = doc.createElement("p");
|
|
784
|
-
p.textContent = content;
|
|
785
|
-
sec.appendChild(p);
|
|
1112
|
+
var BoilerplatePass = class {
|
|
1113
|
+
constructor(root) {
|
|
1114
|
+
this.root = root;
|
|
1115
|
+
this.area = "boilerplate";
|
|
786
1116
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
area: "boilerplate",
|
|
791
|
-
async run(root, _data, options) {
|
|
792
|
-
const bp = options.boilerplate;
|
|
793
|
-
if (!bp) return { warnings: [] };
|
|
794
|
-
const doc = root.ownerDocument;
|
|
1117
|
+
async execute(_data, config) {
|
|
1118
|
+
const bp = config.postprocess?.boilerplate;
|
|
1119
|
+
if (!bp) return { data: { sections: [], ref: null }, warnings: [] };
|
|
795
1120
|
const mountMode = bp.mount || "end";
|
|
796
1121
|
let ref = null;
|
|
797
1122
|
if (mountMode === "before-references") {
|
|
798
|
-
ref = root.querySelector("#references");
|
|
1123
|
+
ref = this.root.querySelector("#references");
|
|
799
1124
|
} else if (mountMode === "after-toc") {
|
|
800
|
-
const toc = root.querySelector("#toc");
|
|
1125
|
+
const toc = this.root.querySelector("#toc");
|
|
801
1126
|
ref = toc ? toc.nextSibling : null;
|
|
802
1127
|
}
|
|
803
|
-
const
|
|
804
|
-
if (ref) {
|
|
805
|
-
root.insertBefore(section, ref);
|
|
806
|
-
} else {
|
|
807
|
-
root.appendChild(section);
|
|
808
|
-
}
|
|
809
|
-
};
|
|
1128
|
+
const sections = [];
|
|
810
1129
|
const defs = [
|
|
811
1130
|
{ key: "conformance", title: "Conformance" },
|
|
812
1131
|
{ key: "security", title: "Security" },
|
|
@@ -817,54 +1136,138 @@ var boilerplatePass = {
|
|
|
817
1136
|
if (!opt) continue;
|
|
818
1137
|
const cfg = typeof opt === "object" ? opt : {};
|
|
819
1138
|
const id = cfg.id || key;
|
|
820
|
-
if (root.querySelector(`#${id}`)) continue;
|
|
1139
|
+
if (this.root.querySelector(`#${id}`)) continue;
|
|
821
1140
|
const title = cfg.title || defaultTitle;
|
|
822
1141
|
const content = cfg.content;
|
|
823
|
-
const
|
|
824
|
-
|
|
1142
|
+
const descriptor = { id, title };
|
|
1143
|
+
if (content !== void 0) descriptor.content = content;
|
|
1144
|
+
sections.push(descriptor);
|
|
825
1145
|
}
|
|
826
|
-
return { warnings: [] };
|
|
1146
|
+
return { data: { sections, ref }, warnings: [] };
|
|
1147
|
+
}
|
|
1148
|
+
async run(ctx, next) {
|
|
1149
|
+
const current = ctx.outputs[this.area];
|
|
1150
|
+
const { data, warnings } = await this.execute(current, ctx.config);
|
|
1151
|
+
if (data !== void 0) ctx.outputs[this.area] = data;
|
|
1152
|
+
if (warnings && warnings.length) ctx.warnings.push(...warnings);
|
|
1153
|
+
await next();
|
|
827
1154
|
}
|
|
828
1155
|
};
|
|
829
1156
|
|
|
830
|
-
// src/
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1157
|
+
// src/renderers/boilerplate-renderer.ts
|
|
1158
|
+
function createSection(doc, id, title, content) {
|
|
1159
|
+
const sec = doc.createElement("section");
|
|
1160
|
+
sec.id = id;
|
|
1161
|
+
const h2 = doc.createElement("h2");
|
|
1162
|
+
h2.textContent = title;
|
|
1163
|
+
sec.appendChild(h2);
|
|
1164
|
+
if (content) {
|
|
1165
|
+
const p = doc.createElement("p");
|
|
1166
|
+
p.textContent = content;
|
|
1167
|
+
sec.appendChild(p);
|
|
1168
|
+
}
|
|
1169
|
+
return sec;
|
|
1170
|
+
}
|
|
1171
|
+
var BoilerplateRenderer = class {
|
|
1172
|
+
constructor(doc) {
|
|
1173
|
+
this.doc = doc;
|
|
1174
|
+
}
|
|
1175
|
+
render(descriptors) {
|
|
1176
|
+
return descriptors.map((d) => createSection(this.doc, d.id, d.title, d.content));
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
// src/renderers/toc-renderer.ts
|
|
1181
|
+
var TocRenderer = class {
|
|
1182
|
+
constructor(doc) {
|
|
1183
|
+
this.doc = doc;
|
|
1184
|
+
}
|
|
1185
|
+
render(items = []) {
|
|
1186
|
+
if (!items.length) return { toc: "" };
|
|
1187
|
+
const minDepth = Math.min(...items.map((i) => i.depth));
|
|
1188
|
+
const rootOl = this.doc.createElement("ol");
|
|
1189
|
+
rootOl.setAttribute("role", "list");
|
|
1190
|
+
const olStack = [rootOl];
|
|
1191
|
+
let currentDepth = 1;
|
|
1192
|
+
for (const item of items) {
|
|
1193
|
+
const targetDepth = item.depth - minDepth + 1;
|
|
1194
|
+
while (targetDepth > currentDepth) {
|
|
1195
|
+
const newOl = this.doc.createElement("ol");
|
|
1196
|
+
newOl.setAttribute("role", "list");
|
|
1197
|
+
const parentOl = olStack[olStack.length - 1];
|
|
1198
|
+
let parentLi = parentOl.lastElementChild;
|
|
1199
|
+
if (!parentLi) {
|
|
1200
|
+
parentLi = this.doc.createElement("li");
|
|
1201
|
+
parentOl.appendChild(parentLi);
|
|
1202
|
+
}
|
|
1203
|
+
parentLi.appendChild(newOl);
|
|
1204
|
+
olStack.push(newOl);
|
|
1205
|
+
currentDepth++;
|
|
1206
|
+
}
|
|
1207
|
+
while (targetDepth < currentDepth) {
|
|
1208
|
+
olStack.pop();
|
|
1209
|
+
currentDepth--;
|
|
1210
|
+
}
|
|
1211
|
+
const currentOl = olStack[olStack.length - 1];
|
|
1212
|
+
const li = this.doc.createElement("li");
|
|
1213
|
+
li.setAttribute("data-depth", String(item.depth));
|
|
1214
|
+
const a = this.doc.createElement("a");
|
|
1215
|
+
a.href = `#${item.id}`;
|
|
1216
|
+
a.textContent = item.text;
|
|
1217
|
+
li.appendChild(a);
|
|
1218
|
+
currentOl.appendChild(li);
|
|
851
1219
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1220
|
+
return { toc: rootOl.outerHTML };
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
// src/pipeline/passes/toc.ts
|
|
1225
|
+
function collectTocItems(root) {
|
|
1226
|
+
const headings = Array.from(root.querySelectorAll("h2, h3, h4"));
|
|
1227
|
+
const items = [];
|
|
1228
|
+
for (const h of headings) {
|
|
1229
|
+
if (!h.id) continue;
|
|
1230
|
+
const depth = h.tagName.toLowerCase() === "h3" ? 2 : h.tagName.toLowerCase() === "h4" ? 3 : 1;
|
|
1231
|
+
items.push({ id: h.id, text: h.textContent || "", depth });
|
|
1232
|
+
}
|
|
1233
|
+
return items;
|
|
1234
|
+
}
|
|
1235
|
+
var TocPass = class {
|
|
1236
|
+
constructor(root) {
|
|
1237
|
+
this.root = root;
|
|
1238
|
+
this.area = "toc";
|
|
1239
|
+
}
|
|
1240
|
+
async execute(_data, config) {
|
|
1241
|
+
const { toc } = config.postprocess || {};
|
|
1242
|
+
if (toc?.enabled === false) return { data: "", warnings: [] };
|
|
1243
|
+
const items = collectTocItems(this.root);
|
|
1244
|
+
if (!items.length) return { data: "", warnings: [] };
|
|
1245
|
+
const renderer = new TocRenderer(this.root.ownerDocument);
|
|
1246
|
+
const { toc: tocHtml } = renderer.render(items);
|
|
1247
|
+
return { data: tocHtml, warnings: [] };
|
|
1248
|
+
}
|
|
1249
|
+
async run(ctx, next) {
|
|
1250
|
+
const current = ctx.outputs[this.area];
|
|
1251
|
+
const { data, warnings } = await this.execute(current, ctx.config);
|
|
1252
|
+
if (data !== void 0) ctx.outputs[this.area] = data;
|
|
1253
|
+
if (warnings && warnings.length) ctx.warnings.push(...warnings);
|
|
1254
|
+
await next();
|
|
855
1255
|
}
|
|
856
1256
|
};
|
|
857
1257
|
|
|
858
1258
|
// src/pipeline/passes/diagnostics.ts
|
|
859
|
-
var
|
|
860
|
-
|
|
861
|
-
|
|
1259
|
+
var DiagnosticsPass = class {
|
|
1260
|
+
constructor(root) {
|
|
1261
|
+
this.root = root;
|
|
1262
|
+
this.area = "diagnostics";
|
|
1263
|
+
}
|
|
1264
|
+
async execute(_data, config) {
|
|
862
1265
|
const warnings = [];
|
|
863
|
-
const suppressClass =
|
|
864
|
-
const idsAndLinks =
|
|
1266
|
+
const suppressClass = config.postprocess?.diagnostics?.suppressClass ?? "no-link-warnings";
|
|
1267
|
+
const idsAndLinks = config.postprocess?.diagnostics?.idsAndLinks ?? true;
|
|
865
1268
|
if (idsAndLinks) {
|
|
866
1269
|
const seen = /* @__PURE__ */ new Map();
|
|
867
|
-
root.querySelectorAll("[id]").forEach((el) => {
|
|
1270
|
+
this.root.querySelectorAll("[id]").forEach((el) => {
|
|
868
1271
|
const id = el.id;
|
|
869
1272
|
if (!id) return;
|
|
870
1273
|
if (seen.has(id)) {
|
|
@@ -875,7 +1278,7 @@ var diagnosticsPass = {
|
|
|
875
1278
|
seen.set(id, el);
|
|
876
1279
|
}
|
|
877
1280
|
});
|
|
878
|
-
const anchors = root.querySelectorAll("a");
|
|
1281
|
+
const anchors = this.root.querySelectorAll("a");
|
|
879
1282
|
anchors.forEach((a) => {
|
|
880
1283
|
if (a.closest(`.${suppressClass}`)) return;
|
|
881
1284
|
const hasHref = a.hasAttribute("href") && a.getAttribute("href") !== "";
|
|
@@ -888,50 +1291,204 @@ var diagnosticsPass = {
|
|
|
888
1291
|
}
|
|
889
1292
|
return { warnings };
|
|
890
1293
|
}
|
|
1294
|
+
async run(ctx, next) {
|
|
1295
|
+
const current = ctx.outputs[this.area];
|
|
1296
|
+
const { warnings } = await this.execute(current, ctx.config);
|
|
1297
|
+
if (warnings && warnings.length) ctx.warnings.push(...warnings);
|
|
1298
|
+
await next();
|
|
1299
|
+
}
|
|
891
1300
|
};
|
|
892
1301
|
|
|
1302
|
+
// src/pipeline/passes/assertions.ts
|
|
1303
|
+
function getSpecAndVersionFromBaseUrl(baseUrl) {
|
|
1304
|
+
if (!baseUrl) return {};
|
|
1305
|
+
try {
|
|
1306
|
+
const url = new URL(baseUrl);
|
|
1307
|
+
const parts = url.pathname.replace(/\/+$/, "").split("/").filter(Boolean);
|
|
1308
|
+
const n = parts.length;
|
|
1309
|
+
if (n >= 2) {
|
|
1310
|
+
const version = parts[n - 1];
|
|
1311
|
+
const spec = parts[n - 2];
|
|
1312
|
+
return { spec, version };
|
|
1313
|
+
}
|
|
1314
|
+
} catch {
|
|
1315
|
+
}
|
|
1316
|
+
return {};
|
|
1317
|
+
}
|
|
1318
|
+
function toStandardId(prefix, major, seq) {
|
|
1319
|
+
const n = String(seq).padStart(3, "0");
|
|
1320
|
+
return `${prefix}-${major}-${n}`;
|
|
1321
|
+
}
|
|
1322
|
+
function closestBlock(el) {
|
|
1323
|
+
return el.closest("p, li, dd, dt, td, th, blockquote") || null;
|
|
1324
|
+
}
|
|
1325
|
+
function normText(el) {
|
|
1326
|
+
const text = (el.textContent || "").replace(/\s+/g, " ").trim();
|
|
1327
|
+
return text.length > 200 ? `${text.slice(0, 197)}...` : text;
|
|
1328
|
+
}
|
|
1329
|
+
function ensureUniqueId(root, desired) {
|
|
1330
|
+
let id = desired;
|
|
1331
|
+
let i = 1;
|
|
1332
|
+
const doc = root.ownerDocument;
|
|
1333
|
+
while (doc && typeof doc.getElementById === "function" && doc.getElementById(id)) {
|
|
1334
|
+
id = `${desired}-${i++}`;
|
|
1335
|
+
}
|
|
1336
|
+
return id;
|
|
1337
|
+
}
|
|
1338
|
+
var AssertionsPass = class {
|
|
1339
|
+
constructor(root) {
|
|
1340
|
+
this.root = root;
|
|
1341
|
+
this.area = "assertions";
|
|
1342
|
+
}
|
|
1343
|
+
async execute(_data, config) {
|
|
1344
|
+
const warnings = [];
|
|
1345
|
+
const { spec: specOpt, version: versionOpt } = config.postprocess?.assertions || {};
|
|
1346
|
+
const { spec: specFromPath, version: versionFromPath } = getSpecAndVersionFromBaseUrl(config.baseUrl);
|
|
1347
|
+
const spec = (specOpt || specFromPath || "SPEC").toUpperCase();
|
|
1348
|
+
const version = (versionOpt || versionFromPath || "0").toString();
|
|
1349
|
+
const majorMatch = version.match(/^(\d+)/);
|
|
1350
|
+
const major = majorMatch ? majorMatch[1] : "0";
|
|
1351
|
+
const map = /* @__PURE__ */ new Map();
|
|
1352
|
+
const markers = Array.from(this.root.querySelectorAll("em.rfc2119"));
|
|
1353
|
+
for (const em of markers) {
|
|
1354
|
+
const block = closestBlock(em);
|
|
1355
|
+
if (!block) continue;
|
|
1356
|
+
const text = (em.textContent || "").trim().toUpperCase();
|
|
1357
|
+
let type = void 0;
|
|
1358
|
+
if (text === "MUST NOT") type = "MUST NOT";
|
|
1359
|
+
else if (text === "MUST") type = "MUST";
|
|
1360
|
+
else if (text === "SHOULD") type = "SHOULD";
|
|
1361
|
+
else if (text === "MAY") type = "MAY";
|
|
1362
|
+
if (!type) continue;
|
|
1363
|
+
const entry = map.get(block) || { keywords: [], markers: [] };
|
|
1364
|
+
entry.keywords.push(type);
|
|
1365
|
+
entry.markers.push(em);
|
|
1366
|
+
map.set(block, entry);
|
|
1367
|
+
}
|
|
1368
|
+
if (!map.size) return { data: [], warnings };
|
|
1369
|
+
const selectors = "p, li, dd, dt, td, th, blockquote";
|
|
1370
|
+
const blocksInOrder = Array.from(this.root.querySelectorAll(selectors)).filter((el) => map.has(el));
|
|
1371
|
+
const items = [];
|
|
1372
|
+
let seq = 1;
|
|
1373
|
+
for (const block of blocksInOrder) {
|
|
1374
|
+
const { keywords } = map.get(block);
|
|
1375
|
+
if (keywords.length > 1) {
|
|
1376
|
+
warnings.push(
|
|
1377
|
+
`Multiple normative keywords (${keywords.join(", ")}) in block: "${normText(block)}"`
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
const type = keywords[0];
|
|
1381
|
+
const standardId = toStandardId(spec, major, seq++);
|
|
1382
|
+
let anchorId = block.id;
|
|
1383
|
+
if (!anchorId) {
|
|
1384
|
+
anchorId = ensureUniqueId(this.root, standardId);
|
|
1385
|
+
block.id = anchorId;
|
|
1386
|
+
}
|
|
1387
|
+
block.setAttribute("data-assertion-id", standardId);
|
|
1388
|
+
items.push({ id: standardId, anchorId, type, snippet: normText(block) });
|
|
1389
|
+
}
|
|
1390
|
+
return { data: items, warnings };
|
|
1391
|
+
}
|
|
1392
|
+
async run(ctx, next) {
|
|
1393
|
+
const current = ctx.outputs[this.area];
|
|
1394
|
+
const { data, warnings } = await this.execute(current, ctx.config);
|
|
1395
|
+
if (data !== void 0) ctx.outputs[this.area] = data;
|
|
1396
|
+
if (warnings && warnings.length) ctx.warnings.push(...warnings);
|
|
1397
|
+
await next();
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
// src/utils/output-areas.ts
|
|
1402
|
+
var CONFIG_TO_OUTPUT_MAP = [
|
|
1403
|
+
{
|
|
1404
|
+
fields: ["sections"],
|
|
1405
|
+
outputs: ["idl", "xref", "references", "boilerplate", "toc", "diagnostics", "assertions"]
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
fields: ["header", "sotd", "pubrules", "legal"],
|
|
1409
|
+
outputs: ["boilerplate"]
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
fields: ["lint"],
|
|
1413
|
+
outputs: ["diagnostics"]
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
fields: ["xref", "mdn"],
|
|
1417
|
+
outputs: ["xref"]
|
|
1418
|
+
}
|
|
1419
|
+
];
|
|
1420
|
+
function fieldChanged(oldVal, newVal) {
|
|
1421
|
+
if (Array.isArray(oldVal) && Array.isArray(newVal)) {
|
|
1422
|
+
if (oldVal === newVal) return false;
|
|
1423
|
+
if (oldVal.length !== newVal.length) return true;
|
|
1424
|
+
for (let i = 0; i < oldVal.length; i++) {
|
|
1425
|
+
if (oldVal[i] !== newVal[i]) return true;
|
|
1426
|
+
}
|
|
1427
|
+
return false;
|
|
1428
|
+
}
|
|
1429
|
+
return oldVal !== newVal;
|
|
1430
|
+
}
|
|
1431
|
+
function getChangedOutputAreas(oldConfig, newConfig) {
|
|
1432
|
+
if (!oldConfig) {
|
|
1433
|
+
const all = /* @__PURE__ */ new Set();
|
|
1434
|
+
for (const mapping of CONFIG_TO_OUTPUT_MAP) {
|
|
1435
|
+
for (const out of mapping.outputs) all.add(out);
|
|
1436
|
+
}
|
|
1437
|
+
return Array.from(all);
|
|
1438
|
+
}
|
|
1439
|
+
const areas = /* @__PURE__ */ new Set();
|
|
1440
|
+
for (const { fields, outputs } of CONFIG_TO_OUTPUT_MAP) {
|
|
1441
|
+
if (fields.some((f) => fieldChanged(oldConfig[f], newConfig[f]))) {
|
|
1442
|
+
outputs.forEach((o) => areas.add(o));
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return Array.from(areas);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
893
1448
|
// src/speculator.ts
|
|
894
1449
|
var Speculator = class {
|
|
895
1450
|
constructor(options = {}) {
|
|
896
1451
|
const baseUrl = options.baseUrl;
|
|
897
1452
|
const fileLoader = options.fileLoader || getDefaultFileLoader();
|
|
898
1453
|
const markdownOptions = options.markdownOptions || {};
|
|
899
|
-
this.
|
|
900
|
-
this.
|
|
901
|
-
this.
|
|
1454
|
+
this.baseConfig = { postprocess: options.postprocess || {} };
|
|
1455
|
+
this.formatRegistry = options.formatRegistry || new FormatRegistry(markdownOptions);
|
|
1456
|
+
this.formatProcessor = options.formatProcessor || new FormatProcessor(this.formatRegistry);
|
|
1457
|
+
this.includeProcessor = options.includeProcessor || new IncludeProcessor(baseUrl, fileLoader, this.formatRegistry);
|
|
902
1458
|
this.htmlRenderer = options.htmlRenderer || new DOMHtmlRenderer();
|
|
903
|
-
this.
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1459
|
+
this.documentBuilder = new DocumentBuilder(this, this.htmlRenderer);
|
|
1460
|
+
this.processors = [this.includeProcessor, this.formatProcessor];
|
|
1461
|
+
const passes = options.passes;
|
|
1462
|
+
if (Array.isArray(passes)) {
|
|
1463
|
+
this.passFactory = () => passes;
|
|
1464
|
+
} else if (typeof passes === "function") {
|
|
1465
|
+
this.passFactory = passes;
|
|
1466
|
+
} else {
|
|
1467
|
+
this.passFactory = (container) => {
|
|
1468
|
+
return [
|
|
1469
|
+
new IdlPass(container),
|
|
1470
|
+
new XrefPass(container),
|
|
1471
|
+
new ReferencesPass(container),
|
|
1472
|
+
new BoilerplatePass(container),
|
|
1473
|
+
new TocPass(container),
|
|
1474
|
+
new AssertionsPass(container),
|
|
1475
|
+
new DiagnosticsPass(container)
|
|
1476
|
+
];
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
this.pipelineRunner = new PipelineRunner(this.passFactory);
|
|
911
1480
|
}
|
|
912
1481
|
/**
|
|
913
1482
|
* Process a single DOM element
|
|
914
1483
|
*/
|
|
915
|
-
async processElement(element) {
|
|
916
|
-
|
|
917
|
-
const stats = {
|
|
918
|
-
elementsProcessed: 0,
|
|
919
|
-
filesIncluded: 0,
|
|
920
|
-
markdownBlocks: 0,
|
|
921
|
-
processingTime: 0
|
|
922
|
-
};
|
|
1484
|
+
async processElement(element, tracker = new StatsTracker()) {
|
|
1485
|
+
tracker.start();
|
|
923
1486
|
const warnings = [];
|
|
924
1487
|
const clonedElement = element.cloneNode(true);
|
|
925
1488
|
try {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
}
|
|
929
|
-
if (clonedElement.hasAttribute("data-format")) {
|
|
930
|
-
this.formatProcessor.process(clonedElement, stats, warnings);
|
|
931
|
-
}
|
|
932
|
-
stats.elementsProcessed = 1;
|
|
933
|
-
stats.processingTime = performance.now() - startTime;
|
|
934
|
-
return { element: clonedElement, warnings, stats };
|
|
1489
|
+
await this.processNode(clonedElement, tracker, warnings);
|
|
1490
|
+
tracker.stop();
|
|
1491
|
+
return { element: clonedElement, warnings, stats: tracker.toJSON() };
|
|
935
1492
|
} catch (error) {
|
|
936
1493
|
throw new SpeculatorError(
|
|
937
1494
|
`Failed to process element: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
@@ -939,71 +1496,126 @@ var Speculator = class {
|
|
|
939
1496
|
);
|
|
940
1497
|
}
|
|
941
1498
|
}
|
|
1499
|
+
async processNode(node, tracker, warnings) {
|
|
1500
|
+
let matched = false;
|
|
1501
|
+
for (const processor of this.processors) {
|
|
1502
|
+
if (!processor.matches(node)) continue;
|
|
1503
|
+
matched = true;
|
|
1504
|
+
const { content, error } = await processor.process(node, tracker, warnings);
|
|
1505
|
+
if (error) {
|
|
1506
|
+
warnings.push(error);
|
|
1507
|
+
insertContent(node, renderError(error));
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
if (content !== void 0 && content !== null) {
|
|
1511
|
+
let renderedContent = content;
|
|
1512
|
+
if (processor instanceof FormatProcessor) {
|
|
1513
|
+
const rendered = this.htmlRenderer.parse(content);
|
|
1514
|
+
renderedContent = this.htmlRenderer.serialize(rendered);
|
|
1515
|
+
}
|
|
1516
|
+
insertContent(node, renderedContent);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (matched) tracker.incrementElements();
|
|
1520
|
+
const children = Array.from(node.children);
|
|
1521
|
+
for (const child of children) {
|
|
1522
|
+
await this.processNode(child, tracker, warnings);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
942
1525
|
/**
|
|
943
|
-
* Process an entire document
|
|
1526
|
+
* Process an entire document described by a SpeculatorConfig
|
|
944
1527
|
*/
|
|
945
|
-
async renderDocument(
|
|
1528
|
+
async renderDocument(spec, configOrOutputs = {}) {
|
|
946
1529
|
const startTime = performance.now();
|
|
947
|
-
const
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
elementsProcessed: 0,
|
|
952
|
-
filesIncluded: 0,
|
|
953
|
-
markdownBlocks: 0,
|
|
954
|
-
processingTime: 0
|
|
1530
|
+
const config = {
|
|
1531
|
+
...this.baseConfig,
|
|
1532
|
+
...spec,
|
|
1533
|
+
postprocess: { ...this.baseConfig.postprocess || {}, ...spec.postprocess || {} }
|
|
955
1534
|
};
|
|
956
|
-
const
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1535
|
+
const { container, header, sotd, stats, warnings: sectionWarnings } = await this.documentBuilder.build(config);
|
|
1536
|
+
const allWarnings = [...sectionWarnings];
|
|
1537
|
+
let areas = getChangedOutputAreas(this.prevConfig, config);
|
|
1538
|
+
this.prevConfig = config;
|
|
1539
|
+
if (Array.isArray(configOrOutputs)) {
|
|
1540
|
+
areas = areas.filter((a) => configOrOutputs.includes(a));
|
|
1541
|
+
}
|
|
1542
|
+
let toc;
|
|
1543
|
+
let boilerplate;
|
|
1544
|
+
let references;
|
|
1545
|
+
let pipelineOutputs = {};
|
|
1546
|
+
try {
|
|
1547
|
+
if (areas.length) {
|
|
1548
|
+
const result = await this.pipelineRunner.run(container, config, areas);
|
|
1549
|
+
pipelineOutputs = result.outputs;
|
|
965
1550
|
allWarnings.push(...result.warnings);
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1551
|
+
if (result.outputs.toc) {
|
|
1552
|
+
toc = result.outputs.toc;
|
|
1553
|
+
}
|
|
1554
|
+
const bpOut = result.outputs.boilerplate;
|
|
1555
|
+
if (bpOut && bpOut.sections.length) {
|
|
1556
|
+
const renderer = new BoilerplateRenderer(container.ownerDocument);
|
|
1557
|
+
const nodes = renderer.render(bpOut.sections);
|
|
1558
|
+
boilerplate = nodes.map((el) => el.outerHTML);
|
|
1559
|
+
}
|
|
1560
|
+
const refOut = result.outputs.references;
|
|
1561
|
+
if (refOut && refOut.html) {
|
|
1562
|
+
refOut.citeUpdates.forEach(
|
|
1563
|
+
({ element, href }) => element.setAttribute("href", href)
|
|
1564
|
+
);
|
|
1565
|
+
references = refOut.html;
|
|
1566
|
+
}
|
|
971
1567
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
section.parentNode.replaceChild(processedElements[index], section);
|
|
1568
|
+
const hooks = config.postProcess ? Array.isArray(config.postProcess) ? config.postProcess : [config.postProcess] : [];
|
|
1569
|
+
for (const hook of hooks) {
|
|
1570
|
+
await hook(container, pipelineOutputs);
|
|
976
1571
|
}
|
|
977
|
-
});
|
|
978
|
-
try {
|
|
979
|
-
const areas = [
|
|
980
|
-
"idl",
|
|
981
|
-
"xref",
|
|
982
|
-
"references",
|
|
983
|
-
"boilerplate",
|
|
984
|
-
"toc",
|
|
985
|
-
"diagnostics"
|
|
986
|
-
];
|
|
987
|
-
const { warnings } = await this.postprocessor.run(
|
|
988
|
-
container,
|
|
989
|
-
areas,
|
|
990
|
-
this.postprocessOptions || {}
|
|
991
|
-
);
|
|
992
|
-
allWarnings.push(...warnings);
|
|
993
1572
|
} catch (e) {
|
|
994
|
-
allWarnings.push(
|
|
1573
|
+
allWarnings.push(
|
|
1574
|
+
`Postprocess failed: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
1575
|
+
);
|
|
995
1576
|
}
|
|
996
|
-
|
|
997
|
-
|
|
1577
|
+
stats.processingTime = performance.now() - startTime;
|
|
1578
|
+
const finalSections = Array.from(container.children).filter(
|
|
1579
|
+
(el) => el !== header && el !== sotd
|
|
1580
|
+
);
|
|
1581
|
+
return {
|
|
1582
|
+
sections: finalSections,
|
|
1583
|
+
warnings: allWarnings,
|
|
1584
|
+
toc,
|
|
1585
|
+
stats,
|
|
1586
|
+
...header ? { header } : {},
|
|
1587
|
+
...sotd ? { sotd } : {},
|
|
1588
|
+
...boilerplate ? { boilerplate } : {},
|
|
1589
|
+
...references ? { references } : {}
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
async renderSections(inputHtml) {
|
|
1593
|
+
const container = this.htmlRenderer.parse(inputHtml);
|
|
1594
|
+
const sections = Array.from(container.children);
|
|
1595
|
+
return this.renderDocument({ sections });
|
|
998
1596
|
}
|
|
999
1597
|
/**
|
|
1000
1598
|
* Process HTML string and return processed HTML
|
|
1001
1599
|
*/
|
|
1002
1600
|
async renderHTML(inputHtml) {
|
|
1003
|
-
const
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1601
|
+
const result = await this.renderSections(inputHtml);
|
|
1602
|
+
const container = this.htmlRenderer.parse("<div></div>");
|
|
1603
|
+
const doc = container.ownerDocument;
|
|
1604
|
+
const root = doc.createElement("div");
|
|
1605
|
+
for (const section of result.sections) {
|
|
1606
|
+
root.appendChild(section);
|
|
1607
|
+
}
|
|
1608
|
+
const htmlSections = this.htmlRenderer.serialize(root);
|
|
1609
|
+
return {
|
|
1610
|
+
sections: htmlSections,
|
|
1611
|
+
toc: result.toc,
|
|
1612
|
+
warnings: result.warnings,
|
|
1613
|
+
stats: result.stats,
|
|
1614
|
+
...result.header ? { header: this.htmlRenderer.serialize(result.header) } : {},
|
|
1615
|
+
...result.sotd ? { sotd: this.htmlRenderer.serialize(result.sotd) } : {},
|
|
1616
|
+
...result.boilerplate ? { boilerplate: result.boilerplate } : {},
|
|
1617
|
+
...result.references ? { references: result.references } : {}
|
|
1618
|
+
};
|
|
1007
1619
|
}
|
|
1008
1620
|
};
|
|
1009
1621
|
|
|
@@ -1082,14 +1694,18 @@ var Speculator2 = class extends Speculator {
|
|
|
1082
1694
|
0 && (module.exports = {
|
|
1083
1695
|
DOMHtmlRenderer,
|
|
1084
1696
|
FormatProcessor,
|
|
1697
|
+
FormatRegistry,
|
|
1085
1698
|
IncludeProcessor,
|
|
1086
1699
|
LinkedomHtmlRenderer,
|
|
1087
1700
|
RespecXrefResolver,
|
|
1088
1701
|
Speculator,
|
|
1089
1702
|
SpeculatorError,
|
|
1703
|
+
StatsTracker,
|
|
1090
1704
|
browserFileLoader,
|
|
1091
1705
|
createFallbackFileLoader,
|
|
1092
1706
|
createMarkdownRenderer,
|
|
1707
|
+
fromRespecConfig,
|
|
1708
|
+
getChangedOutputAreas,
|
|
1093
1709
|
getDefaultFileLoader,
|
|
1094
1710
|
nodeFileLoader,
|
|
1095
1711
|
parseMarkdown
|