rapier-markdown-kit 1.2.51 → 1.2.55
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/dist/kit/render-markdown.mjs +37 -13
- package/dist/kit/render-sanitize.mjs +4 -4
- package/dist/math/delimiters.mjs +56 -17
- package/dist/math/markdown-rules.mjs +22 -6
- package/dist/spec/md-assets.mjs +39 -80
- package/dist/spec/md-engine.mjs +650 -26
- package/dist/spec/md-marks.mjs +15 -12
- package/dist/spec/md-parser-config.mjs +175 -25
- package/package.json +1 -1
|
@@ -156,12 +156,16 @@ function initMarkdownIt() {
|
|
|
156
156
|
md.renderer.rules.softbreak = () =>
|
|
157
157
|
sourceToken('\n', '\u2060', 'rapier-source-token--softbreak');
|
|
158
158
|
md.core.ruler.before('text_join', 'rapier_escape_token', state => {
|
|
159
|
+
if (state.src.indexOf('\\') < 0) return;
|
|
159
160
|
for (let at = 0; at < state.tokens.length; at++) {
|
|
160
161
|
const token = state.tokens[at];
|
|
161
|
-
if (token.type !== 'inline' || !token.children) continue;
|
|
162
|
+
if (token.type !== 'inline' || !token.children || token.content.indexOf('\\') < 0) continue;
|
|
162
163
|
for (let next = 0; next < token.children.length; next++) {
|
|
163
164
|
const child = token.children[next];
|
|
164
|
-
if (child.type === 'text_special' && child.info === 'escape' && child.content.length === 1
|
|
165
|
+
if (child.type === 'text_special' && child.info === 'escape' && child.content.length === 1) {
|
|
166
|
+
const c = child.content.charCodeAt(0);
|
|
167
|
+
if (c !== 0x5c && c !== 0x2a && c !== 0x60 && c !== 0x5b && c !== 0x5d && c !== 0x5f) child.type = 'rapier_escape';
|
|
168
|
+
}
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
});
|
|
@@ -320,15 +324,27 @@ function initMarkdownIt() {
|
|
|
320
324
|
};
|
|
321
325
|
|
|
322
326
|
md.core.ruler.push('rapier_setext_rule', function (state) {
|
|
323
|
-
let
|
|
327
|
+
let starts = null;
|
|
324
328
|
for (let at = 0; at < state.tokens.length; at++) {
|
|
325
329
|
const token = state.tokens[at];
|
|
326
330
|
if (token.type !== 'heading_open') continue;
|
|
327
331
|
if (token.markup !== '=' && token.markup !== '-') continue;
|
|
328
332
|
if (!token.map) continue;
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
333
|
+
if (starts === null) {
|
|
334
|
+
starts = state._lineStarts;
|
|
335
|
+
if (!starts) {
|
|
336
|
+
starts = [0];
|
|
337
|
+
for (let i = state.src.indexOf('\n'); i >= 0; i = state.src.indexOf('\n', i + 1)) starts.push(i + 1);
|
|
338
|
+
state._lineStarts = starts;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const lineIdx = token.map[1] - 1;
|
|
342
|
+
if (lineIdx < 0 || lineIdx >= starts.length) continue;
|
|
343
|
+
const lineStart = starts[lineIdx];
|
|
344
|
+
const nextStart = starts[lineIdx + 1];
|
|
345
|
+
const lineEnd = nextStart !== undefined ? nextStart - 1 : state.src.length;
|
|
346
|
+
const underline = state.src.slice(lineStart, lineEnd);
|
|
347
|
+
if (/^ {0,3}(?:=+|-+)[ \t]*$/.test(underline)) {
|
|
332
348
|
token.attrSet('data-rapier-heading-rule', underline);
|
|
333
349
|
}
|
|
334
350
|
}
|
|
@@ -920,15 +936,15 @@ function _rapierInstallSemanticProbe(instance) {
|
|
|
920
936
|
return false;
|
|
921
937
|
}
|
|
922
938
|
const firstEnd = state.md.helpers.parseLinkLabel(state, labelStart, false);
|
|
923
|
-
if (firstEnd < 0) return false;
|
|
924
|
-
const firstLabel = src.slice(labelStart + 1, firstEnd);
|
|
939
|
+
if (firstEnd < 0 || firstEnd === labelStart + 1 && src.charCodeAt(firstEnd + 1) !== 0x5b && src.charCodeAt(firstEnd + 1) !== 0x28) return false;
|
|
925
940
|
const after = firstEnd + 1;
|
|
941
|
+
const afterCode = src.charCodeAt(after);
|
|
926
942
|
|
|
927
|
-
if (
|
|
943
|
+
if (afterCode === 0x5b) {
|
|
928
944
|
const secondEnd = state.md.helpers.parseLinkLabel(state, after, false);
|
|
929
945
|
if (secondEnd < 0) return false;
|
|
930
946
|
const explicitLabel = src.slice(after + 1, secondEnd);
|
|
931
|
-
const rawKey = explicitLabel.length ? explicitLabel :
|
|
947
|
+
const rawKey = explicitLabel.length ? explicitLabel : src.slice(labelStart + 1, firstEnd);
|
|
932
948
|
record({
|
|
933
949
|
kind: image ? 'image-reference-use' : 'link-reference-use', rawKey,
|
|
934
950
|
localStart: pos, localEnd: secondEnd + 1,
|
|
@@ -938,9 +954,13 @@ function _rapierInstallSemanticProbe(instance) {
|
|
|
938
954
|
return false;
|
|
939
955
|
}
|
|
940
956
|
|
|
941
|
-
if (
|
|
957
|
+
if (afterCode === 0x28) {
|
|
942
958
|
let destinationPos = after + 1;
|
|
943
|
-
while (destinationPos < max
|
|
959
|
+
while (destinationPos < max) {
|
|
960
|
+
const c = src.charCodeAt(destinationPos);
|
|
961
|
+
if (c === 0x20 || c === 0x09 || c === 0x0a) destinationPos++;
|
|
962
|
+
else break;
|
|
963
|
+
}
|
|
944
964
|
const first = src.charCodeAt(destinationPos);
|
|
945
965
|
if (first !== 0x23 && first !== 0x3c && first !== 0x5c && first !== 0x26) return false;
|
|
946
966
|
const destination = state.md.helpers.parseLinkDestination(src, destinationPos, max);
|
|
@@ -957,9 +977,12 @@ function _rapierInstallSemanticProbe(instance) {
|
|
|
957
977
|
return false;
|
|
958
978
|
}
|
|
959
979
|
|
|
980
|
+
if (!image && !state.env.references) return false;
|
|
981
|
+
const firstLabel = src.slice(labelStart + 1, firstEnd);
|
|
982
|
+
if (!firstLabel) return false;
|
|
960
983
|
const normalize = state.md.utils && state.md.utils.normalizeReference;
|
|
961
984
|
const normalized = typeof normalize === 'function' ? normalize(firstLabel) : firstLabel.trim().replace(/\s+/g, ' ').toUpperCase();
|
|
962
|
-
if (
|
|
985
|
+
if (image || (state.env.references && state.env.references[normalized])) {
|
|
963
986
|
record({
|
|
964
987
|
kind: image ? 'image-reference-use' : 'link-reference-use', rawKey: firstLabel,
|
|
965
988
|
localStart: pos, localEnd: firstEnd + 1,
|
|
@@ -971,6 +994,7 @@ function _rapierInstallSemanticProbe(instance) {
|
|
|
971
994
|
|
|
972
995
|
try {
|
|
973
996
|
instance.core.ruler.before('footnote_tail', 'rapier_footnote_definition_probe', function (state) {
|
|
997
|
+
if (!state.env?.footnotes) { state.env.__rapierFootnoteDefinitions = []; return; }
|
|
974
998
|
const out = [];
|
|
975
999
|
const tokens = state && Array.isArray(state.tokens) ? state.tokens : [];
|
|
976
1000
|
const signatureFor = values => {
|
|
@@ -121,7 +121,7 @@ function prepareMathSource(input, DOMParser) {
|
|
|
121
121
|
return sourceRoot ? root.outerHTML : root;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function createInertHtmlSanitizer({parseFragment, serialize, sanitizeHtml, safeRasterDataUrl, chromeOwnsId = id => /^rapier-(?!anchor-)/.test(id)}) {
|
|
124
|
+
function createInertHtmlSanitizer({parseFragment, serialize, sanitizeHtml, safeRasterDataUrl, chromeOwnsId = id => /^(?:rapier-(?!anchor-)|Rapier[A-Z]|_rapier)/.test(id)}) {
|
|
125
125
|
if (![parseFragment, serialize, sanitizeHtml, safeRasterDataUrl, chromeOwnsId].every(port => typeof port === 'function'))
|
|
126
126
|
throw new TypeError('The inert HTML sanitizer requires every parser and filtering port');
|
|
127
127
|
const uri = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
|
|
@@ -184,7 +184,7 @@ function createInertHtmlSanitizer({parseFragment, serialize, sanitizeHtml, safeR
|
|
|
184
184
|
const key = name.toLowerCase();
|
|
185
185
|
const value = name === 'value' ? inputValue : inputValue.trim();
|
|
186
186
|
if (forbiddenAttributes.has(key) || key.startsWith('on')) continue;
|
|
187
|
-
if (key === 'id' && chromeOwnsId(value)) continue;
|
|
187
|
+
if ((key === 'id' || key === 'name') && chromeOwnsId(value)) continue;
|
|
188
188
|
if (/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i.test(value)
|
|
189
189
|
|| key === 'attributename' && value.includes('href')) continue;
|
|
190
190
|
const data = /^data-[\-\w.\u00b7-\uffff]+$/.test(key), aria = /^aria-[\-\w]+$/.test(key);
|
|
@@ -372,7 +372,7 @@ function _rapierInstallDocumentSanitizeHooks() {
|
|
|
372
372
|
data.keepAttr = !!data.attrValue;
|
|
373
373
|
return;
|
|
374
374
|
}
|
|
375
|
-
if (String(data.attrName || '').toLowerCase()
|
|
375
|
+
if (/^(?:id|name)$/.test(String(data.attrName || '').toLowerCase()) && _rapierChromeOwnsId(value)) { data.keepAttr = false; return; }
|
|
376
376
|
if (_rapierRemoteContent.allowed || _rapierSanitizeRuntime.context === 'source') return;
|
|
377
377
|
const attribute = String(data.attrName || '').toLowerCase();
|
|
378
378
|
if (attribute === 'alt' || attribute === 'title') return;
|
|
@@ -502,7 +502,7 @@ function escapeRapierHtmlText(value) {
|
|
|
502
502
|
|
|
503
503
|
function _rapierChromeOwnsId(id) {
|
|
504
504
|
if (!_rapierSanitizeRuntime.chromeIds) _rapierSanitizeRuntime.chromeIds = new Set(Array.from(typeof document?.querySelectorAll === 'function' ? document.querySelectorAll('[id]') : [], element => element.id));
|
|
505
|
-
return _rapierSanitizeRuntime.chromeIds.has(String(id)) || /^rapier-(?!anchor-)/.test(id);
|
|
505
|
+
return _rapierSanitizeRuntime.chromeIds.has(String(id)) || /^(?:rapier-(?!anchor-)|Rapier[A-Z]|_rapier)/.test(id);
|
|
506
506
|
}
|
|
507
507
|
|
|
508
508
|
const RAPIER_BIDI_CONTROL = /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/;
|
package/dist/math/delimiters.mjs
CHANGED
|
@@ -1,32 +1,71 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
+
function countLines(source, from, to) {
|
|
3
|
+
let lines = 0;
|
|
4
|
+
for (let i = from; i < to; i++) {
|
|
5
|
+
const code = source.charCodeAt(i);
|
|
6
|
+
if (code === 0x0a || code === 0x0d) lines++;
|
|
7
|
+
}
|
|
8
|
+
return lines;
|
|
9
|
+
}
|
|
10
|
+
|
|
2
11
|
export function createMathMatcher(source, {singleDollar = false, brackets = true} = {}) {
|
|
3
12
|
if (typeof source !== 'string') throw new TypeError('Math boundaries require source text.');
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
const len = source.length;
|
|
14
|
+
if (len === 0 || (source.indexOf('$') < 0 && (!brackets || source.indexOf('\\') < 0))) {
|
|
15
|
+
return () => null;
|
|
16
|
+
}
|
|
17
|
+
const markers = new Map(), list = [], next = Object.create(null);
|
|
18
|
+
if (!brackets) {
|
|
19
|
+
let line = 0, lastAt = 0;
|
|
20
|
+
for (let at = source.indexOf('$'); at >= 0; at = source.indexOf('$', at)) {
|
|
21
|
+
line += countLines(source, lastAt, at);
|
|
22
|
+
let slashes = 0;
|
|
23
|
+
for (let s = at - 1; s >= 0 && source.charCodeAt(s) === 0x5c; s--) slashes++;
|
|
11
24
|
let end = at + 1;
|
|
12
|
-
while (source
|
|
25
|
+
while (end < len && source.charCodeAt(end) === 0x24) end++;
|
|
13
26
|
const count = end - at;
|
|
14
|
-
if (!(slashes & 1) && (count === 2 || count === 1 && singleDollar))
|
|
15
|
-
|
|
27
|
+
if (!(slashes & 1) && (count === 2 || count === 1 && singleDollar)) {
|
|
28
|
+
const entry = {at, marker: count === 2 ? '$$' : '$', line, close: undefined};
|
|
29
|
+
markers.set(at, entry);
|
|
30
|
+
list.push(entry);
|
|
31
|
+
}
|
|
32
|
+
lastAt = end;
|
|
16
33
|
at = end;
|
|
17
|
-
} else {
|
|
18
|
-
if (brackets && slashes & 1 && '()[]'.includes(char))
|
|
19
|
-
markers.set(at - 1, {at: at - 1, marker: '\\' + char, line});
|
|
20
|
-
at++;
|
|
21
34
|
}
|
|
22
|
-
|
|
35
|
+
} else {
|
|
36
|
+
let slashes = 0, line = 0;
|
|
37
|
+
for (let at = 0; at < len;) {
|
|
38
|
+
const code = source.charCodeAt(at);
|
|
39
|
+
if (code === 0x5c) { slashes++; at++; continue; }
|
|
40
|
+
if (code === 0x0a || code === 0x0d) line++;
|
|
41
|
+
if (code === 0x24) {
|
|
42
|
+
let end = at + 1;
|
|
43
|
+
while (end < len && source.charCodeAt(end) === 0x24) end++;
|
|
44
|
+
const count = end - at;
|
|
45
|
+
if (!(slashes & 1) && (count === 2 || count === 1 && singleDollar)) {
|
|
46
|
+
const entry = {at, marker: count === 2 ? '$$' : '$', line, close: undefined};
|
|
47
|
+
markers.set(at, entry);
|
|
48
|
+
list.push(entry);
|
|
49
|
+
}
|
|
50
|
+
at = end;
|
|
51
|
+
} else {
|
|
52
|
+
if (slashes & 1 && (code === 0x28 || code === 0x29 || code === 0x5b || code === 0x5d)) {
|
|
53
|
+
const entry = {at: at - 1, marker: '\\' + source[at], line, close: undefined};
|
|
54
|
+
markers.set(at - 1, entry);
|
|
55
|
+
list.push(entry);
|
|
56
|
+
}
|
|
57
|
+
at++;
|
|
58
|
+
}
|
|
59
|
+
slashes = 0;
|
|
60
|
+
}
|
|
23
61
|
}
|
|
24
|
-
for (
|
|
62
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
63
|
+
const entry = list[i];
|
|
25
64
|
const closing = entry.marker === '\\(' ? '\\)' : entry.marker === '\\[' ? '\\]' : entry.marker;
|
|
26
65
|
entry.close = next[closing];
|
|
27
66
|
next[entry.marker] = entry;
|
|
28
67
|
}
|
|
29
|
-
return function match(start, limit =
|
|
68
|
+
return function match(start, limit = len) {
|
|
30
69
|
const open = markers.get(start), close = open?.close;
|
|
31
70
|
if (!close || open.marker === '\\)' || open.marker === '\\]' || close.at + close.marker.length > limit) return null;
|
|
32
71
|
const bodyStart = start + open.marker.length, bodyEnd = close.at;
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {createMathMatcher} from './delimiters.mjs';
|
|
3
3
|
|
|
4
|
+
function isMathFenceInfo(info) {
|
|
5
|
+
if (!info || info.length < 4) return false;
|
|
6
|
+
let start = 0;
|
|
7
|
+
const len = info.length;
|
|
8
|
+
while (start < len && info.charCodeAt(start) <= 0x20) start++;
|
|
9
|
+
if (start + 4 > len) return false;
|
|
10
|
+
if ((info.charCodeAt(start) | 32) !== 0x6d ||
|
|
11
|
+
(info.charCodeAt(start + 1) | 32) !== 0x61 ||
|
|
12
|
+
(info.charCodeAt(start + 2) | 32) !== 0x74 ||
|
|
13
|
+
(info.charCodeAt(start + 3) | 32) !== 0x68) return false;
|
|
14
|
+
return start + 4 === len || info.charCodeAt(start + 4) <= 0x20;
|
|
15
|
+
}
|
|
16
|
+
|
|
4
17
|
export function installMathRules(md, {singleDollar = false, brackets = true, ruleName = 'rapier_math', inLink = false} = {}) {
|
|
5
18
|
const authored = new WeakMap();
|
|
6
19
|
md.core.ruler.before('normalize', ruleName + '_source', state => {
|
|
@@ -9,13 +22,13 @@ export function installMathRules(md, {singleDollar = false, brackets = true, rul
|
|
|
9
22
|
md.core.ruler.after('block', ruleName + '_source_release', state => authored.delete(state.tokens));
|
|
10
23
|
const tokenize = md.block.tokenize;
|
|
11
24
|
md.block.tokenize = function(state, startLine, endLine) {
|
|
12
|
-
const first = state.tokens.length;
|
|
25
|
+
const first = Math.max(state.tokens.length, state._mathScanned || 0);
|
|
13
26
|
try { return tokenize.call(this, state, startLine, endLine); }
|
|
14
27
|
finally {
|
|
15
|
-
|
|
28
|
+
const len = state.tokens.length;
|
|
29
|
+
for (let index = first; index < len; index++) {
|
|
16
30
|
const token = state.tokens[index];
|
|
17
|
-
if (token.type !== 'fence' || !token.map || token.meta?.mathSource !== undefined ||
|
|
18
|
-
String(token.info || '').trim().split(/\s+/)[0].toLowerCase() !== 'math') continue;
|
|
31
|
+
if (token.type !== 'fence' || !token.map || token.meta?.mathSource !== undefined || !isMathFenceInfo(token.info)) continue;
|
|
19
32
|
const held = authored.get(state.tokens), source = held?.source ?? state.src;
|
|
20
33
|
let value;
|
|
21
34
|
if (!state.rapierSourceSlice && source === state.src) value = state.getLines(token.map[0], token.map[1], state.blkIndent, false);
|
|
@@ -42,6 +55,7 @@ export function installMathRules(md, {singleDollar = false, brackets = true, rul
|
|
|
42
55
|
}
|
|
43
56
|
token.meta = {...token.meta, mathSource: value};
|
|
44
57
|
}
|
|
58
|
+
state._mathScanned = len;
|
|
45
59
|
}
|
|
46
60
|
};
|
|
47
61
|
const renderText = md.renderer.renderInlineAsText;
|
|
@@ -62,7 +76,8 @@ export function installMathRules(md, {singleDollar = false, brackets = true, rul
|
|
|
62
76
|
}
|
|
63
77
|
md.inline.ruler.before('escape', ruleName, (state, silent) => {
|
|
64
78
|
if (!inLink && state.linkLevel > 0) return false;
|
|
65
|
-
|
|
79
|
+
const ch = state.src.charCodeAt(state.pos);
|
|
80
|
+
if (ch !== 0x24 && (!brackets || ch !== 0x5c)) return false;
|
|
66
81
|
const span = match(state, state.pos, state.posMax);
|
|
67
82
|
if (!span) return false;
|
|
68
83
|
if (!silent) {
|
|
@@ -77,7 +92,8 @@ export function installMathRules(md, {singleDollar = false, brackets = true, rul
|
|
|
77
92
|
md.block.ruler.before('fence', ruleName, (state, startLine, endLine, silent) => {
|
|
78
93
|
if (state.sCount[startLine] - state.blkIndent >= 4) return false;
|
|
79
94
|
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
80
|
-
|
|
95
|
+
const c0 = state.src.charCodeAt(start), c1 = state.src.charCodeAt(start + 1);
|
|
96
|
+
if (!(c0 === 0x24 && c1 === 0x24) && (!brackets || !(c0 === 0x5c && c1 === 0x5b))) return false;
|
|
81
97
|
const span = match(state, start, state.eMarks[endLine - 1]);
|
|
82
98
|
if (!span?.displayMode) return false;
|
|
83
99
|
let last = startLine;
|
package/dist/spec/md-assets.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Pure text and markdown-it tokens: no DOM, no byte decoding, no globalThis.Rapier* (MIT kit; decoders stay in images/assets.mjs).
|
|
3
3
|
import {isWordSidecarBlock} from './word-sidecar.mjs';
|
|
4
|
+
import {tuneMarkdownEngine} from './md-engine.mjs';
|
|
4
5
|
import {sourceFacts, sourceView, sourceEdit} from '../kit/ledger/source.mjs';
|
|
5
6
|
|
|
6
7
|
export const IMAGE_LIMITS = Object.freeze({bytes: 16 * 1024 * 1024, sourceChars: 64 * 1024 * 1024, dimension: 16384, pixels: 24000000, assets: 1024});
|
|
@@ -66,27 +67,22 @@ function parserFor(factory = configured || globalThis.markdownit) {
|
|
|
66
67
|
if (configured && factory === globalThis.markdownit) return configured;
|
|
67
68
|
if (typeof factory !== 'function') return fail('markdown_parser_unavailable');
|
|
68
69
|
let parser = parsers.get(factory);
|
|
69
|
-
if (!parser) { parser = installMarkdownImages(factory({html: true, maxNesting: 20})); parsers.set(factory, parser); }
|
|
70
|
+
if (!parser) { parser = installMarkdownImages(tuneMarkdownEngine(factory({html: true, maxNesting: 20}))); parsers.set(factory, parser); }
|
|
70
71
|
return parser;
|
|
71
72
|
}
|
|
72
73
|
export {parserFor as markdownParser};
|
|
73
74
|
export const markdownBodyOffset = source => splitSource ? splitSource(source).bodyOffset : 0;
|
|
74
75
|
|
|
75
|
-
const BLOCK_WINDOW = 1024,
|
|
76
|
-
const LINE_TABLES = ['bMarks', 'eMarks', 'tShift', 'sCount', 'bsCount'];
|
|
77
|
-
const lineIndex = key => typeof key === 'string' && /^(?:0|[1-9]\d*)$/.test(key);
|
|
78
|
-
let unheldLine = null;
|
|
79
|
-
const unheldLines = new Proxy(Array.prototype, {
|
|
80
|
-
get: (target, key, table) => unheldLine && lineIndex(key) ? unheldLine(table, Number(key)) : Reflect.get(target, key, table),
|
|
81
|
-
set: (target, key, value, table) => unheldLine && lineIndex(key) ? unheldLine(table, Number(key), true, value) : Reflect.set(target, key, value, table),
|
|
82
|
-
});
|
|
76
|
+
const BLOCK_WINDOW = 1024, WINDOW_END = Symbol('block window end');
|
|
83
77
|
|
|
84
78
|
export function eachBlockWindow(source, bodyOffset, parser, env, visit, keepNul = false) {
|
|
85
79
|
for (const window of blockWindows(source, bodyOffset, parser, env, keepNul)) visit(window);
|
|
86
80
|
}
|
|
87
81
|
|
|
88
82
|
export function* blockWindows(source, bodyOffset, parser, env, keepNul = false) {
|
|
89
|
-
const
|
|
83
|
+
const rawSlice = bodyOffset ? source.slice(bodyOffset) : source;
|
|
84
|
+
const lineEnds = rawSlice.indexOf('\r') >= 0 ? rawSlice.replace(/\r\n?/g, '\n') : rawSlice;
|
|
85
|
+
const body = keepNul || lineEnds.indexOf('\0') < 0 ? lineEnds : lineEnds.replace(/\0/g, '\ufffd');
|
|
90
86
|
let pair = source.indexOf('\r\n', bodyOffset), shift = bodyOffset;
|
|
91
87
|
const sourceOffset = at => {
|
|
92
88
|
while (pair >= 0 && at + shift > pair) { shift++; pair = source.indexOf('\r\n', pair + 2); }
|
|
@@ -94,89 +90,52 @@ export function* blockWindows(source, bodyOffset, parser, env, keepNul = false)
|
|
|
94
90
|
return at + shift;
|
|
95
91
|
};
|
|
96
92
|
const tokens = [];
|
|
97
|
-
|
|
93
|
+
const state = new parser.block.State(body, parser, env, tokens);
|
|
94
|
+
state.rapierSourceSlice = (from, to) => source.slice(sourceOffset(from), sourceOffset(to));
|
|
95
|
+
const totalLines = state.lineMax;
|
|
96
|
+
let origin = 0, first = 0, windowStartLine = 0;
|
|
98
97
|
const report = () => ({tokens, firstLine: first, lineStart: line => {
|
|
99
98
|
if (!line) return sourceOffset(origin);
|
|
100
|
-
const
|
|
99
|
+
const absLine = first + line - 1;
|
|
100
|
+
const end = absLine >= 0 && absLine < totalLines ? state.eMarks[absLine] : body.length;
|
|
101
101
|
return end < body.length ? sourceOffset(end + 1) : undefined;
|
|
102
102
|
}});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (lines <= BLOCK_WINDOW) {
|
|
106
|
-
state = new parser.block.State(body, parser, env, tokens);
|
|
107
|
-
state.rapierSourceSlice = (from, to) => source.slice(sourceOffset(from), sourceOffset(to));
|
|
108
|
-
parser.block.tokenize(state, state.line, state.lineMax);
|
|
103
|
+
if (totalLines <= BLOCK_WINDOW) {
|
|
104
|
+
parser.block.tokenize(state, state.line, totalLines);
|
|
109
105
|
yield report(); return;
|
|
110
106
|
}
|
|
111
|
-
state = new parser.block.State(body.slice(body.lastIndexOf('\n') + 1), parser, env, tokens);
|
|
112
|
-
state.rapierSourceSlice = (from, to) => source.slice(sourceOffset(from), sourceOffset(to));
|
|
113
|
-
lines += state.lineMax;
|
|
114
|
-
state.src = body;
|
|
115
|
-
const tables = new WeakMap();
|
|
116
|
-
for (const name of LINE_TABLES) { state[name].length = 0; tables.set(Object.setPrototypeOf(state[name], unheldLines), name); }
|
|
117
|
-
let frontier = 0, ended = false, betweenBlocks = false;
|
|
118
|
-
const fill = line => {
|
|
119
|
-
while (!ended && line >= state.bMarks.length) {
|
|
120
|
-
const held = state.bMarks.length;
|
|
121
|
-
let until = frontier;
|
|
122
|
-
for (let more = held ? Math.max(TABLE_STEP, held - BLOCK_WINDOW) : BLOCK_WINDOW; more && until < body.length; more--) {
|
|
123
|
-
const end = body.indexOf('\n', until);
|
|
124
|
-
until = end < 0 ? body.length : end + 1;
|
|
125
|
-
}
|
|
126
|
-
const scanned = new parser.block.State(body.slice(frontier, until), parser, env, []);
|
|
127
|
-
ended = until === body.length;
|
|
128
|
-
const added = ended ? scanned.lineMax + 1 : scanned.lineMax;
|
|
129
|
-
for (const name of LINE_TABLES) {
|
|
130
|
-
const old = state[name], next = scanned[name], offset = name === 'bMarks' || name === 'eMarks' ? frontier : 0, table = [];
|
|
131
|
-
for (let at = 0; at < held; at++) table.push(old[at]);
|
|
132
|
-
for (let at = 0; at < added; at++) table.push(next[at] + offset);
|
|
133
|
-
old.length = 0;
|
|
134
|
-
tables.set(table, name);
|
|
135
|
-
state[name] = Object.setPrototypeOf(table, unheldLines);
|
|
136
|
-
}
|
|
137
|
-
frontier = until;
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
const answer = (table, line, write, value) => {
|
|
141
|
-
const name = tables.get(table);
|
|
142
|
-
if (line >= state[name].length) {
|
|
143
|
-
if (betweenBlocks) throw WINDOW_END;
|
|
144
|
-
fill(line);
|
|
145
|
-
}
|
|
146
|
-
const current = state[name], held = Object.hasOwn(current, line);
|
|
147
|
-
if (!write) return held ? current[line] : undefined;
|
|
148
|
-
if (held) current[line] = value;
|
|
149
|
-
else Object.defineProperty(current, line, {value, writable: true, enumerable: true, configurable: true});
|
|
150
|
-
return true;
|
|
151
|
-
};
|
|
152
107
|
const skipEmptyLines = state.skipEmptyLines;
|
|
153
108
|
state.skipEmptyLines = function (line) {
|
|
154
|
-
if (this.level || line !== this.line ||
|
|
155
|
-
if (line >= BLOCK_WINDOW) throw WINDOW_END;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
161
|
-
for (;;) {
|
|
162
|
-
for (let at = origin; at < body.length; at++) {
|
|
163
|
-
const code = body.charCodeAt(at);
|
|
164
|
-
if (code === 10) { origin = at + 1; first++; }
|
|
165
|
-
else if (!parser.utils.isSpace(code)) break;
|
|
109
|
+
if (this.level || line !== this.line || line === windowStartLine) return skipEmptyLines.call(this, line);
|
|
110
|
+
if (line - windowStartLine >= BLOCK_WINDOW) throw WINDOW_END;
|
|
111
|
+
const next = skipEmptyLines.call(this, line);
|
|
112
|
+
if (next - windowStartLine >= BLOCK_WINDOW) {
|
|
113
|
+
this.line = windowStartLine + BLOCK_WINDOW;
|
|
114
|
+
throw WINDOW_END;
|
|
166
115
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
116
|
+
return next;
|
|
117
|
+
};
|
|
118
|
+
for (let startLine = 0; startLine < totalLines;) {
|
|
119
|
+
startLine = skipEmptyLines.call(state, startLine);
|
|
120
|
+
if (startLine >= totalLines) break;
|
|
121
|
+
windowStartLine = startLine;
|
|
122
|
+
first = startLine;
|
|
123
|
+
origin = startLine === 0 ? 0 : state.eMarks[startLine - 1] + 1;
|
|
124
|
+
state.line = startLine;
|
|
125
|
+
state.rapierFirstLine = first;
|
|
171
126
|
let finished = true;
|
|
172
|
-
try { parser.block.tokenize(state,
|
|
127
|
+
try { parser.block.tokenize(state, startLine, totalLines); }
|
|
173
128
|
catch (error) { if (error !== WINDOW_END) throw error; finished = false; }
|
|
174
|
-
|
|
129
|
+
if (first !== 0) {
|
|
130
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
131
|
+
const m = tokens[i].map;
|
|
132
|
+
if (m) { m[0] -= first; m[1] -= first; }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
175
135
|
yield report();
|
|
176
136
|
if (finished) return;
|
|
177
137
|
tokens.length = 0;
|
|
178
|
-
|
|
179
|
-
for (const name of LINE_TABLES) state[name].length = 0;
|
|
138
|
+
startLine = state.line;
|
|
180
139
|
}
|
|
181
140
|
}
|
|
182
141
|
|
|
@@ -184,7 +143,7 @@ const INVALID_PERCENT_PAYLOAD = /[^A-Za-z0-9._~:\/?@+$,;=%-]/;
|
|
|
184
143
|
const isHexUnit = code => code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
|
|
185
144
|
let lastScan = null;
|
|
186
145
|
export function dataImage(url) {
|
|
187
|
-
if (typeof url !== 'string' || url.length > IMAGE_LIMITS.sourceChars) return null;
|
|
146
|
+
if (typeof url !== 'string' || url.length < 15 || (url.charCodeAt(0) | 32) !== 0x64 || url.charCodeAt(4) !== 0x3a || url.length > IMAGE_LIMITS.sourceChars) return null;
|
|
188
147
|
if (url.length > 4096) {
|
|
189
148
|
if (lastScan?.url !== url) lastScan = {url, image: scanDataImage(url)};
|
|
190
149
|
return lastScan.image && {...lastScan.image};
|