1 | "use strict";
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4 | return new (P || (P = Promise))(function (resolve, reject) {
|
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9 | });
|
10 | };
|
11 | Object.defineProperty(exports, "__esModule", { value: true });
|
12 | const path = require("path");
|
13 | const fs = require("fs");
|
14 | const crypto = require("crypto");
|
15 | const yaml = require("js-yaml");
|
16 | const utils = require("./utils");
|
17 | const hljs = require("highlight.js");
|
18 |
|
19 | const Import_1 = require("./Import");
|
20 | const Clause_1 = require("./Clause");
|
21 | const clauseNums_1 = require("./clauseNums");
|
22 | const Algorithm_1 = require("./Algorithm");
|
23 | const Dfn_1 = require("./Dfn");
|
24 | const Example_1 = require("./Example");
|
25 | const Figure_1 = require("./Figure");
|
26 | const Note_1 = require("./Note");
|
27 | const Toc_1 = require("./Toc");
|
28 | const Menu_1 = require("./Menu");
|
29 | const Production_1 = require("./Production");
|
30 | const NonTerminal_1 = require("./NonTerminal");
|
31 | const ProdRef_1 = require("./ProdRef");
|
32 | const Grammar_1 = require("./Grammar");
|
33 | const Xref_1 = require("./Xref");
|
34 | const Eqn_1 = require("./Eqn");
|
35 | const Biblio_1 = require("./Biblio");
|
36 | const autolinker_1 = require("./autolinker");
|
37 | const lint_1 = require("./lint/lint");
|
38 | const prex_1 = require("prex");
|
39 | const DRAFT_DATE_FORMAT = {
|
40 | year: 'numeric',
|
41 | month: 'long',
|
42 | day: 'numeric',
|
43 | timeZone: 'UTC',
|
44 | };
|
45 | const STANDARD_DATE_FORMAT = {
|
46 | year: 'numeric',
|
47 | month: 'long',
|
48 | timeZone: 'UTC',
|
49 | };
|
50 | const NO_EMD = new Set(['PRE', 'CODE', 'EMU-PRODUCTION', 'EMU-ALG', 'EMU-GRAMMAR', 'EMU-EQN']);
|
51 | const YES_EMD = new Set(['EMU-GMOD']);
|
52 | const builders = [
|
53 | Clause_1.default,
|
54 | Algorithm_1.default,
|
55 | Xref_1.default,
|
56 | Dfn_1.default,
|
57 | Eqn_1.default,
|
58 | Grammar_1.default,
|
59 | Production_1.default,
|
60 | Example_1.default,
|
61 | Figure_1.default,
|
62 | NonTerminal_1.default,
|
63 | ProdRef_1.default,
|
64 | Note_1.default,
|
65 | ];
|
66 | const visitorMap = builders.reduce((map, T) => {
|
67 | T.elements.forEach(e => (map[e] = T));
|
68 | return map;
|
69 | }, {});
|
70 | function wrapWarn(source, spec, warn) {
|
71 | return (e) => {
|
72 | const { message, ruleId } = e;
|
73 | let line;
|
74 | let column;
|
75 | let nodeType;
|
76 | let file = undefined;
|
77 | switch (e.type) {
|
78 | case 'global':
|
79 | line = undefined;
|
80 | column = undefined;
|
81 | nodeType = 'html';
|
82 | break;
|
83 | case 'raw':
|
84 | ({ line, column } = e);
|
85 | nodeType = 'html';
|
86 | if (e.file != null) {
|
87 | file = e.file;
|
88 | source = e.source;
|
89 | }
|
90 | break;
|
91 | case 'node':
|
92 | if (e.node.nodeType === 3 ) {
|
93 | const loc = spec.locate(e.node);
|
94 | if (loc) {
|
95 | file = loc.file;
|
96 | source = loc.source;
|
97 | ({ line, col: column } = loc);
|
98 | }
|
99 | nodeType = 'text';
|
100 | }
|
101 | else {
|
102 | const loc = spec.locate(e.node);
|
103 | if (loc) {
|
104 | file = loc.file;
|
105 | source = loc.source;
|
106 | ({ line, col: column } = loc.startTag);
|
107 | }
|
108 | nodeType = e.node.tagName.toLowerCase();
|
109 | }
|
110 | break;
|
111 | case 'attr': {
|
112 | const loc = spec.locate(e.node);
|
113 | if (loc) {
|
114 | file = loc.file;
|
115 | source = loc.source;
|
116 | ({ line, column } = utils.attrLocation(source, loc, e.attr));
|
117 | }
|
118 | nodeType = e.node.tagName.toLowerCase();
|
119 | break;
|
120 | }
|
121 | case 'attr-value': {
|
122 | const loc = spec.locate(e.node);
|
123 | if (loc) {
|
124 | file = loc.file;
|
125 | source = loc.source;
|
126 | ({ line, column } = utils.attrValueLocation(source, loc, e.attr));
|
127 | }
|
128 | nodeType = e.node.tagName.toLowerCase();
|
129 | break;
|
130 | }
|
131 | case 'contents': {
|
132 | const { nodeRelativeLine, nodeRelativeColumn } = e;
|
133 | if (e.node.nodeType === 3 ) {
|
134 |
|
135 | const loc = spec.locate(e.node);
|
136 | if (loc) {
|
137 | file = loc.file;
|
138 | source = loc.source;
|
139 | line = loc.line + nodeRelativeLine - 1;
|
140 | column = nodeRelativeLine === 1 ? loc.col + nodeRelativeColumn - 1 : nodeRelativeColumn;
|
141 | }
|
142 | nodeType = 'text';
|
143 | }
|
144 | else {
|
145 | const loc = spec.locate(e.node);
|
146 | if (loc) {
|
147 | file = loc.file;
|
148 | source = loc.source;
|
149 |
|
150 |
|
151 | const tagSrc = source.slice(loc.startTag.startOffset, loc.startTag.endOffset);
|
152 | const tagEnd = utils.offsetToLineAndColumn(tagSrc, loc.startTag.endOffset - loc.startOffset);
|
153 | line = loc.startTag.line + tagEnd.line + nodeRelativeLine - 2;
|
154 | if (nodeRelativeLine === 1) {
|
155 | if (tagEnd.line === 1) {
|
156 | column = loc.startTag.col + tagEnd.column + nodeRelativeColumn - 2;
|
157 | }
|
158 | else {
|
159 | column = tagEnd.column + nodeRelativeColumn - 1;
|
160 | }
|
161 | }
|
162 | else {
|
163 | column = nodeRelativeColumn;
|
164 | }
|
165 | }
|
166 | nodeType = e.node.tagName.toLowerCase();
|
167 | }
|
168 | break;
|
169 | }
|
170 | }
|
171 | warn({
|
172 | message,
|
173 | ruleId,
|
174 |
|
175 | source: e.type === 'global' ? undefined : source,
|
176 | file,
|
177 | nodeType,
|
178 | line,
|
179 | column,
|
180 | });
|
181 | };
|
182 | }
|
183 | function isEmuImportElement(node) {
|
184 | return node.nodeType === 1 && node.nodeName === 'EMU-IMPORT';
|
185 | }
|
186 | function maybeAddClauseToEffectWorklist(effectName, clause, worklist) {
|
187 | if (!worklist.includes(clause) &&
|
188 | clause.canHaveEffect(effectName) &&
|
189 | !clause.effects.includes(effectName)) {
|
190 | clause.effects.push(effectName);
|
191 | worklist.push(clause);
|
192 | }
|
193 | }
|
194 |
|
195 | class Spec {
|
196 | constructor(rootPath, fetch, dom, opts, sourceText, token = prex_1.CancellationToken.none) {
|
197 | var _a;
|
198 | opts = opts || {};
|
199 | this.spec = this;
|
200 | this.opts = {};
|
201 | this.rootPath = rootPath;
|
202 | this.rootDir = path.dirname(this.rootPath);
|
203 | this.sourceText = sourceText;
|
204 | this.doc = dom.window.document;
|
205 | this.dom = dom;
|
206 | this._fetch = fetch;
|
207 | this.subclauses = [];
|
208 | this.imports = [];
|
209 | this.node = this.doc.body;
|
210 | this.nodeIds = new Set();
|
211 | this.replacementAlgorithmToContainedLabeledStepEntries = new Map();
|
212 | this.labeledStepsToBeRectified = new Set();
|
213 | this.replacementAlgorithms = [];
|
214 | this.cancellationToken = token;
|
215 | this.generatedFiles = new Map();
|
216 | this.log = (_a = opts.log) !== null && _a !== void 0 ? _a : (() => { });
|
217 | this.warn = opts.warn ? wrapWarn(sourceText, this, opts.warn) : () => { };
|
218 | this._figureCounts = {
|
219 | table: 0,
|
220 | figure: 0,
|
221 | };
|
222 | this._xrefs = [];
|
223 | this._ntRefs = [];
|
224 | this._ntStringRefs = [];
|
225 | this._prodRefs = [];
|
226 | this._textNodes = {};
|
227 | this._effectWorklist = new Map();
|
228 | this._effectfulAOs = new Map();
|
229 | this.refsByClause = Object.create(null);
|
230 | this.processMetadata();
|
231 | Object.assign(this.opts, opts);
|
232 | if (this.opts.multipage) {
|
233 | if (this.opts.jsOut || this.opts.cssOut) {
|
234 | throw new Error('Cannot use --multipage with --js-out or --css-out');
|
235 | }
|
236 | if (this.opts.outfile == null) {
|
237 | this.opts.outfile = '';
|
238 | }
|
239 | if (this.opts.assets !== 'none') {
|
240 | this.opts.jsOut = path.join(this.opts.outfile, 'ecmarkup.js');
|
241 | this.opts.cssOut = path.join(this.opts.outfile, 'ecmarkup.css');
|
242 | }
|
243 | }
|
244 | if (typeof this.opts.status === 'undefined') {
|
245 | this.opts.status = 'proposal';
|
246 | }
|
247 | if (typeof this.opts.toc === 'undefined') {
|
248 | this.opts.toc = true;
|
249 | }
|
250 | if (typeof this.opts.copyright === 'undefined') {
|
251 | this.opts.copyright = true;
|
252 | }
|
253 | if (typeof this.opts.ecma262Biblio === 'undefined') {
|
254 | this.opts.ecma262Biblio = true;
|
255 | }
|
256 | if (!this.opts.date) {
|
257 | this.opts.date = new Date();
|
258 | }
|
259 | if (this.opts.stage != undefined) {
|
260 | this.opts.stage = String(this.opts.stage);
|
261 | }
|
262 | if (!this.opts.location) {
|
263 | this.opts.location = '<no location>';
|
264 | }
|
265 | this.namespace = this.opts.location;
|
266 | this.biblio = new Biblio_1.default(this.opts.location);
|
267 | }
|
268 | fetch(file) {
|
269 | return this._fetch(file, this.cancellationToken);
|
270 | }
|
271 | build() {
|
272 | var _a;
|
273 | return __awaiter(this, void 0, void 0, function* () {
|
274 | |
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | this.log('Loading biblios...');
|
291 | if (this.opts.ecma262Biblio) {
|
292 | yield this.loadECMA262Biblio();
|
293 | }
|
294 | yield this.loadBiblios();
|
295 | this.log('Loading imports...');
|
296 | yield this.loadImports();
|
297 | this.log('Building boilerplate...');
|
298 | this.buildBoilerplate();
|
299 | const context = {
|
300 | spec: this,
|
301 | node: this.doc.body,
|
302 | importStack: [],
|
303 | clauseStack: [],
|
304 | tagStack: [],
|
305 | clauseNumberer: clauseNums_1.default(),
|
306 | inNoAutolink: false,
|
307 | inAlg: false,
|
308 | inNoEmd: false,
|
309 | followingEmd: null,
|
310 | currentId: null,
|
311 | };
|
312 | const document = this.doc;
|
313 | if (this.opts.lintSpec) {
|
314 | this.log('Linting...');
|
315 | const source = this.sourceText;
|
316 | if (source === undefined) {
|
317 | throw new Error('Cannot lint when source text is not available');
|
318 | }
|
319 | yield lint_1.lint(this.warn, source, this, document);
|
320 | }
|
321 | this.log('Walking document, building various elements...');
|
322 | const walker = document.createTreeWalker(document.body, 1 | 4 );
|
323 | yield walk(walker, context);
|
324 | const sdoJs = this.generateSDOMap();
|
325 | this.setReplacementAlgorithmOffsets();
|
326 | this.autolink();
|
327 | this.log('Propagating effects annotations...');
|
328 | this.propagateEffects();
|
329 | this.log('Linking xrefs...');
|
330 | this._xrefs.forEach(xref => xref.build());
|
331 | this.log('Linking non-terminal references...');
|
332 | this._ntRefs.forEach(nt => nt.build());
|
333 | if (this.opts.lintSpec) {
|
334 | this._ntStringRefs.forEach(({ name, loc, node, namespace }) => {
|
335 | if (this.biblio.byProductionName(name, namespace) == null) {
|
336 | this.warn({
|
337 | type: 'contents',
|
338 | ruleId: 'undefined-nonterminal',
|
339 | message: `could not find a definition for nonterminal ${name}`,
|
340 | node,
|
341 | nodeRelativeLine: loc.line,
|
342 | nodeRelativeColumn: loc.column,
|
343 | });
|
344 | }
|
345 | });
|
346 | }
|
347 | this.log('Linking production references...');
|
348 | this._prodRefs.forEach(prod => prod.build());
|
349 | this.log('Building reference graph...');
|
350 | this.buildReferenceGraph();
|
351 | this.highlightCode();
|
352 | this.setCharset();
|
353 | const wrapper = this.buildSpecWrapper();
|
354 | let tocEles = [];
|
355 | let tocJs = '';
|
356 | if (this.opts.toc) {
|
357 | this.log('Building table of contents...');
|
358 | if (this.opts.oldToc) {
|
359 | new Toc_1.default(this).build();
|
360 | }
|
361 | else {
|
362 | ({ js: tocJs, eles: tocEles } = Menu_1.default(this));
|
363 | }
|
364 | }
|
365 | for (const ele of tocEles) {
|
366 | this.doc.body.insertBefore(ele, this.doc.body.firstChild);
|
367 | }
|
368 | const jsContents = (yield concatJs(sdoJs, tocJs)) + `\n;let usesMultipage = ${!!this.opts.multipage}`;
|
369 | const jsSha = sha(jsContents);
|
370 | if (this.opts.multipage) {
|
371 | yield this.buildMultipage(wrapper, tocEles, jsSha);
|
372 | }
|
373 | yield this.buildAssets(jsContents, jsSha);
|
374 | const file = this.opts.multipage
|
375 | ? path.join(this.opts.outfile, 'index.html')
|
376 | : (_a = this.opts.outfile) !== null && _a !== void 0 ? _a : null;
|
377 | this.generatedFiles.set(file, this.toHTML());
|
378 | return this;
|
379 | });
|
380 | }
|
381 | toHTML() {
|
382 | const htmlEle = this.doc.documentElement;
|
383 | return '<!doctype html>\n' + (htmlEle.hasAttributes() ? htmlEle.outerHTML : htmlEle.innerHTML);
|
384 | }
|
385 | locate(node) {
|
386 | let pointer = node;
|
387 | while (pointer != null) {
|
388 | if (isEmuImportElement(pointer)) {
|
389 | break;
|
390 | }
|
391 | pointer = pointer.parentElement;
|
392 | }
|
393 | if (pointer == null) {
|
394 | const loc = this.dom.nodeLocation(node);
|
395 | if (loc) {
|
396 |
|
397 | return {
|
398 | source: this.sourceText,
|
399 | startTag: loc.startTag,
|
400 | endTag: loc.endTag,
|
401 | startOffset: loc.startOffset,
|
402 | endOffset: loc.endOffset,
|
403 | attrs: loc.attrs,
|
404 | line: loc.line,
|
405 | col: loc.col,
|
406 | };
|
407 | }
|
408 | }
|
409 | else if (pointer.dom) {
|
410 | const loc = pointer.dom.nodeLocation(node);
|
411 | if (loc) {
|
412 | return {
|
413 | file: pointer.importPath,
|
414 | source: pointer.source,
|
415 | startTag: loc.startTag,
|
416 | endTag: loc.endTag,
|
417 | startOffset: loc.startOffset,
|
418 | endOffset: loc.endOffset,
|
419 | attrs: loc.attrs,
|
420 | line: loc.line,
|
421 | col: loc.col,
|
422 | };
|
423 | }
|
424 | }
|
425 | }
|
426 | buildReferenceGraph() {
|
427 | const refToClause = this.refsByClause;
|
428 | const setParent = (node) => {
|
429 | let pointer = node;
|
430 | while (pointer && !['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX'].includes(pointer.nodeName)) {
|
431 | pointer = pointer.parentNode;
|
432 | }
|
433 |
|
434 | if (pointer == null || pointer.id == null) {
|
435 |
|
436 | pointer = { id: 'sec-intro' };
|
437 | }
|
438 |
|
439 | if (refToClause[pointer.id] == null) {
|
440 |
|
441 | refToClause[pointer.id] = [];
|
442 | }
|
443 |
|
444 | refToClause[pointer.id].push(node.id);
|
445 | };
|
446 | let counter = 0;
|
447 | this._xrefs.forEach(xref => {
|
448 | let entry = xref.entry;
|
449 | if (!entry || entry.namespace === 'global')
|
450 | return;
|
451 | if (!entry.id && entry.refId) {
|
452 | entry = this.spec.biblio.byId(entry.refId);
|
453 | }
|
454 | if (!xref.id) {
|
455 | const id = `_ref_${counter++}`;
|
456 | xref.node.setAttribute('id', id);
|
457 | xref.id = id;
|
458 | }
|
459 | setParent(xref.node);
|
460 | entry.referencingIds.push(xref.id);
|
461 | });
|
462 | this._ntRefs.forEach(prod => {
|
463 | const entry = prod.entry;
|
464 | if (!entry || entry.namespace === 'global')
|
465 | return;
|
466 |
|
467 | if (prod.node.parentNode.nodeName === 'EMU-PRODUCTION')
|
468 | return;
|
469 | const id = `_ref_${counter++}`;
|
470 | prod.node.setAttribute('id', id);
|
471 | setParent(prod.node);
|
472 | entry.referencingIds.push(id);
|
473 | });
|
474 | }
|
475 | checkValidSectionId(ele) {
|
476 | if (!ele.id.startsWith('sec-')) {
|
477 | this.warn({
|
478 | type: 'node',
|
479 | ruleId: 'top-level-section-id',
|
480 | message: 'When using --multipage, top-level sections must have ids beginning with `sec-`',
|
481 | node: ele,
|
482 | });
|
483 | return false;
|
484 | }
|
485 | if (!/^[A-Za-z0-9-_]+$/.test(ele.id)) {
|
486 | this.warn({
|
487 | type: 'node',
|
488 | ruleId: 'top-level-section-id',
|
489 | message: 'When using --multipage, top-level sections must have ids matching /^[A-Za-z0-9-_]+$/',
|
490 | node: ele,
|
491 | });
|
492 | return false;
|
493 | }
|
494 | if (ele.id.toLowerCase() === 'sec-index') {
|
495 | this.warn({
|
496 | type: 'node',
|
497 | ruleId: 'top-level-section-id',
|
498 | message: 'When using --multipage, top-level sections must not be named "index"',
|
499 | node: ele,
|
500 | });
|
501 | return false;
|
502 | }
|
503 | return true;
|
504 | }
|
505 | propagateEffects() {
|
506 | for (const [effectName, worklist] of this._effectWorklist) {
|
507 | this.propagateEffect(effectName, worklist);
|
508 | }
|
509 | }
|
510 | propagateEffect(effectName, worklist) {
|
511 | const usersOfAoid = new Map();
|
512 | for (const xref of this._xrefs) {
|
513 | if (xref.clause == null || xref.aoid == null)
|
514 | continue;
|
515 | if (!xref.canHaveEffect(effectName))
|
516 | continue;
|
517 | if (xref.hasAddedEffect(effectName)) {
|
518 | maybeAddClauseToEffectWorklist(effectName, xref.clause, worklist);
|
519 | }
|
520 | const usedAoid = xref.aoid;
|
521 | if (!usersOfAoid.has(usedAoid)) {
|
522 | usersOfAoid.set(usedAoid, new Set());
|
523 | }
|
524 | usersOfAoid.get(usedAoid).add(xref.clause);
|
525 | }
|
526 | while (worklist.length !== 0) {
|
527 | const clause = worklist.shift();
|
528 | const aoid = clause.aoid;
|
529 | if (aoid == null || !usersOfAoid.has(aoid)) {
|
530 | continue;
|
531 | }
|
532 | this._effectfulAOs.set(aoid, clause);
|
533 | for (const userClause of usersOfAoid.get(aoid)) {
|
534 | maybeAddClauseToEffectWorklist(effectName, userClause, worklist);
|
535 | }
|
536 | }
|
537 | }
|
538 | getEffectsByAoid(aoid) {
|
539 | if (this._effectfulAOs.has(aoid)) {
|
540 | return this._effectfulAOs.get(aoid).effects;
|
541 | }
|
542 | return null;
|
543 | }
|
544 | buildMultipage(wrapper, tocEles, jsSha) {
|
545 | return __awaiter(this, void 0, void 0, function* () {
|
546 | let stillIntro = true;
|
547 | const introEles = [];
|
548 | const sections = [];
|
549 | const containedIdToSection = new Map();
|
550 | const sectionToContainedIds = new Map();
|
551 | const clauseTypes = ['EMU-ANNEX', 'EMU-CLAUSE'];
|
552 |
|
553 | for (const child of wrapper.children) {
|
554 | if (stillIntro) {
|
555 | if (clauseTypes.includes(child.nodeName)) {
|
556 | throw new Error('cannot make multipage build without intro');
|
557 | }
|
558 | else if (child.nodeName === 'EMU-INTRO') {
|
559 | stillIntro = false;
|
560 | if (child.id == null) {
|
561 | this.warn({
|
562 | type: 'node',
|
563 | ruleId: 'top-level-section-id',
|
564 | message: 'When using --multipage, top-level sections must have ids',
|
565 | node: child,
|
566 | });
|
567 | continue;
|
568 | }
|
569 | if (child.id !== 'sec-intro') {
|
570 | this.warn({
|
571 | type: 'node',
|
572 | ruleId: 'top-level-section-id',
|
573 | message: 'When using --multipage, the introduction must have id "sec-intro"',
|
574 | node: child,
|
575 | });
|
576 | continue;
|
577 | }
|
578 | const name = 'index';
|
579 | introEles.push(child);
|
580 | sections.push({ name, eles: introEles });
|
581 | const contained = [];
|
582 | sectionToContainedIds.set(name, contained);
|
583 | for (const item of introEles) {
|
584 | if (item.id) {
|
585 | contained.push(item.id);
|
586 | containedIdToSection.set(item.id, name);
|
587 | }
|
588 | }
|
589 |
|
590 | for (const item of [...introEles].flatMap(e => [...e.querySelectorAll('[id]')])) {
|
591 | contained.push(item.id);
|
592 | containedIdToSection.set(item.id, name);
|
593 | }
|
594 | }
|
595 | else {
|
596 | introEles.push(child);
|
597 | }
|
598 | }
|
599 | else {
|
600 | if (!clauseTypes.includes(child.nodeName)) {
|
601 | throw new Error('non-clause children are not yet implemented: ' + child.nodeName);
|
602 | }
|
603 | if (child.id == null) {
|
604 | this.warn({
|
605 | type: 'node',
|
606 | ruleId: 'top-level-section-id',
|
607 | message: 'When using --multipage, top-level sections must have ids',
|
608 | node: child,
|
609 | });
|
610 | continue;
|
611 | }
|
612 | if (!this.checkValidSectionId(child)) {
|
613 | continue;
|
614 | }
|
615 | const name = child.id.substring(4);
|
616 | const contained = [];
|
617 | sectionToContainedIds.set(name, contained);
|
618 | contained.push(child.id);
|
619 | containedIdToSection.set(child.id, name);
|
620 | for (const item of child.querySelectorAll('[id]')) {
|
621 | contained.push(item.id);
|
622 | containedIdToSection.set(item.id, name);
|
623 | }
|
624 | sections.push({ name, eles: [child] });
|
625 | }
|
626 | }
|
627 | let htmlEle = '';
|
628 | if (this.doc.documentElement.hasAttributes()) {
|
629 | const clonedHtmlEle = this.doc.documentElement.cloneNode(false);
|
630 | clonedHtmlEle.innerHTML = '';
|
631 | const src = clonedHtmlEle.outerHTML;
|
632 | htmlEle = src.substring(0, src.length - '<head></head><body></body></html>'.length);
|
633 | }
|
634 | const head = this.doc.head.cloneNode(true);
|
635 | this.addStyle(head, 'ecmarkup.css');
|
636 | this.addStyle(head, `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/${hljs.versionString}/styles/base16/solarized-light.min.css`);
|
637 | const script = this.doc.createElement('script');
|
638 | script.src = '../ecmarkup.js?cache=' + jsSha;
|
639 | script.setAttribute('defer', '');
|
640 | head.appendChild(script);
|
641 | const containedMap = JSON.stringify(Object.fromEntries(sectionToContainedIds)).replace(/[\\`$]/g, '\\$&');
|
642 | const multipageJsContents = `'use strict';
|
643 | let multipageMap = JSON.parse(\`${containedMap}\`);
|
644 | ${yield utils.readFile(path.join(__dirname, '../js/multipage.js'))}
|
645 | `;
|
646 | if (this.opts.assets !== 'none') {
|
647 | this.generatedFiles.set(path.join(this.opts.outfile, 'multipage/multipage.js'), multipageJsContents);
|
648 | }
|
649 | const multipageScript = this.doc.createElement('script');
|
650 | multipageScript.src = 'multipage.js?cache=' + sha(multipageJsContents);
|
651 | multipageScript.setAttribute('defer', '');
|
652 | head.appendChild(multipageScript);
|
653 | for (const { name, eles } of sections) {
|
654 | this.log(`Generating section ${name}...`);
|
655 | const headClone = head.cloneNode(true);
|
656 | const tocClone = tocEles.map(e => e.cloneNode(true));
|
657 | const clones = eles.map(e => e.cloneNode(true));
|
658 | const allClones = [headClone, ...tocClone, ...clones];
|
659 |
|
660 | const links = allClones.flatMap(e => [...e.querySelectorAll('a,link')]);
|
661 | for (const link of links) {
|
662 | if (linkIsAbsolute(link)) {
|
663 | continue;
|
664 | }
|
665 | if (linkIsInternal(link)) {
|
666 | let p = link.hash.substring(1);
|
667 | if (!containedIdToSection.has(p)) {
|
668 | try {
|
669 | p = decodeURIComponent(p);
|
670 | }
|
671 | catch (_a) {
|
672 |
|
673 | }
|
674 | if (!containedIdToSection.has(p)) {
|
675 | this.warn({
|
676 | type: 'node',
|
677 | ruleId: 'multipage-link-target',
|
678 | message: 'could not find appropriate section for ' + link.hash,
|
679 | node: link,
|
680 | });
|
681 | continue;
|
682 | }
|
683 | }
|
684 | const targetSec = containedIdToSection.get(p);
|
685 | link.href = (targetSec === 'index' ? './' : targetSec + '.html') + link.hash;
|
686 | }
|
687 | else if (linkIsPathRelative(link)) {
|
688 | link.href = '../' + pathFromRelativeLink(link);
|
689 | }
|
690 | }
|
691 |
|
692 | for (const img of allClones.flatMap(e => [...e.querySelectorAll('img')])) {
|
693 | if (!/^(http:|https:|:|\/)/.test(img.src)) {
|
694 | img.src = '../' + img.src;
|
695 | }
|
696 | }
|
697 |
|
698 |
|
699 | for (const object of allClones.flatMap(e => [...e.querySelectorAll('object[data]')])) {
|
700 | if (!/^(http:|https:|:|\/)/.test(object.data)) {
|
701 | object.data = '../' + object.data;
|
702 | }
|
703 | }
|
704 | if (eles[0].hasAttribute('id')) {
|
705 | const canonical = this.doc.createElement('link');
|
706 | canonical.setAttribute('rel', 'canonical');
|
707 | canonical.setAttribute('href', `../#${eles[0].id}`);
|
708 | headClone.appendChild(canonical);
|
709 | }
|
710 |
|
711 | const tocHTML = tocClone.map(e => e.outerHTML).join('\n');
|
712 |
|
713 | const clonesHTML = clones.map(e => e.outerHTML).join('\n');
|
714 | const content = `<!doctype html>${htmlEle}\n${headClone.outerHTML}\n<body>${tocHTML}<div id='spec-container'>${clonesHTML}</div></body>`;
|
715 | this.generatedFiles.set(path.join(this.opts.outfile, `multipage/${name}.html`), content);
|
716 | }
|
717 | });
|
718 | }
|
719 | buildAssets(jsContents, jsSha) {
|
720 | return __awaiter(this, void 0, void 0, function* () {
|
721 | const cssContents = yield utils.readFile(path.join(__dirname, '../css/elements.css'));
|
722 | if (this.opts.jsOut) {
|
723 | this.generatedFiles.set(this.opts.jsOut, jsContents);
|
724 | }
|
725 | if (this.opts.cssOut) {
|
726 | this.generatedFiles.set(this.opts.cssOut, cssContents);
|
727 | }
|
728 | if (this.opts.assets === 'none')
|
729 | return;
|
730 | const outDir = this.opts.outfile
|
731 | ? this.opts.multipage
|
732 | ? this.opts.outfile
|
733 | : path.dirname(this.opts.outfile)
|
734 | : process.cwd();
|
735 | if (this.opts.jsOut) {
|
736 | let skipJs = false;
|
737 | const scripts = this.doc.querySelectorAll('script');
|
738 | for (let i = 0; i < scripts.length; i++) {
|
739 | const script = scripts[i];
|
740 | const src = script.getAttribute('src');
|
741 | if (src && path.normalize(path.join(outDir, src)) === path.normalize(this.opts.jsOut)) {
|
742 | this.log(`Found existing js link to ${src}, skipping inlining...`);
|
743 | skipJs = true;
|
744 | }
|
745 | }
|
746 | if (!skipJs) {
|
747 | const script = this.doc.createElement('script');
|
748 | script.src = path.relative(outDir, this.opts.jsOut) + '?cache=' + jsSha;
|
749 | script.setAttribute('defer', '');
|
750 | this.doc.head.appendChild(script);
|
751 | }
|
752 | }
|
753 | else {
|
754 | this.log('Inlining JavaScript assets...');
|
755 | const script = this.doc.createElement('script');
|
756 | script.textContent = jsContents;
|
757 | this.doc.head.appendChild(script);
|
758 | }
|
759 | if (this.opts.cssOut) {
|
760 | let skipCss = false;
|
761 | const links = this.doc.querySelectorAll('link[rel=stylesheet]');
|
762 | for (let i = 0; i < links.length; i++) {
|
763 | const link = links[i];
|
764 | const href = link.getAttribute('href');
|
765 | if (href && path.normalize(path.join(outDir, href)) === path.normalize(this.opts.cssOut)) {
|
766 | this.log(`Found existing css link to ${href}, skipping inlining...`);
|
767 | skipCss = true;
|
768 | }
|
769 | }
|
770 | if (!skipCss) {
|
771 | this.addStyle(this.doc.head, path.relative(outDir, this.opts.cssOut));
|
772 | }
|
773 | }
|
774 | else {
|
775 | this.log('Inlining CSS assets...');
|
776 | const style = this.doc.createElement('style');
|
777 | style.textContent = cssContents;
|
778 | this.doc.head.appendChild(style);
|
779 | }
|
780 | this.addStyle(this.doc.head, `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/${hljs.versionString}/styles/base16/solarized-light.min.css`);
|
781 | });
|
782 | }
|
783 | addStyle(head, href) {
|
784 | const style = this.doc.createElement('link');
|
785 | style.setAttribute('rel', 'stylesheet');
|
786 | style.setAttribute('href', href);
|
787 |
|
788 | const firstLink = head.querySelector('link[rel=stylesheet], style');
|
789 | if (firstLink != null) {
|
790 | head.insertBefore(style, firstLink);
|
791 | }
|
792 | else {
|
793 | head.appendChild(style);
|
794 | }
|
795 | }
|
796 | buildSpecWrapper() {
|
797 | const elements = this.doc.body.childNodes;
|
798 | const wrapper = this.doc.createElement('div');
|
799 | wrapper.id = 'spec-container';
|
800 | while (elements.length > 0) {
|
801 | wrapper.appendChild(elements[0]);
|
802 | }
|
803 | this.doc.body.appendChild(wrapper);
|
804 | return wrapper;
|
805 | }
|
806 | processMetadata() {
|
807 | const block = this.doc.querySelector('pre.metadata');
|
808 | if (!block || !block.parentNode) {
|
809 | return;
|
810 | }
|
811 | let data;
|
812 | try {
|
813 | data = yaml.safeLoad(block.textContent);
|
814 | }
|
815 | catch (e) {
|
816 | if (typeof (e === null || e === void 0 ? void 0 : e.mark.line) === 'number' && typeof (e === null || e === void 0 ? void 0 : e.mark.column) === 'number') {
|
817 | this.warn({
|
818 | type: 'contents',
|
819 | ruleId: 'invalid-metadata',
|
820 | message: `metadata block failed to parse: ${e.reason}`,
|
821 | node: block,
|
822 | nodeRelativeLine: e.mark.line + 1,
|
823 | nodeRelativeColumn: e.mark.column + 1,
|
824 | });
|
825 | }
|
826 | else {
|
827 | this.warn({
|
828 | type: 'node',
|
829 | ruleId: 'invalid-metadata',
|
830 | message: 'metadata block failed to parse',
|
831 | node: block,
|
832 | });
|
833 | }
|
834 | return;
|
835 | }
|
836 | finally {
|
837 | block.parentNode.removeChild(block);
|
838 | }
|
839 | Object.assign(this.opts, data);
|
840 | }
|
841 | loadECMA262Biblio() {
|
842 | return __awaiter(this, void 0, void 0, function* () {
|
843 | this.cancellationToken.throwIfCancellationRequested();
|
844 | yield this.loadBiblio(path.join(__dirname, '../ecma262biblio.json'));
|
845 | });
|
846 | }
|
847 | loadBiblios() {
|
848 | return __awaiter(this, void 0, void 0, function* () {
|
849 | this.cancellationToken.throwIfCancellationRequested();
|
850 | yield Promise.all(Array.from(this.doc.querySelectorAll('emu-biblio'), biblio => this.loadBiblio(path.join(this.rootDir, biblio.getAttribute('href')))));
|
851 | });
|
852 | }
|
853 | loadBiblio(file) {
|
854 | return __awaiter(this, void 0, void 0, function* () {
|
855 | const contents = yield this.fetch(file);
|
856 | this.biblio.addExternalBiblio(JSON.parse(contents));
|
857 | });
|
858 | }
|
859 | loadImports() {
|
860 | return __awaiter(this, void 0, void 0, function* () {
|
861 | yield loadImports(this, this.spec.doc.body, this.rootDir);
|
862 | });
|
863 | }
|
864 | exportBiblio() {
|
865 | if (!this.opts.location) {
|
866 | this.warn({
|
867 | type: 'global',
|
868 | ruleId: 'no-location',
|
869 | message: "no spec location specified; biblio not generated. try --location or setting the location in the document's metadata block",
|
870 | });
|
871 | return {};
|
872 | }
|
873 | const biblio = {};
|
874 | biblio[this.opts.location] = this.biblio.toJSON();
|
875 | return biblio;
|
876 | }
|
877 | highlightCode() {
|
878 | this.log('Highlighting syntax...');
|
879 | const codes = this.doc.querySelectorAll('pre code');
|
880 | for (let i = 0; i < codes.length; i++) {
|
881 | const classAttr = codes[i].getAttribute('class');
|
882 | if (!classAttr)
|
883 | continue;
|
884 | const language = classAttr.replace(/lang(uage)?-/, '');
|
885 | let input = codes[i].textContent;
|
886 |
|
887 | input = input.replace(/^(\s*[\r\n])+|([\r\n]\s*)+$/g, '');
|
888 |
|
889 | const baseIndent = input.match(/^\s*/) || '';
|
890 | const baseIndentRe = new RegExp('^' + baseIndent, 'gm');
|
891 | input = input.replace(baseIndentRe, '');
|
892 |
|
893 | const result = hljs.highlight(input, { language });
|
894 | codes[i].innerHTML = result.value;
|
895 | codes[i].setAttribute('class', classAttr + ' hljs');
|
896 | }
|
897 | }
|
898 | buildBoilerplate() {
|
899 | this.cancellationToken.throwIfCancellationRequested();
|
900 | const status = this.opts.status;
|
901 | const version = this.opts.version;
|
902 | const title = this.opts.title;
|
903 | const shortname = this.opts.shortname;
|
904 | const location = this.opts.location;
|
905 | const stage = this.opts.stage;
|
906 | if (this.opts.copyright) {
|
907 | if (status !== 'draft' && status !== 'standard' && !this.opts.contributors) {
|
908 | this.warn({
|
909 | type: 'global',
|
910 | ruleId: 'no-contributors',
|
911 | message: 'contributors not specified, skipping copyright boilerplate. specify contributors in your frontmatter metadata',
|
912 | });
|
913 | }
|
914 | else {
|
915 | this.buildCopyrightBoilerplate();
|
916 | }
|
917 | }
|
918 |
|
919 | if (!title)
|
920 | return;
|
921 |
|
922 | if (title && !this._updateBySelector('title', title)) {
|
923 | const titleElem = this.doc.createElement('title');
|
924 | titleElem.innerHTML = title;
|
925 | this.doc.head.appendChild(titleElem);
|
926 | const h1 = this.doc.createElement('h1');
|
927 | h1.setAttribute('class', 'title');
|
928 | h1.innerHTML = title;
|
929 | this.doc.body.insertBefore(h1, this.doc.body.firstChild);
|
930 | }
|
931 |
|
932 | let versionText = '';
|
933 | let omitShortname = false;
|
934 | if (version) {
|
935 | versionText += version + ' / ';
|
936 | }
|
937 | else if (status === 'proposal' && stage) {
|
938 | versionText += 'Stage ' + stage + ' Draft / ';
|
939 | }
|
940 | else if (shortname && status === 'draft') {
|
941 | versionText += 'Draft ' + shortname + ' / ';
|
942 | omitShortname = true;
|
943 | }
|
944 | else {
|
945 | return;
|
946 | }
|
947 | const defaultDateFormat = status === 'standard' ? STANDARD_DATE_FORMAT : DRAFT_DATE_FORMAT;
|
948 | const date = new Intl.DateTimeFormat('en-US', defaultDateFormat).format(this.opts.date);
|
949 | versionText += date;
|
950 | if (!this._updateBySelector('h1.version', versionText)) {
|
951 | const h1 = this.doc.createElement('h1');
|
952 | h1.setAttribute('class', 'version');
|
953 | h1.innerHTML = versionText;
|
954 | this.doc.body.insertBefore(h1, this.doc.body.firstChild);
|
955 | }
|
956 |
|
957 | if (shortname && !omitShortname) {
|
958 |
|
959 | const shortnameLinkHtml = status === 'proposal' && location ? `<a href="${location}">${shortname}</a>` : shortname;
|
960 | const shortnameHtml = status.charAt(0).toUpperCase() + status.slice(1) + ' ' + shortnameLinkHtml;
|
961 | if (!this._updateBySelector('h1.shortname', shortnameHtml)) {
|
962 | const h1 = this.doc.createElement('h1');
|
963 | h1.setAttribute('class', 'shortname');
|
964 | h1.innerHTML = shortnameHtml;
|
965 | this.doc.body.insertBefore(h1, this.doc.body.firstChild);
|
966 | }
|
967 | }
|
968 | }
|
969 | buildCopyrightBoilerplate() {
|
970 | let addressFile;
|
971 | let copyrightFile;
|
972 | let licenseFile;
|
973 | if (this.opts.boilerplate) {
|
974 | if (this.opts.boilerplate.address) {
|
975 | addressFile = path.join(process.cwd(), this.opts.boilerplate.address);
|
976 | }
|
977 | if (this.opts.boilerplate.copyright) {
|
978 | copyrightFile = path.join(process.cwd(), this.opts.boilerplate.copyright);
|
979 | }
|
980 | if (this.opts.boilerplate.license) {
|
981 | licenseFile = path.join(process.cwd(), this.opts.boilerplate.license);
|
982 | }
|
983 | }
|
984 |
|
985 | let address = getBoilerplate(addressFile || 'address');
|
986 | let copyright = getBoilerplate(copyrightFile || `${this.opts.status}-copyright`);
|
987 | const license = getBoilerplate(licenseFile || 'software-license');
|
988 | if (this.opts.status === 'proposal') {
|
989 | address = '';
|
990 | }
|
991 |
|
992 | copyright = copyright.replace(/!YEAR!/g, '' + this.opts.date.getFullYear());
|
993 | if (this.opts.contributors) {
|
994 | copyright = copyright.replace(/!CONTRIBUTORS!/g, this.opts.contributors);
|
995 | }
|
996 | let copyrightClause = this.doc.querySelector('.copyright-and-software-license');
|
997 | if (!copyrightClause) {
|
998 | let last;
|
999 | utils.domWalkBackward(this.doc.body, node => {
|
1000 | if (last)
|
1001 | return false;
|
1002 | if (node.nodeName === 'EMU-CLAUSE' || node.nodeName === 'EMU-ANNEX') {
|
1003 | last = node;
|
1004 | return false;
|
1005 | }
|
1006 | });
|
1007 | copyrightClause = this.doc.createElement('emu-annex');
|
1008 | copyrightClause.setAttribute('id', 'sec-copyright-and-software-license');
|
1009 | if (last && last.parentNode) {
|
1010 | last.parentNode.insertBefore(copyrightClause, last.nextSibling);
|
1011 | }
|
1012 | else {
|
1013 | this.doc.body.appendChild(copyrightClause);
|
1014 | }
|
1015 | }
|
1016 | copyrightClause.innerHTML = `
|
1017 | <h1>Copyright & Software License</h1>
|
1018 | ${address}
|
1019 | <h2>Copyright Notice</h2>
|
1020 | ${copyright.replace('!YEAR!', '' + this.opts.date.getFullYear())}
|
1021 | <h2>Software License</h2>
|
1022 | ${license}
|
1023 | `;
|
1024 | }
|
1025 | generateSDOMap() {
|
1026 | var _a;
|
1027 | const sdoMap = Object.create(null);
|
1028 | this.log('Building SDO map...');
|
1029 | const mainGrammar = new Set(this.doc.querySelectorAll('emu-grammar[type=definition]:not([example])'));
|
1030 |
|
1031 | for (const annexEle of this.doc.querySelectorAll('emu-annex emu-grammar[type=definition]')) {
|
1032 | mainGrammar.delete(annexEle);
|
1033 | }
|
1034 | const productions = new Map();
|
1035 | for (const grammar of mainGrammar) {
|
1036 | for (const production of grammar.querySelectorAll('emu-production')) {
|
1037 | if (!production.hasAttribute('name')) {
|
1038 |
|
1039 | this.warn({
|
1040 | type: 'node',
|
1041 |
|
1042 | node: grammar,
|
1043 | ruleId: 'grammar-shape',
|
1044 | message: 'expected emu-production node to have name',
|
1045 | });
|
1046 | continue;
|
1047 | }
|
1048 | const name = production.getAttribute('name');
|
1049 | if (productions.has(name)) {
|
1050 | this.warn({
|
1051 | type: 'node',
|
1052 | node: grammar,
|
1053 | ruleId: 'grammar-shape',
|
1054 | message: `found duplicate definition for production ${name}`,
|
1055 | });
|
1056 | continue;
|
1057 | }
|
1058 | const rhses = [...production.querySelectorAll('emu-rhs')];
|
1059 | productions.set(name, rhses);
|
1060 | }
|
1061 | }
|
1062 | const sdos = this.doc.querySelectorAll('emu-clause[type=sdo],emu-clause[type="syntax-directed operation"]');
|
1063 | outer: for (const sdo of sdos) {
|
1064 | let header;
|
1065 | for (const child of sdo.children) {
|
1066 | if (child.tagName === 'SPAN' && child.childNodes.length === 0) {
|
1067 |
|
1068 | continue;
|
1069 | }
|
1070 | if (child.tagName === 'H1') {
|
1071 | header = child;
|
1072 | break;
|
1073 | }
|
1074 | this.warn({
|
1075 | type: 'node',
|
1076 | node: child,
|
1077 | ruleId: 'sdo-name',
|
1078 | message: 'expected H1 as first child of syntax-directed operation',
|
1079 | });
|
1080 | continue outer;
|
1081 | }
|
1082 | if (!header) {
|
1083 | continue;
|
1084 | }
|
1085 | const clause = header.firstElementChild.textContent;
|
1086 | const nameMatch = (_a = header.textContent) === null || _a === void 0 ? void 0 : _a.slice(clause.length + 1).match(/^(?:(?:Static|Runtime) Semantics: )?\s*(\w+)\b/);
|
1087 | if (nameMatch == null) {
|
1088 | this.warn({
|
1089 | type: 'contents',
|
1090 | node: header,
|
1091 | ruleId: 'sdo-name',
|
1092 | message: 'could not parse name of syntax-directed operation',
|
1093 | nodeRelativeLine: 1,
|
1094 | nodeRelativeColumn: 1,
|
1095 | });
|
1096 | continue;
|
1097 | }
|
1098 | const sdoName = nameMatch[1];
|
1099 | for (const grammar of sdo.children) {
|
1100 | if (grammar.tagName !== 'EMU-GRAMMAR') {
|
1101 | continue;
|
1102 | }
|
1103 | for (const production of grammar.querySelectorAll('emu-production')) {
|
1104 | if (!production.hasAttribute('name')) {
|
1105 |
|
1106 | this.warn({
|
1107 | type: 'node',
|
1108 | node: grammar,
|
1109 | ruleId: 'grammar-shape',
|
1110 | message: 'expected emu-production node to have name',
|
1111 | });
|
1112 | continue;
|
1113 | }
|
1114 | const name = production.getAttribute('name');
|
1115 | if (!productions.has(name)) {
|
1116 | this.warn({
|
1117 | type: 'node',
|
1118 | node: grammar,
|
1119 | ruleId: 'grammar-shape',
|
1120 | message: `could not find definition corresponding to production ${name}`,
|
1121 | });
|
1122 | continue;
|
1123 | }
|
1124 | const mainRhses = productions.get(name);
|
1125 | for (const rhs of production.querySelectorAll('emu-rhs')) {
|
1126 | const matches = mainRhses.filter(r => rhsMatches(rhs, r));
|
1127 | if (matches.length === 0) {
|
1128 | this.warn({
|
1129 | type: 'node',
|
1130 | node: grammar,
|
1131 | ruleId: 'grammar-shape',
|
1132 | message: `could not find definition for rhs ${rhs.textContent}`,
|
1133 | });
|
1134 | continue;
|
1135 | }
|
1136 | if (matches.length > 1) {
|
1137 | this.warn({
|
1138 | type: 'node',
|
1139 | node: grammar,
|
1140 | ruleId: 'grammar-shape',
|
1141 | message: `found multiple definitions for rhs ${rhs.textContent}`,
|
1142 | });
|
1143 | continue;
|
1144 | }
|
1145 | const match = matches[0];
|
1146 | if (match.id == '') {
|
1147 | match.id = 'prod-' + sha(`${name} : ${match.textContent}`);
|
1148 | }
|
1149 | const mainId = match.id;
|
1150 | if (rhs.id == '') {
|
1151 | rhs.id = 'prod-' + sha(`[${sdoName}] ${name} ${rhs.textContent}`);
|
1152 | }
|
1153 | if (!{}.hasOwnProperty.call(sdoMap, mainId)) {
|
1154 | sdoMap[mainId] = Object.create(null);
|
1155 | }
|
1156 | const sdosForThisId = sdoMap[mainId];
|
1157 | if (!{}.hasOwnProperty.call(sdosForThisId, sdoName)) {
|
1158 | sdosForThisId[sdoName] = { clause, ids: [] };
|
1159 | }
|
1160 | else if (sdosForThisId[sdoName].clause !== clause) {
|
1161 | this.warn({
|
1162 | type: 'node',
|
1163 | node: grammar,
|
1164 | ruleId: 'grammar-shape',
|
1165 | message: `SDO ${sdoName} found in multiple clauses`,
|
1166 | });
|
1167 | }
|
1168 | sdosForThisId[sdoName].ids.push(rhs.id);
|
1169 | }
|
1170 | }
|
1171 | }
|
1172 | }
|
1173 | const json = JSON.stringify(sdoMap);
|
1174 | return `let sdoMap = JSON.parse(\`${json.replace(/[\\`$]/g, '\\$&')}\`);`;
|
1175 | }
|
1176 | setReplacementAlgorithmOffsets() {
|
1177 | this.log('Finding offsets for replacement algorithm steps...');
|
1178 | const pending = new Map();
|
1179 | const setReplacementAlgorithmStart = (element, stepNumbers) => {
|
1180 | const rootList = element.firstElementChild;
|
1181 | rootList.start = stepNumbers[stepNumbers.length - 1];
|
1182 | if (stepNumbers.length > 1) {
|
1183 |
|
1184 | if (stepNumbers.length === 2) {
|
1185 | rootList.classList.add('nested-once');
|
1186 | }
|
1187 | else if (stepNumbers.length === 3) {
|
1188 | rootList.classList.add('nested-twice');
|
1189 | }
|
1190 | else if (stepNumbers.length === 4) {
|
1191 | rootList.classList.add('nested-thrice');
|
1192 | }
|
1193 | else if (stepNumbers.length === 5) {
|
1194 | rootList.classList.add('nested-four-times');
|
1195 | }
|
1196 | else {
|
1197 |
|
1198 | rootList.classList.add('nested-lots');
|
1199 | }
|
1200 | }
|
1201 |
|
1202 | for (const entry of this.replacementAlgorithmToContainedLabeledStepEntries.get(element)) {
|
1203 | entry.stepNumbers = [...stepNumbers, ...entry.stepNumbers.slice(1)];
|
1204 | this.labeledStepsToBeRectified.delete(entry.id);
|
1205 |
|
1206 | if (pending.has(entry.id)) {
|
1207 | const todo = pending.get(entry.id);
|
1208 | pending.delete(entry.id);
|
1209 | for (const replacementAlgorithm of todo) {
|
1210 | setReplacementAlgorithmStart(replacementAlgorithm, entry.stepNumbers);
|
1211 | }
|
1212 | }
|
1213 | }
|
1214 | };
|
1215 | for (const { element, target } of this.replacementAlgorithms) {
|
1216 | if (this.labeledStepsToBeRectified.has(target)) {
|
1217 | if (!pending.has(target)) {
|
1218 | pending.set(target, []);
|
1219 | }
|
1220 | pending.get(target).push(element);
|
1221 | }
|
1222 | else {
|
1223 |
|
1224 | const targetEntry = this.biblio.byId(target);
|
1225 | if (targetEntry == null) {
|
1226 | this.warn({
|
1227 | type: 'attr-value',
|
1228 | attr: 'replaces-step',
|
1229 | ruleId: 'invalid-replacement',
|
1230 | message: `could not find step ${JSON.stringify(target)}`,
|
1231 | node: element,
|
1232 | });
|
1233 | }
|
1234 | else if (targetEntry.type !== 'step') {
|
1235 | this.warn({
|
1236 | type: 'attr-value',
|
1237 | attr: 'replaces-step',
|
1238 | ruleId: 'invalid-replacement',
|
1239 | message: `expected algorithm to replace a step, not a ${targetEntry.type}`,
|
1240 | node: element,
|
1241 | });
|
1242 | }
|
1243 | else {
|
1244 | setReplacementAlgorithmStart(element, targetEntry.stepNumbers);
|
1245 | }
|
1246 | }
|
1247 | }
|
1248 | if (pending.size > 0) {
|
1249 |
|
1250 | this.warn({
|
1251 | type: 'global',
|
1252 | ruleId: 'invalid-replacement',
|
1253 | message: 'could not unambiguously determine replacement algorithm offsets - do you have a cycle in your replacement algorithms?',
|
1254 | });
|
1255 | }
|
1256 | }
|
1257 | autolink() {
|
1258 | this.log('Autolinking terms and abstract ops...');
|
1259 | const namespaces = Object.keys(this._textNodes);
|
1260 | for (let i = 0; i < namespaces.length; i++) {
|
1261 | const namespace = namespaces[i];
|
1262 | const { replacer, autolinkmap } = autolinker_1.replacerForNamespace(namespace, this.biblio);
|
1263 | const nodes = this._textNodes[namespace];
|
1264 | for (let j = 0; j < nodes.length; j++) {
|
1265 | const { node, clause, inAlg, currentId } = nodes[j];
|
1266 | autolinker_1.autolink(node, replacer, autolinkmap, clause, currentId, inAlg);
|
1267 | }
|
1268 | }
|
1269 | }
|
1270 | setCharset() {
|
1271 | let current = this.spec.doc.querySelector('meta[charset]');
|
1272 | if (!current) {
|
1273 | current = this.spec.doc.createElement('meta');
|
1274 | this.spec.doc.head.insertBefore(current, this.spec.doc.head.firstChild);
|
1275 | }
|
1276 | current.setAttribute('charset', 'utf-8');
|
1277 | }
|
1278 | _updateBySelector(selector, contents) {
|
1279 | const elem = this.doc.querySelector(selector);
|
1280 | if (elem && elem.textContent.trim().length > 0) {
|
1281 | return true;
|
1282 | }
|
1283 | if (elem) {
|
1284 | elem.innerHTML = contents;
|
1285 | return true;
|
1286 | }
|
1287 | return false;
|
1288 | }
|
1289 | }
|
1290 | exports.default = Spec;
|
1291 | function getBoilerplate(file) {
|
1292 | let boilerplateFile = file;
|
1293 | try {
|
1294 | if (fs.lstatSync(file).isFile()) {
|
1295 | boilerplateFile = file;
|
1296 | }
|
1297 | }
|
1298 | catch (error) {
|
1299 | boilerplateFile = path.join(__dirname, '../boilerplate', `${file}.html`);
|
1300 | }
|
1301 | return fs.readFileSync(boilerplateFile, 'utf8');
|
1302 | }
|
1303 | function loadImports(spec, rootElement, rootPath) {
|
1304 | return __awaiter(this, void 0, void 0, function* () {
|
1305 | const imports = rootElement.querySelectorAll('EMU-IMPORT');
|
1306 | for (let i = 0; i < imports.length; i++) {
|
1307 | const node = imports[i];
|
1308 | const imp = yield Import_1.default.build(spec, node, rootPath);
|
1309 | yield loadImports(spec, node, imp.relativeRoot);
|
1310 | }
|
1311 | });
|
1312 | }
|
1313 | function walk(walker, context) {
|
1314 | return __awaiter(this, void 0, void 0, function* () {
|
1315 | const previousInNoAutolink = context.inNoAutolink;
|
1316 | let previousInNoEmd = context.inNoEmd;
|
1317 | const { spec } = context;
|
1318 | context.node = walker.currentNode;
|
1319 | context.tagStack.push(context.node);
|
1320 | if (context.node === context.followingEmd) {
|
1321 | context.followingEmd = null;
|
1322 | context.inNoEmd = false;
|
1323 |
|
1324 |
|
1325 | previousInNoEmd = false;
|
1326 | }
|
1327 | if (context.node.nodeType === 3) {
|
1328 |
|
1329 | if (context.node.textContent.trim().length === 0)
|
1330 | return;
|
1331 | const clause = context.clauseStack[context.clauseStack.length - 1] || context.spec;
|
1332 | const namespace = clause ? clause.namespace : context.spec.namespace;
|
1333 | if (!context.inNoEmd) {
|
1334 |
|
1335 | context.inNoEmd = true;
|
1336 | let node = context.node;
|
1337 | while (node && !node.nextSibling) {
|
1338 | node = node.parentNode;
|
1339 | }
|
1340 | if (node) {
|
1341 |
|
1342 | context.followingEmd = node.nextSibling;
|
1343 | }
|
1344 |
|
1345 | utils.emdTextNode(context.spec, context.node, namespace);
|
1346 | }
|
1347 | if (!context.inNoAutolink) {
|
1348 |
|
1349 |
|
1350 | context.spec._textNodes[namespace] = context.spec._textNodes[namespace] || [];
|
1351 | context.spec._textNodes[namespace].push({
|
1352 | node: context.node,
|
1353 | clause,
|
1354 | inAlg: context.inAlg,
|
1355 | currentId: context.currentId,
|
1356 | });
|
1357 | }
|
1358 | return;
|
1359 | }
|
1360 |
|
1361 |
|
1362 | const oldids = context.node.getAttribute('oldids');
|
1363 | if (oldids) {
|
1364 | if (!context.node.childNodes) {
|
1365 | throw new Error('oldids found on unsupported element: ' + context.node.nodeName);
|
1366 | }
|
1367 | oldids
|
1368 | .split(/,/g)
|
1369 | .map(s => s.trim())
|
1370 | .forEach(oid => {
|
1371 | const s = spec.doc.createElement('span');
|
1372 | s.setAttribute('id', oid);
|
1373 | context.node.insertBefore(s, context.node.childNodes[0]);
|
1374 | });
|
1375 | }
|
1376 | const parentId = context.currentId;
|
1377 | if (context.node.hasAttribute('id')) {
|
1378 | context.currentId = context.node.getAttribute('id');
|
1379 | }
|
1380 | if (autolinker_1.NO_CLAUSE_AUTOLINK.has(context.node.nodeName)) {
|
1381 | context.inNoAutolink = true;
|
1382 | }
|
1383 | else if (autolinker_1.YES_CLAUSE_AUTOLINK.has(context.node.nodeName)) {
|
1384 | context.inNoAutolink = false;
|
1385 | }
|
1386 | if (NO_EMD.has(context.node.nodeName)) {
|
1387 | context.inNoEmd = true;
|
1388 | }
|
1389 | else if (YES_EMD.has(context.node.nodeName)) {
|
1390 | context.inNoEmd = false;
|
1391 | }
|
1392 | const visitor = visitorMap[context.node.nodeName];
|
1393 | if (visitor) {
|
1394 | yield visitor.enter(context);
|
1395 | }
|
1396 | const firstChild = walker.firstChild();
|
1397 | if (firstChild) {
|
1398 | while (true) {
|
1399 | yield walk(walker, context);
|
1400 | const next = walker.nextSibling();
|
1401 | if (!next)
|
1402 | break;
|
1403 | }
|
1404 | walker.parentNode();
|
1405 | context.node = walker.currentNode;
|
1406 | }
|
1407 | if (visitor)
|
1408 | visitor.exit(context);
|
1409 | context.inNoAutolink = previousInNoAutolink;
|
1410 | context.inNoEmd = previousInNoEmd;
|
1411 | context.currentId = parentId;
|
1412 | context.tagStack.pop();
|
1413 | });
|
1414 | }
|
1415 | const jsDependencies = ['sdoMap.js', 'menu.js', 'listNumbers.js'];
|
1416 | function concatJs(...extras) {
|
1417 | return __awaiter(this, void 0, void 0, function* () {
|
1418 | let dependencies = yield Promise.all(jsDependencies.map(dependency => utils.readFile(path.join(__dirname, '../js/' + dependency))));
|
1419 | dependencies = dependencies.concat(extras);
|
1420 | return dependencies.join('\n');
|
1421 | });
|
1422 | }
|
1423 |
|
1424 |
|
1425 | function rhsMatches(aRhs, bRhs) {
|
1426 | const a = aRhs.firstElementChild;
|
1427 | const b = bRhs.firstElementChild;
|
1428 | if (a == null || b == null) {
|
1429 | throw new Error('RHS must have content');
|
1430 | }
|
1431 | return symbolSpanMatches(a, b);
|
1432 | }
|
1433 | function symbolSpanMatches(a, b) {
|
1434 | if (a == null || a.textContent === '[empty]') {
|
1435 | return canBeEmpty(b);
|
1436 | }
|
1437 | if (b != null && symbolMatches(a, b)) {
|
1438 | return symbolSpanMatches(a.nextElementSibling, b.nextElementSibling);
|
1439 | }
|
1440 |
|
1441 |
|
1442 | if (b != null && canSkipSymbol(b)) {
|
1443 | return symbolSpanMatches(a, b.nextElementSibling);
|
1444 | }
|
1445 | return false;
|
1446 | }
|
1447 | function canBeEmpty(b) {
|
1448 | return b == null || (canSkipSymbol(b) && canBeEmpty(b.nextElementSibling));
|
1449 | }
|
1450 | function canSkipSymbol(a) {
|
1451 | if (a.tagName === 'EMU-NT' && a.hasAttribute('optional')) {
|
1452 | return true;
|
1453 | }
|
1454 |
|
1455 |
|
1456 | return ['EMU-CONSTRAINTS', 'EMU-GMOD', 'EMU-GANN'].includes(a.tagName);
|
1457 | }
|
1458 | function symbolMatches(a, b) {
|
1459 | const aKind = a.tagName.toLowerCase();
|
1460 | const bKind = b.tagName.toLowerCase();
|
1461 | if (aKind !== bKind) {
|
1462 | return false;
|
1463 | }
|
1464 | switch (aKind) {
|
1465 | case 'emu-t': {
|
1466 | return a.textContent === b.textContent;
|
1467 | }
|
1468 | case 'emu-nt': {
|
1469 | if (a.childNodes.length > 1 || b.childNodes.length > 1) {
|
1470 |
|
1471 | throw new Error('emu-nt nodes should not have other contents at this phase');
|
1472 | }
|
1473 | return a.textContent === b.textContent;
|
1474 | }
|
1475 | case 'emu-gmod':
|
1476 | case 'emu-gann': {
|
1477 | return a.textContent === b.textContent;
|
1478 | }
|
1479 | default: {
|
1480 | throw new Error('unimplemented: symbol matches ' + aKind);
|
1481 | }
|
1482 | }
|
1483 | }
|
1484 | function sha(str) {
|
1485 | return crypto
|
1486 | .createHash('sha256')
|
1487 | .update(str)
|
1488 | .digest('base64')
|
1489 | .slice(0, 8)
|
1490 | .replace(/\+/g, '-')
|
1491 | .replace(/\//g, '_');
|
1492 | }
|
1493 |
|
1494 |
|
1495 | function linkIsAbsolute(link) {
|
1496 | return !link.href.startsWith('about:blank') && /^[a-z]+:/.test(link.href);
|
1497 | }
|
1498 | function linkIsInternal(link) {
|
1499 | return link.href.startsWith('#') || link.href.startsWith('about:blank#');
|
1500 | }
|
1501 | function linkIsPathRelative(link) {
|
1502 | return !link.href.startsWith('/') && !link.href.startsWith('about:blank/');
|
1503 | }
|
1504 | function pathFromRelativeLink(link) {
|
1505 | return link.href.startsWith('about:blank') ? link.href.substring(11) : link.href;
|
1506 | }
|