UNPKG

13.3 kBJavaScriptView Raw
1"use strict";
2var __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};
11Object.defineProperty(exports, "__esModule", { value: true });
12const Builder_1 = require("./Builder");
13const utils_1 = require("./utils");
14/*@internal*/
15class Xref extends Builder_1.default {
16 constructor(spec, node, clause, namespace, href, aoid) {
17 super(spec, node);
18 this.namespace = namespace;
19 this.href = href;
20 this.aoid = aoid;
21 this.clause = clause;
22 this.id = node.getAttribute('id');
23 this.isInvocation = node.hasAttribute('is-invocation');
24 node.removeAttribute('is-invocation');
25 // Check if there's metadata adding or suppressing effects
26 this.addEffects = null;
27 this.suppressEffects = null;
28 if (node.parentElement && node.parentElement.tagName === 'EMU-META') {
29 if (node.parentElement.hasAttribute('effects')) {
30 const addEffects = node.parentElement.getAttribute('effects').split(',');
31 if (addEffects.length !== 0) {
32 this.addEffects = utils_1.validateEffects(spec, addEffects, node.parentElement);
33 }
34 }
35 if (node.parentElement.hasAttribute('suppress-effects')) {
36 const suppressEffects = node.parentElement.getAttribute('suppress-effects').split(',');
37 if (suppressEffects.length !== 0) {
38 this.suppressEffects = utils_1.validateEffects(spec, suppressEffects, node.parentElement);
39 }
40 }
41 if (this.addEffects !== null && this.suppressEffects !== null) {
42 for (const e of this.addEffects) {
43 if (this.suppressEffects.includes(e)) {
44 throw new Error('effect suppression is contradictory');
45 }
46 }
47 for (const e of this.suppressEffects) {
48 if (this.addEffects.includes(e)) {
49 throw new Error('effect suppression is contradictory');
50 }
51 }
52 }
53 // Strip an outer <emu-meta> if present
54 const children = node.parentElement.childNodes;
55 node.parentElement.replaceWith(...children);
56 }
57 }
58 canHaveEffect(effectName) {
59 if (!this.isInvocation)
60 return false;
61 if (this.clause && !this.clause.canHaveEffect(effectName)) {
62 return false;
63 }
64 if (this.suppressEffects !== null) {
65 return !this.suppressEffects.includes(effectName);
66 }
67 return true;
68 }
69 hasAddedEffect(effectName) {
70 if (!this.isInvocation)
71 return false;
72 if (this.addEffects !== null) {
73 return this.addEffects.includes(effectName);
74 }
75 return false;
76 }
77 static enter({ node, spec, clauseStack }) {
78 return __awaiter(this, void 0, void 0, function* () {
79 const href = node.getAttribute('href');
80 const aoid = node.getAttribute('aoid');
81 const parentClause = clauseStack[clauseStack.length - 1];
82 let namespace;
83 if (node.hasAttribute('namespace')) {
84 namespace = node.getAttribute('namespace');
85 }
86 else {
87 namespace = parentClause ? parentClause.namespace : spec.namespace;
88 }
89 if (href && aoid) {
90 spec.warn({
91 type: 'node',
92 ruleId: 'invalid-xref',
93 message: "xref can't have both href and aoid",
94 node,
95 });
96 return;
97 }
98 if (!href && !aoid) {
99 spec.warn({
100 type: 'node',
101 ruleId: 'invalid-xref',
102 message: 'xref has neither href nor aoid',
103 node,
104 });
105 return;
106 }
107 const xref = new Xref(spec, node, parentClause, namespace, href, aoid);
108 spec._xrefs.push(xref);
109 });
110 }
111 build() {
112 const spec = this.spec;
113 const href = this.href;
114 const node = this.node;
115 const aoid = this.aoid;
116 const namespace = this.namespace;
117 if (href) {
118 if (href[0] !== '#') {
119 spec.warn({
120 type: 'attr-value',
121 attr: 'href',
122 ruleId: 'invalid-xref',
123 message: `xref to anything other than a fragment id is not supported (is ${JSON.stringify(href)}). try href="#sec-id" instead`,
124 node: this.node,
125 });
126 return;
127 }
128 const id = href.slice(1);
129 this.entry = spec.biblio.byId(id);
130 if (!this.entry) {
131 spec.warn({
132 type: 'attr-value',
133 attr: 'href',
134 ruleId: 'xref-not-found',
135 message: `can't find clause, production, note or example with id ${JSON.stringify(id)}`,
136 node: this.node,
137 });
138 return;
139 }
140 switch (this.entry.type) {
141 case 'clause':
142 buildClauseLink(node, this.entry);
143 break;
144 case 'production':
145 buildProductionLink(node, this.entry);
146 break;
147 case 'example':
148 buildFigureLink(spec, this.clause, node, this.entry, 'Example');
149 break;
150 case 'note':
151 buildFigureLink(spec, this.clause, node, this.entry, 'Note');
152 break;
153 case 'table':
154 buildFigureLink(spec, this.clause, node, this.entry, 'Table');
155 break;
156 case 'figure':
157 buildFigureLink(spec, this.clause, node, this.entry, 'Figure');
158 break;
159 case 'term':
160 buildTermLink(node, this.entry);
161 break;
162 case 'step':
163 buildStepLink(spec, node, this.entry);
164 break;
165 default: {
166 spec.warn({
167 type: 'node',
168 ruleId: 'unknown-biblio',
169 message: `found unknown biblio entry ${this.entry.type} (this is a bug, please file it with ecmarkup)`,
170 node: this.node,
171 });
172 }
173 }
174 }
175 else if (aoid) {
176 this.entry = spec.biblio.byAoid(aoid, namespace);
177 if (this.entry) {
178 let effects = null;
179 let classNames = null;
180 if (this.isInvocation) {
181 effects = spec.getEffectsByAoid(aoid);
182 }
183 if (this.addEffects !== null) {
184 if (effects !== null) {
185 effects.push(...this.addEffects);
186 }
187 else {
188 effects = this.addEffects;
189 }
190 }
191 if (effects) {
192 if (this.suppressEffects !== null) {
193 effects = effects.filter(e => !this.suppressEffects.includes(e));
194 }
195 if (effects.length !== 0) {
196 const parentClause = this.clause;
197 effects = parentClause ? effects.filter(e => parentClause.canHaveEffect(e)) : effects;
198 if (effects.length !== 0) {
199 classNames = effects.map(e => `e-${e}`).join(' ');
200 }
201 }
202 }
203 buildAOLink(node, this.entry, classNames);
204 return;
205 }
206 const namespaceSuffix = namespace === '<no location>' ? '' : ` in namespace ${JSON.stringify(namespace)}`;
207 spec.warn({
208 type: 'attr-value',
209 attr: 'aoid',
210 ruleId: 'xref-not-found',
211 message: `can't find abstract op with aoid ${JSON.stringify(aoid)}` + namespaceSuffix,
212 node: this.node,
213 });
214 }
215 }
216}
217exports.default = Xref;
218Xref.elements = ['EMU-XREF'];
219function buildClauseLink(xref, entry) {
220 if (xref.textContent.trim() === '') {
221 if (xref.hasAttribute('title')) {
222 // titleHTML might not be present from older biblio files.
223 xref.innerHTML = buildXrefLink(entry, entry.titleHTML || entry.title);
224 }
225 else {
226 xref.innerHTML = buildXrefLink(entry, entry.number);
227 }
228 }
229 else {
230 xref.innerHTML = buildXrefLink(entry, xref.innerHTML);
231 }
232}
233function buildProductionLink(xref, entry) {
234 if (xref.textContent.trim() === '') {
235 xref.innerHTML = buildXrefLink(entry, '<emu-nt>' + entry.name + '</emu-nt>');
236 }
237 else {
238 xref.innerHTML = buildXrefLink(entry, xref.innerHTML);
239 }
240}
241function buildAOLink(xref, entry, classNames) {
242 if (xref.textContent.trim() === '') {
243 xref.innerHTML = buildXrefLink(entry, xref.getAttribute('aoid'), classNames);
244 }
245 else {
246 xref.innerHTML = buildXrefLink(entry, xref.innerHTML, classNames);
247 }
248}
249function buildTermLink(xref, entry) {
250 if (xref.textContent.trim() === '') {
251 xref.innerHTML = buildXrefLink(entry, entry.term);
252 }
253 else {
254 xref.innerHTML = buildXrefLink(entry, xref.innerHTML);
255 }
256}
257function buildFigureLink(spec, parentClause, xref, entry, type) {
258 if (xref.textContent.trim() === '') {
259 if (entry.clauseId) {
260 // first need to find the associated clause
261 const clauseEntry = spec.biblio.byId(entry.clauseId);
262 if (clauseEntry.type !== 'clause') {
263 spec.warn({
264 type: 'node',
265 ruleId: 'invalid-xref',
266 message: `could not find parent clause for ${type} id ${entry.id}`,
267 node: entry.node,
268 });
269 return;
270 }
271 if (parentClause && parentClause.id === clauseEntry.id) {
272 xref.innerHTML = buildXrefLink(entry, type + ' ' + entry.number);
273 }
274 else {
275 if (xref.hasAttribute('title')) {
276 xref.innerHTML = buildXrefLink(entry, clauseEntry.title + ' ' + type + ' ' + entry.number);
277 }
278 else {
279 xref.innerHTML = buildXrefLink(entry, clauseEntry.number + ' ' + type + ' ' + entry.number);
280 }
281 }
282 }
283 else {
284 xref.innerHTML = buildXrefLink(entry, type + ' ' + entry.number);
285 }
286 }
287 else {
288 xref.innerHTML = buildXrefLink(entry, xref.innerHTML);
289 }
290}
291const decimalBullet = Array.from({ length: 100 }).map((a, i) => '' + (i + 1));
292const alphaBullet = Array.from({ length: 26 }).map((a, i) => String.fromCharCode('a'.charCodeAt(0) + i));
293// prettier-ignore
294const romanBullet = ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix', 'x', 'xi', 'xii', 'xiii', 'xiv', 'xv', 'xvi', 'xvii', 'xviii', 'xix', 'xx', 'xxi', 'xxii', 'xxiii', 'xxiv', 'xxv'];
295const bullets = [decimalBullet, alphaBullet, romanBullet, decimalBullet, alphaBullet, romanBullet];
296function buildStepLink(spec, xref, entry) {
297 if (xref.innerHTML !== '') {
298 spec.warn({
299 type: 'contents',
300 ruleId: 'step-xref-contents',
301 message: 'the contents of emu-xrefs to steps are ignored',
302 node: xref,
303 nodeRelativeLine: 1,
304 nodeRelativeColumn: 1,
305 });
306 }
307 const stepBullets = entry.stepNumbers.map((s, i) => {
308 const applicable = bullets[Math.min(i, 5)];
309 if (s > applicable.length) {
310 spec.warn({
311 type: 'attr-value',
312 ruleId: 'high-step-number',
313 message: `ecmarkup does not know how to deal with step numbers as high as ${s}; if you need this, open an issue on ecmarkup`,
314 node: xref,
315 attr: 'href',
316 });
317 return '?';
318 }
319 return applicable[s - 1];
320 });
321 const text = stepBullets.join('.');
322 xref.innerHTML = buildXrefLink(entry, text);
323}
324function buildXrefLink(entry, contents, classNames = null) {
325 const classSnippet = classNames == null ? '' : ' class="' + classNames + '"';
326 return `<a href="${entry.location}#${entry.id || entry.refId}"${classSnippet}>${contents}</a>`;
327}