UNPKG

6.31 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.replacerForNamespace = exports.autolink = exports.YES_CLAUSE_AUTOLINK = exports.NO_CLAUSE_AUTOLINK = void 0;
4const Xref_1 = require("./Xref");
5const utils = require("./utils");
6const escape = require('html-escape');
7exports.NO_CLAUSE_AUTOLINK = new Set([
8 'PRE',
9 'CODE',
10 'EMU-CONST',
11 'EMU-PRODUCTION',
12 'EMU-GRAMMAR',
13 'EMU-XREF',
14 'H1',
15 'H2',
16 'H3',
17 'H4',
18 'H5',
19 'H6',
20 'EMU-VAR',
21 'EMU-VAL',
22 'VAR',
23 'A',
24 'DFN',
25 'SUB',
26 'EMU-NOT-REF',
27]);
28exports.YES_CLAUSE_AUTOLINK = new Set(['EMU-GMOD']); // these are processed even if they are nested in NO_CLAUSE_AUTOLINK contexts
29function autolink(node, replacer, autolinkmap, clause, currentId, allowSameId) {
30 const spec = clause.spec;
31 const template = spec.doc.createElement('template');
32 const content = escape(node.textContent);
33 const autolinked = content.replace(replacer, (match, offset) => {
34 const entry = autolinkmap[narrowSpace(match)];
35 if (!entry) {
36 return match;
37 }
38 const entryId = entry.id || entry.refId;
39 const skipLinking = !allowSameId && currentId && entryId === currentId;
40 if (skipLinking) {
41 return match;
42 }
43 if (entry.aoid) {
44 let isInvocationAttribute = '';
45 // Matches function-style invocation with parentheses and SDO-style 'of'
46 // invocation.
47 if (content[offset + match.length] === '(' ||
48 (content[offset + match.length] === ' ' &&
49 content[offset + match.length + 1] === 'o' &&
50 content[offset + match.length + 2] === 'f')) {
51 isInvocationAttribute = ' is-invocation';
52 }
53 return `<emu-xref aoid="${entry.aoid}"${isInvocationAttribute}>${match}</emu-xref>`;
54 }
55 else {
56 return `<emu-xref href="#${entry.id || entry.refId}">${match}</emu-xref>`;
57 }
58 });
59 if (autolinked !== content) {
60 template.innerHTML = autolinked;
61 const newXrefNodes = utils.replaceTextNode(node, template.content);
62 const newXrefs = newXrefNodes.map(node => new Xref_1.default(spec, node, clause, clause.namespace, node.getAttribute('href'), node.getAttribute('aoid')));
63 spec._xrefs = spec._xrefs.concat(newXrefs);
64 }
65}
66exports.autolink = autolink;
67function replacerForNamespace(namespace, biblio) {
68 const autolinkmap = biblio.getDefinedWords(namespace);
69 const replacer = new RegExp(regexpPatternForAutolinkKeys(autolinkmap, Object.keys(autolinkmap), 0), 'g');
70 return { replacer, autolinkmap };
71}
72exports.replacerForNamespace = replacerForNamespace;
73function isCommonAbstractOp(op) {
74 return (op === 'Call' || op === 'Set' || op === 'Type' || op === 'UTC' || op === 'min' || op === 'max');
75}
76function lookAheadBeyond(key, entry) {
77 if (isCommonAbstractOp(key)) {
78 // must be followed by parentheses
79 return '\\b(?=\\()';
80 }
81 if (entry.type !== 'term' || /^\w/.test(key)) {
82 // must not be followed by `.word` or `%%` or `]]`
83 return '\\b(?!\\.\\w|%%|\\]\\])';
84 }
85 return '';
86}
87// returns a regexp string where each space can be many spaces or line breaks.
88function widenSpace(str) {
89 return str.replace(/\s+/g, '\\s+');
90}
91// replaces multiple whitespace characters with a single space
92function narrowSpace(str) {
93 return str.replace(/\s+/g, ' ');
94}
95function regexpEscape(str) {
96 return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
97}
98function regexpUnion(alternatives) {
99 var _a;
100 if (alternatives.length < 2) {
101 return `${(_a = alternatives[0]) !== null && _a !== void 0 ? _a : ''}`;
102 }
103 return `(?:${alternatives.join('|')})`;
104}
105// Search a non-empty array of string `items` for the longest common
106// substring starting at position `beginIndex`. The part of each string
107// before `beginIndex` is ignored, and is not included in the result.
108function longestCommonPrefix(items, beginIndex = 0) {
109 let endIndex = beginIndex;
110 OUTER: while (endIndex < items[0].length) {
111 const char = items[0][endIndex];
112 for (let i = 1; i < items.length; ++i) {
113 if (char !== items[i][endIndex]) {
114 break OUTER;
115 }
116 }
117 ++endIndex;
118 }
119 return items[0].slice(beginIndex, endIndex);
120}
121function regexpPatternForAutolinkKeys(autolinkmap, subsetKeys, initialCommonLength) {
122 var _a;
123 const resultAlternatives = [];
124 const wordStartAlternatives = [];
125 const groups = {};
126 for (const key of subsetKeys) {
127 const char = key[initialCommonLength];
128 const group = ((_a = groups[char]) !== null && _a !== void 0 ? _a : (groups[char] = []));
129 group.push(key);
130 }
131 const matchEmpty = '' in groups;
132 if (matchEmpty) {
133 delete groups[''];
134 }
135 const longestFirst = (a, b) => b.length - a.length;
136 for (const groupChar of Object.keys(groups).sort()) {
137 // sort by length to ensure longer keys are tested first
138 const groupItems = groups[groupChar].sort(longestFirst);
139 const prefix = longestCommonPrefix(groupItems, initialCommonLength);
140 const prefixRegex = widenSpace(regexpEscape(prefix));
141 const suffixPos = initialCommonLength + prefix.length;
142 let suffixRegex;
143 if (groupItems.length > 5) {
144 // recursively split the group into smaller chunks
145 suffixRegex = regexpPatternForAutolinkKeys(autolinkmap, groupItems, suffixPos);
146 }
147 else {
148 suffixRegex = regexpUnion(groupItems.map(k => {
149 const item = widenSpace(regexpEscape(k.slice(suffixPos)));
150 return item + lookAheadBeyond(k, autolinkmap[k]);
151 }));
152 }
153 if (initialCommonLength === 0 && /^\w/.test(prefix)) {
154 wordStartAlternatives.push(prefixRegex + suffixRegex);
155 }
156 else {
157 resultAlternatives.push(prefixRegex + suffixRegex);
158 }
159 }
160 if (matchEmpty) {
161 resultAlternatives.push('');
162 }
163 if (wordStartAlternatives.length) {
164 resultAlternatives.unshift('\\b' + regexpUnion(wordStartAlternatives));
165 }
166 return regexpUnion(resultAlternatives);
167}