securemark 0.298.1 → 0.298.3
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/CHANGELOG.md +8 -0
- package/dist/index.js +140 -168
- package/markdown.d.ts +43 -43
- package/package.json +1 -1
- package/src/combinator/control/constraint/line.ts +1 -1
- package/src/combinator/control/manipulation/match.ts +1 -1
- package/src/combinator/data/delimiter.ts +42 -37
- package/src/combinator/data/parser/context.ts +4 -2
- package/src/combinator/data/parser/inits.ts +1 -1
- package/src/combinator/data/parser/sequence.ts +1 -1
- package/src/combinator/data/parser/some.ts +4 -3
- package/src/parser/api/bind.ts +6 -8
- package/src/parser/api/header.ts +1 -1
- package/src/parser/api/parse.ts +5 -6
- package/src/parser/block/codeblock.ts +1 -1
- package/src/parser/block/extension/fig.ts +1 -1
- package/src/parser/block/extension/figure.ts +1 -1
- package/src/parser/block/extension/message.ts +1 -1
- package/src/parser/block/extension/placeholder.ts +1 -1
- package/src/parser/block/extension/table.ts +1 -1
- package/src/parser/block/heading.ts +1 -1
- package/src/parser/block/mathblock.ts +1 -1
- package/src/parser/block.ts +5 -6
- package/src/parser/header.test.ts +1 -0
- package/src/parser/header.ts +3 -3
- package/src/parser/inline/annotation.ts +26 -10
- package/src/parser/inline/autolink.ts +3 -3
- package/src/parser/inline/bracket.test.ts +5 -2
- package/src/parser/inline/bracket.ts +36 -30
- package/src/parser/inline/html.test.ts +5 -1
- package/src/parser/inline/html.ts +61 -53
- package/src/parser/inline.ts +8 -2
- package/src/parser/processor/figure.ts +5 -3
- package/src/parser/processor/note.test.ts +2 -2
- package/src/parser/processor/note.ts +21 -15
- package/src/parser/segment.ts +8 -7
- package/src/parser/source/escapable.ts +0 -1
- package/src/parser/source/line.ts +6 -4
- package/src/parser/source/text.ts +4 -5
|
@@ -3,7 +3,7 @@ import { State, Recursion } from '../context';
|
|
|
3
3
|
import { List, Node } from '../../combinator/data/parser';
|
|
4
4
|
import { union, some, recursions, precedence, constraint, surround, open, lazy } from '../../combinator';
|
|
5
5
|
import { inline } from '../inline';
|
|
6
|
-
import { indexA } from './bracket';
|
|
6
|
+
import { bracketname, indexA } from './bracket';
|
|
7
7
|
import { beforeNonblank, trimBlankNodeEnd } from '../visibility';
|
|
8
8
|
import { unwrap } from '../util';
|
|
9
9
|
import { html, defrag } from 'typed-dom/dom';
|
|
@@ -34,14 +34,20 @@ export const annotation: AnnotationParser = lazy(() => constraint(State.annotati
|
|
|
34
34
|
if (linebreak !== 0) {
|
|
35
35
|
ns.unshift(new Node('('));
|
|
36
36
|
ns.push(new Node(')'));
|
|
37
|
-
return new List([
|
|
37
|
+
return new List([
|
|
38
|
+
new Node(html('span',
|
|
39
|
+
{ class: 'bracket' },
|
|
40
|
+
['(', html('span', { class: 'bracket' }, defrag(unwrap(ns))), ')']))
|
|
41
|
+
]);
|
|
38
42
|
}
|
|
39
43
|
const depth = MAX_DEPTH - (resources?.recursions[Recursion.annotation] ?? resources?.recursions.at(-1) ?? MAX_DEPTH);
|
|
40
44
|
recursion.add(depth);
|
|
41
|
-
return new List([
|
|
45
|
+
return new List([
|
|
46
|
+
new Node(html('sup', { class: 'annotation' }, [html('span', defrag(unwrap(trimBlankNodeEnd(ns))))]))
|
|
47
|
+
]);
|
|
42
48
|
},
|
|
43
49
|
([, bs], context) => {
|
|
44
|
-
const { source, position,
|
|
50
|
+
const { source, position, linebreak, recursion, resources } = context;
|
|
45
51
|
const depth = MAX_DEPTH - (resources?.recursions[Recursion.annotation] ?? resources?.recursions.at(-1) ?? MAX_DEPTH);
|
|
46
52
|
if (linebreak === 0 && bs && bs.length === 1 && source[position] === ')' && typeof bs.head?.value === 'object') {
|
|
47
53
|
const { className } = bs.head.value;
|
|
@@ -63,12 +69,20 @@ export const annotation: AnnotationParser = lazy(() => constraint(State.annotati
|
|
|
63
69
|
}
|
|
64
70
|
context.position += 1;
|
|
65
71
|
recursion.add(depth);
|
|
66
|
-
return new List([
|
|
72
|
+
return new List([
|
|
73
|
+
new Node(html('span',
|
|
74
|
+
{ class: 'bracket' },
|
|
75
|
+
['(', html('sup', { class: 'annotation' }, [html('span', bs.head.value.childNodes)])]))
|
|
76
|
+
]);
|
|
67
77
|
}
|
|
68
78
|
if (className === 'annotation' && deepunwrap(bs)) {
|
|
69
79
|
context.position += 1;
|
|
70
80
|
recursion.add(depth);
|
|
71
|
-
return new List([
|
|
81
|
+
return new List([
|
|
82
|
+
new Node(html('span',
|
|
83
|
+
{ class: 'bracket' },
|
|
84
|
+
['(', html('sup', { class: 'annotation' }, [html('span', [bs.head.value])])]))
|
|
85
|
+
]);
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
bs ??= new List();
|
|
@@ -76,11 +90,13 @@ export const annotation: AnnotationParser = lazy(() => constraint(State.annotati
|
|
|
76
90
|
if (source[context.position] === ')') {
|
|
77
91
|
bs.push(new Node(')'));
|
|
78
92
|
context.position += 1;
|
|
93
|
+
context.range += 1;
|
|
79
94
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
bs = new List([
|
|
96
|
+
new Node(html('span',
|
|
97
|
+
{ class: bracketname(context, indexA, 2, context.position - position) },
|
|
98
|
+
defrag(unwrap(bs))))
|
|
99
|
+
]);
|
|
84
100
|
bs.unshift(new Node('('));
|
|
85
101
|
const cs = parser(context);
|
|
86
102
|
if (source[context.position] === ')') {
|
|
@@ -14,8 +14,8 @@ export const autolink: AutolinkParser = lazy(() =>
|
|
|
14
14
|
input => {
|
|
15
15
|
const { source, position } = input;
|
|
16
16
|
if (position === source.length) return;
|
|
17
|
-
const
|
|
18
|
-
switch (
|
|
17
|
+
const char = source[position];
|
|
18
|
+
switch (char) {
|
|
19
19
|
case '@':
|
|
20
20
|
return account(input);
|
|
21
21
|
case '#':
|
|
@@ -41,6 +41,6 @@ export const autolink: AutolinkParser = lazy(() =>
|
|
|
41
41
|
}
|
|
42
42
|
return url(input) || email(input);
|
|
43
43
|
default:
|
|
44
|
-
if (isAlphanumeric(
|
|
44
|
+
if (isAlphanumeric(char)) return email(input);
|
|
45
45
|
}
|
|
46
46
|
}));
|
|
@@ -20,9 +20,10 @@ describe('Unit: parser/inline/bracket', () => {
|
|
|
20
20
|
assert.deepStrictEqual(inspect(parser, input('(0)-1', new Context())), [['<span class="paren">(0)</span>'], '-1']);
|
|
21
21
|
assert.deepStrictEqual(inspect(parser, input('(0.1)', new Context())), [['<span class="paren">(0.1)</span>'], '']);
|
|
22
22
|
assert.deepStrictEqual(inspect(parser, input('(0.1.2)', new Context())), [['<span class="paren">(0.1.2)</span>'], '']);
|
|
23
|
-
assert.deepStrictEqual(inspect(parser, input('(1.1, 1.2-1.3, 1.4)', new Context())), [['<span class="paren">(1.1, 1.2-1.3, 1.4)</span>'], '']);
|
|
24
|
-
assert.deepStrictEqual(inspect(parser, input('(1 2)', new Context())), [['<span class="bracket">(1 2)</span>'], '']);
|
|
25
23
|
assert.deepStrictEqual(inspect(parser, input('(1, 2)', new Context())), [['<span class="paren">(1, 2)</span>'], '']);
|
|
24
|
+
assert.deepStrictEqual(inspect(parser, input('(1.1-1.2)', new Context())), [['<span class="paren">(1.1-1.2)</span>'], '']);
|
|
25
|
+
assert.deepStrictEqual(inspect(parser, input('(1.1, 1.2)', new Context())), [['<span class="paren">(1.1, 1.2)</span>'], '']);
|
|
26
|
+
assert.deepStrictEqual(inspect(parser, input('(1 2)', new Context())), [['<span class="bracket">(1 2)</span>'], '']);
|
|
26
27
|
assert.deepStrictEqual(inspect(parser, input('(1a)', new Context())), [['<span class="paren">(1a)</span>'], '']);
|
|
27
28
|
assert.deepStrictEqual(inspect(parser, input('(a)', new Context())), [['<span class="paren">(a)</span>'], '']);
|
|
28
29
|
assert.deepStrictEqual(inspect(parser, input('(a1)', new Context())), [['<span class="paren">(a1)</span>'], '']);
|
|
@@ -39,6 +40,8 @@ describe('Unit: parser/inline/bracket', () => {
|
|
|
39
40
|
assert.deepStrictEqual(inspect(parser, input('(Name, Name)', new Context())), [['<span class="paren">(Name, Name)</span>'], '']);
|
|
40
41
|
assert.deepStrictEqual(inspect(parser, input('(ABBR)', new Context())), [['<span class="paren">(ABBR)</span>'], '']);
|
|
41
42
|
assert.deepStrictEqual(inspect(parser, input('(ABBR, ABBR)', new Context())), [['<span class="paren">(ABBR, ABBR)</span>'], '']);
|
|
43
|
+
assert.deepStrictEqual(inspect(parser, input(`(${'0'.repeat(16)})`, new Context())), [[`<span class="paren">(${'0'.repeat(16)})</span>`], '']);
|
|
44
|
+
assert.deepStrictEqual(inspect(parser, input(`(${'0'.repeat(17)})`, new Context())), [[`<span class="bracket">(${'0'.repeat(17)})</span>`], '']);
|
|
42
45
|
assert.deepStrictEqual(inspect(parser, input('(\\a)', new Context())), [['<span class="bracket">(a)</span>'], '']);
|
|
43
46
|
assert.deepStrictEqual(inspect(parser, input('(==)', new Context())), [['<span class="bracket">(==)</span>'], '']);
|
|
44
47
|
assert.deepStrictEqual(inspect(parser, input('(()', new Context())), [['<span class="bracket">(<span class="paren">()</span></span>'], '']);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BracketParser } from '../inline';
|
|
2
|
-
import { State, Recursion, Backtrack } from '../context';
|
|
2
|
+
import { Context, State, Recursion, Backtrack } from '../context';
|
|
3
3
|
import { List, Node } from '../../combinator/data/parser';
|
|
4
4
|
import { union, some, recursion, precedence, surround, isBacktrack, setBacktrack, lazy } from '../../combinator';
|
|
5
5
|
import { inline } from '../inline';
|
|
@@ -9,8 +9,20 @@ import { unwrap } from '../util';
|
|
|
9
9
|
import { html, defrag } from 'typed-dom/dom';
|
|
10
10
|
|
|
11
11
|
export const indexA = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*$/;
|
|
12
|
-
const indexF = new RegExp(
|
|
13
|
-
.replace(
|
|
12
|
+
const indexF = new RegExp(
|
|
13
|
+
indexA.source.replace(', ', '[,、]')
|
|
14
|
+
.replace(/[09AZaz.]|\-(?!\w)/g, c => String.fromCodePoint(c.codePointAt(0)! + 0xFEE0)),
|
|
15
|
+
'');
|
|
16
|
+
export function bracketname(context: Context, syntax: RegExp, opener: number, closer: number): string {
|
|
17
|
+
const { source, position, range, linebreak } = context;
|
|
18
|
+
syntax.lastIndex = position - range + opener;
|
|
19
|
+
return range - opener - closer === 0
|
|
20
|
+
|| linebreak === 0 &&
|
|
21
|
+
range - opener - closer <= 16 &&
|
|
22
|
+
syntax.test(source.slice(position - range + opener, position - closer))
|
|
23
|
+
? 'paren'
|
|
24
|
+
: 'bracket';
|
|
25
|
+
}
|
|
14
26
|
|
|
15
27
|
export const bracket: BracketParser = lazy(() => union([
|
|
16
28
|
input => {
|
|
@@ -32,45 +44,39 @@ export const bracket: BracketParser = lazy(() => union([
|
|
|
32
44
|
return d1(input);
|
|
33
45
|
}
|
|
34
46
|
}
|
|
35
|
-
]))
|
|
47
|
+
]));
|
|
36
48
|
|
|
37
49
|
const p1 = lazy(() => surround(
|
|
38
50
|
str('('),
|
|
39
51
|
precedence(1, recursion(Recursion.bracket, some(inline, ')', [[')', 1]]))),
|
|
40
52
|
str(')'),
|
|
41
53
|
true, [],
|
|
42
|
-
([as, bs =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
? new List([new Node(html('span', { class: 'paren' }, `(${str}`))])
|
|
53
|
-
: new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(as.import(bs as List<Node<string>>)))))]);
|
|
54
|
-
}));
|
|
54
|
+
([as, bs = new List(), cs], context) => new List([
|
|
55
|
+
new Node(html('span',
|
|
56
|
+
{ class: bracketname(context, indexA, 1, 1) },
|
|
57
|
+
defrag(unwrap(as.import(bs as List<Node<string>>).import(cs)))))
|
|
58
|
+
]),
|
|
59
|
+
([as, bs = new List()], context) => new List([
|
|
60
|
+
new Node(html('span',
|
|
61
|
+
{ class: bracketname(context, indexA, 1, 0) },
|
|
62
|
+
defrag(unwrap(as.import(bs as List<Node<string>>)))))
|
|
63
|
+
])));
|
|
55
64
|
|
|
56
65
|
const p2 = lazy(() => surround(
|
|
57
66
|
str('('),
|
|
58
67
|
precedence(1, recursion(Recursion.bracket, some(inline, ')', [[')', 1]]))),
|
|
59
68
|
str(')'),
|
|
60
69
|
true, [],
|
|
61
|
-
([as, bs =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
? new List([new Node(html('span', { class: 'paren' }, `(${str}`))])
|
|
72
|
-
: new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(as.import(bs as List<Node<string>>)))))]);
|
|
73
|
-
}));
|
|
70
|
+
([as, bs = new List(), cs], context) => new List([
|
|
71
|
+
new Node(html('span',
|
|
72
|
+
{ class: bracketname(context, indexF, 1, 1) },
|
|
73
|
+
defrag(unwrap(as.import(bs as List<Node<string>>).import(cs)))))
|
|
74
|
+
]),
|
|
75
|
+
([as, bs = new List()], context) => new List([
|
|
76
|
+
new Node(html('span',
|
|
77
|
+
{ class: bracketname(context, indexF, 1, 0) },
|
|
78
|
+
defrag(unwrap(as.import(bs as List<Node<string>>)))))
|
|
79
|
+
])));
|
|
74
80
|
|
|
75
81
|
const s1 = lazy(() => surround(
|
|
76
82
|
str('['),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { html } from './html';
|
|
1
|
+
import { html, TAGS } from './html';
|
|
2
2
|
import { some } from '../../combinator';
|
|
3
3
|
import { input } from '../../combinator/data/parser';
|
|
4
4
|
import { Context } from '../context';
|
|
@@ -8,6 +8,10 @@ describe('Unit: parser/inline/html', () => {
|
|
|
8
8
|
describe('html', () => {
|
|
9
9
|
const parser = some(html);
|
|
10
10
|
|
|
11
|
+
it('hash', () => {
|
|
12
|
+
assert(TAGS.every(tag => parser(input(`<${tag}>`, new Context()))));
|
|
13
|
+
});
|
|
14
|
+
|
|
11
15
|
it('xss', () => {
|
|
12
16
|
assert.deepStrictEqual(inspect(parser, input('<script>', new Context())), [['<span class="invalid"><script></span>'], '']);
|
|
13
17
|
assert.deepStrictEqual(inspect(parser, input('<script>alert()<script>', new Context())), [['<span class="invalid"><script>alert()<script></span>'], '']);
|
|
@@ -2,7 +2,7 @@ import { HTMLParser } from '../inline';
|
|
|
2
2
|
import { Recursion } from '../context';
|
|
3
3
|
import { List, Node, Context } from '../../combinator/data/parser';
|
|
4
4
|
import { Flag } from '../node';
|
|
5
|
-
import { union, some, recursion, precedence,
|
|
5
|
+
import { union, some, recursion, precedence, surround, open, match, lazy } from '../../combinator';
|
|
6
6
|
import { inline } from '../inline';
|
|
7
7
|
import { str } from '../source';
|
|
8
8
|
import { isNonblankFirstLine, blankWith } from '../visibility';
|
|
@@ -20,58 +20,57 @@ const attrspecs = {
|
|
|
20
20
|
Object.setPrototypeOf(attrspecs, null);
|
|
21
21
|
Object.values(attrspecs).forEach(o => Object.setPrototypeOf(o, null));
|
|
22
22
|
|
|
23
|
-
export const html: HTMLParser = lazy(() =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
([as, bs
|
|
32
|
-
|
|
33
|
-
([as, bs
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
surround
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
open(str(/ ?/y), str('>'), true),
|
|
44
|
-
true, [],
|
|
45
|
-
([as, bs = new List(), cs]) => as.import(bs).import(cs),
|
|
46
|
-
([as, bs = new List()]) => as.import(bs)),
|
|
47
|
-
// 不可視のHTML構造が可視構造を変化させるべきでない。
|
|
48
|
-
// 可視のHTMLは優先度変更を検討する。
|
|
49
|
-
// このため`<>`記号は将来的に共通構造を変化させる可能性があり
|
|
50
|
-
// 共通構造を変化させない非構造文字列としては依然としてエスケープを要する。
|
|
51
|
-
precedence(0, recursion(Recursion.inline,
|
|
52
|
-
some(union([
|
|
53
|
-
some(inline, blankWith('\n', `</${tag}>`)),
|
|
54
|
-
open('\n', some(inline, `</${tag}>`), true),
|
|
55
|
-
])))),
|
|
56
|
-
str(`</${tag}>`),
|
|
23
|
+
export const html: HTMLParser = lazy(() => union([
|
|
24
|
+
surround(
|
|
25
|
+
// https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
|
26
|
+
str(/<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[ >])/y),
|
|
27
|
+
precedence(9, some(union([attribute]))),
|
|
28
|
+
open(str(/ ?/y), str('>'), true),
|
|
29
|
+
true, [],
|
|
30
|
+
([as, bs = new List(), cs], context) =>
|
|
31
|
+
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context), as.head!.value === '<wbr' ? Flag.blank : Flag.none)]),
|
|
32
|
+
([as, bs = new List()], context) =>
|
|
33
|
+
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
|
|
34
|
+
match(
|
|
35
|
+
new RegExp(String.raw`<(${TAGS.join('|')})(?=[ >])`, 'y'),
|
|
36
|
+
memoize(
|
|
37
|
+
([, tag]) =>
|
|
38
|
+
surround<HTMLParser.TagParser, string>(
|
|
39
|
+
surround(
|
|
40
|
+
str(`<${tag}`),
|
|
41
|
+
precedence(9, some(attribute)),
|
|
42
|
+
open(str(/ ?/y), str('>'), true),
|
|
57
43
|
true, [],
|
|
58
|
-
([as, bs = new List(), cs]
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
44
|
+
([as, bs = new List(), cs]) => as.import(bs).import(cs),
|
|
45
|
+
([as, bs = new List()]) => as.import(bs)),
|
|
46
|
+
// 不可視のHTML構造が可視構造を変化させるべきでない。
|
|
47
|
+
// 可視のHTMLは優先度変更を検討する。
|
|
48
|
+
// このため`<>`記号は将来的に共通構造を変化させる可能性があり
|
|
49
|
+
// 共通構造を変化させない非構造文字列としては依然としてエスケープを要する。
|
|
50
|
+
precedence(0, recursion(Recursion.inline,
|
|
51
|
+
some(union([
|
|
52
|
+
some(inline, blankWith('\n', `</${tag}>`)),
|
|
53
|
+
open('\n', some(inline, `</${tag}>`), true),
|
|
54
|
+
])))),
|
|
55
|
+
str(`</${tag}>`),
|
|
56
|
+
true, [],
|
|
57
|
+
([as, bs = new List(), cs], context) =>
|
|
58
|
+
new List([new Node(elem(tag, true, [...unwrap(as)], bs, cs, context))]),
|
|
59
|
+
([as, bs = new List()], context) =>
|
|
60
|
+
new List([new Node(elem(tag, true, [...unwrap(as)], bs, new List(), context))])),
|
|
61
|
+
([, tag]) => tag2index(tag),
|
|
62
|
+
Array(TAGS.length))),
|
|
63
|
+
surround(
|
|
64
|
+
// https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
|
65
|
+
str(/<[a-z]+(?=[ >])/yi),
|
|
66
|
+
precedence(9, some(union([attribute]))),
|
|
67
|
+
open(str(/ ?/y), str('>'), true),
|
|
68
|
+
true, [],
|
|
69
|
+
([as, bs = new List(), cs], context) =>
|
|
70
|
+
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context))]),
|
|
71
|
+
([as, bs = new List()], context) =>
|
|
72
|
+
new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
|
|
73
|
+
]));
|
|
75
74
|
|
|
76
75
|
export const attribute: HTMLParser.AttributeParser = union([
|
|
77
76
|
str(/ [a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[ >])/yi),
|
|
@@ -146,7 +145,7 @@ export function attributes(
|
|
|
146
145
|
|
|
147
146
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
|
|
148
147
|
// [...document.querySelectorAll('tbody > tr > td:first-child')].map(el => el.textContent.slice(1, -1))
|
|
149
|
-
const TAGS: readonly string[] = [
|
|
148
|
+
export const TAGS: readonly string[] = [
|
|
150
149
|
"html",
|
|
151
150
|
"base",
|
|
152
151
|
"head",
|
|
@@ -284,3 +283,12 @@ const TAGS: readonly string[] = [
|
|
|
284
283
|
"tt",
|
|
285
284
|
"xmp",
|
|
286
285
|
];
|
|
286
|
+
|
|
287
|
+
const tag2index: (tag: string) => number = eval([
|
|
288
|
+
'tag => {',
|
|
289
|
+
'switch(tag){',
|
|
290
|
+
TAGS.map((tag, i) => `case '${tag}':return ${i};`).join(''),
|
|
291
|
+
'default: throw new Error();',
|
|
292
|
+
'}',
|
|
293
|
+
'}',
|
|
294
|
+
].join(''));
|
package/src/parser/inline.ts
CHANGED
|
@@ -92,7 +92,8 @@ export const inline: InlineParser = lazy(() => union([
|
|
|
92
92
|
case '{':
|
|
93
93
|
return bracket(input);
|
|
94
94
|
case '<':
|
|
95
|
-
return html(input);
|
|
95
|
+
if (isAlphabet(source[position + 1])) return html(input);
|
|
96
|
+
break;
|
|
96
97
|
case '$':
|
|
97
98
|
if (source[position + 1] === '{') return math(input);
|
|
98
99
|
return label(input)
|
|
@@ -123,7 +124,7 @@ export const inline: InlineParser = lazy(() => union([
|
|
|
123
124
|
},
|
|
124
125
|
autolink,
|
|
125
126
|
text
|
|
126
|
-
]))
|
|
127
|
+
]));
|
|
127
128
|
|
|
128
129
|
export { indexee } from './inline/extension/indexee';
|
|
129
130
|
export { indexer } from './inline/extension/indexer';
|
|
@@ -131,3 +132,8 @@ export { dataindex } from './inline/extension/index';
|
|
|
131
132
|
export { medialink } from './inline/link';
|
|
132
133
|
export { media } from './inline/media';
|
|
133
134
|
export { shortmedia, lineshortmedia } from './inline/shortmedia';
|
|
135
|
+
|
|
136
|
+
function isAlphabet(char: string): boolean {
|
|
137
|
+
assert(char.length === 1);
|
|
138
|
+
return 'a' <= char && char <= 'z';
|
|
139
|
+
}
|
|
@@ -13,22 +13,24 @@ export function* figure(
|
|
|
13
13
|
readonly local?: boolean;
|
|
14
14
|
} = {},
|
|
15
15
|
): Generator<HTMLAnchorElement | undefined, undefined, undefined> {
|
|
16
|
+
const selector = ':is(figure[data-label], h1, h2)';
|
|
16
17
|
const refs = new MultiQueue<string, HTMLAnchorElement>(push(
|
|
17
18
|
querySelectorAll(target, 'a.label:not(.local)[data-label]'),
|
|
18
19
|
notes && querySelectorAll(notes.references, 'a.label:not(.local)') || [])
|
|
19
20
|
.map(el => [el.getAttribute('data-label')!, el]));
|
|
20
21
|
const labels = new Set<string>();
|
|
21
22
|
const numbers = new Map<string, string>();
|
|
22
|
-
const scope = target instanceof Element ? ':scope > ' : '';
|
|
23
23
|
let base = '0';
|
|
24
24
|
let bases: readonly string[] = base.split('.');
|
|
25
25
|
let index: readonly string[] = bases;
|
|
26
26
|
for (
|
|
27
|
-
let defs = target
|
|
27
|
+
let defs = target instanceof Element
|
|
28
|
+
? target.querySelectorAll(`:scope > ${selector}`)
|
|
29
|
+
: target.querySelectorAll(`${selector}:not(* > *)`),
|
|
28
30
|
len = defs.length, i = 0; i < len; ++i) {
|
|
29
31
|
yield;
|
|
30
32
|
const def = defs[i];
|
|
31
|
-
|
|
33
|
+
assert(def.parentNode === target || !def.parentNode);
|
|
32
34
|
const { tagName } = def;
|
|
33
35
|
if (bases.length === 1 && tagName[0] === 'H') continue;
|
|
34
36
|
assert(base === '0' || bases.length > 1);
|
|
@@ -116,11 +116,11 @@ describe('Unit: parser/processor/note', () => {
|
|
|
116
116
|
});
|
|
117
117
|
|
|
118
118
|
it('separation', () => {
|
|
119
|
-
const target =
|
|
119
|
+
const target = parse([
|
|
120
120
|
'!>> ((1))\n> ((2))\n~~~',
|
|
121
121
|
'~~~~example/markdown\n((3))\n~~~~',
|
|
122
122
|
'((4))',
|
|
123
|
-
].join('\n\n'))
|
|
123
|
+
].join('\n\n'));
|
|
124
124
|
for (let i = 0; i < 3; ++i) {
|
|
125
125
|
[...note(target)];
|
|
126
126
|
assert.deepStrictEqual(
|
|
@@ -50,19 +50,19 @@ const referenceRefsMemoryCaller = memoize((target: Node) =>
|
|
|
50
50
|
const annotation = build(
|
|
51
51
|
'annotation',
|
|
52
52
|
'annotations',
|
|
53
|
-
'.annotation:not(:is(.annotations, .references)
|
|
53
|
+
'.annotation:not(:is(.annotations, .references) &, .local)',
|
|
54
54
|
n => `*${n}`,
|
|
55
55
|
'h1, h2, h3, h4, h5, h6, aside.aside, hr');
|
|
56
56
|
const reference = build(
|
|
57
57
|
'reference',
|
|
58
58
|
'references',
|
|
59
|
-
'.reference:not(:is(.annotations, .references)
|
|
59
|
+
'.reference:not(:is(.annotations, .references) &, .local)',
|
|
60
60
|
(n, abbr) => `[${abbr || n}]`);
|
|
61
61
|
|
|
62
62
|
function build(
|
|
63
63
|
syntax: string,
|
|
64
64
|
list: string,
|
|
65
|
-
|
|
65
|
+
selector: string,
|
|
66
66
|
marker: (index: number, abbr: string) => string,
|
|
67
67
|
splitter: string = '',
|
|
68
68
|
) {
|
|
@@ -101,7 +101,7 @@ function build(
|
|
|
101
101
|
};
|
|
102
102
|
}, memory);
|
|
103
103
|
const defs = new Map<string, HTMLLIElement>();
|
|
104
|
-
const refs = target.querySelectorAll<HTMLElement>(
|
|
104
|
+
const refs = target.querySelectorAll<HTMLElement>(selector);
|
|
105
105
|
const identifierInfoCaller = memoize((identifier: string) => ({
|
|
106
106
|
defIndex: 0,
|
|
107
107
|
defSubindex: 0,
|
|
@@ -109,8 +109,11 @@ function build(
|
|
|
109
109
|
title: '' && identifier,
|
|
110
110
|
queue: [] as HTMLElement[],
|
|
111
111
|
}));
|
|
112
|
-
const
|
|
113
|
-
|
|
112
|
+
const splitters = splitter
|
|
113
|
+
? target instanceof Element
|
|
114
|
+
? target.querySelectorAll(`:scope > :is(${splitter}, .${list})`)
|
|
115
|
+
: target.querySelectorAll(`:is(${splitter}, .${list}):not(* > *)`)
|
|
116
|
+
: [];
|
|
114
117
|
let iSplitters = 0;
|
|
115
118
|
let total = 0;
|
|
116
119
|
let format: 'number' | 'abbr';
|
|
@@ -118,11 +121,12 @@ function build(
|
|
|
118
121
|
for (let len = refs.length, i = 0; i < len; ++i) {
|
|
119
122
|
const ref = refs[i];
|
|
120
123
|
if (splitter) for (let splitter; splitter = splitters[iSplitters]; ++iSplitters) {
|
|
124
|
+
assert(splitter.parentNode === target || !splitter.parentNode);
|
|
121
125
|
const pos = splitter?.compareDocumentPosition(ref) ?? 0;
|
|
122
126
|
if (pos & (Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_DISCONNECTED)) break;
|
|
123
127
|
if (~iSplitters << 32 - 8 === 0) yield;
|
|
124
|
-
if (splitter.classList.contains(list) &&
|
|
125
|
-
|
|
128
|
+
if (splitter.classList.contains(list) && splitter.nextElementSibling !== splitters[iSplitters + 1]) {
|
|
129
|
+
yield* proc(splitter as HTMLOListElement);
|
|
126
130
|
splitter.remove();
|
|
127
131
|
continue;
|
|
128
132
|
}
|
|
@@ -133,7 +137,7 @@ function build(
|
|
|
133
137
|
? splitter as HTMLOListElement
|
|
134
138
|
: target.insertBefore(html('ol', { class: list }), splitter);
|
|
135
139
|
assert(note.parentNode);
|
|
136
|
-
yield* proc(
|
|
140
|
+
yield* proc(note, defs);
|
|
137
141
|
assert(defs.size === 0);
|
|
138
142
|
}
|
|
139
143
|
}
|
|
@@ -209,25 +213,27 @@ function build(
|
|
|
209
213
|
}
|
|
210
214
|
if (note || defs.size > 0) {
|
|
211
215
|
const splitter = splitters[iSplitters++];
|
|
212
|
-
|
|
216
|
+
note ??= splitter?.classList.contains(list)
|
|
213
217
|
? splitter as HTMLOListElement
|
|
214
|
-
: target.insertBefore(html('ol', { class: list }), splitter ?? bottom)
|
|
218
|
+
: target.insertBefore(html('ol', { class: list }), splitter ?? bottom);
|
|
219
|
+
yield* proc(note, defs);
|
|
215
220
|
assert(defs.size === 0);
|
|
216
221
|
}
|
|
217
222
|
if (splitter) for (let splitter; splitter = splitters[iSplitters]; ++iSplitters) {
|
|
218
223
|
if (~iSplitters << 32 - 8 === 0) yield;
|
|
219
224
|
if (splitter.classList.contains(list)) {
|
|
225
|
+
yield* proc(splitter as HTMLOListElement);
|
|
220
226
|
splitter.remove();
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
|
-
}
|
|
229
|
+
};
|
|
224
230
|
}
|
|
225
231
|
|
|
226
|
-
function* proc(
|
|
227
|
-
const { children } = note;
|
|
232
|
+
function* proc(note: HTMLOListElement, defs?: Map<string, HTMLLIElement>): Generator<HTMLLIElement | undefined, undefined, undefined> {
|
|
228
233
|
for (let defs = note.children, i = defs.length; i--;) {
|
|
229
|
-
yield note.removeChild(
|
|
234
|
+
yield note.removeChild(defs[i] as HTMLLIElement);
|
|
230
235
|
}
|
|
236
|
+
if (!defs) return;
|
|
231
237
|
for (const [, def] of defs) {
|
|
232
238
|
yield note.appendChild(def);
|
|
233
239
|
}
|
package/src/parser/segment.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { segment as codeblock } from './block/codeblock';
|
|
|
6
6
|
import { segment as mathblock } from './block/mathblock';
|
|
7
7
|
import { segment as extension } from './block/extension';
|
|
8
8
|
import { contentline, emptysegment } from './source';
|
|
9
|
+
import { normalize } from './api';
|
|
9
10
|
|
|
10
11
|
import SegmentParser = MarkdownParser.SegmentParser;
|
|
11
12
|
|
|
@@ -35,10 +36,10 @@ const parser: SegmentParser = union([
|
|
|
35
36
|
}
|
|
36
37
|
},
|
|
37
38
|
some(contentline, MAX_SEGMENT_SIZE + 1),
|
|
38
|
-
])
|
|
39
|
+
]);
|
|
39
40
|
|
|
40
|
-
export function* segment(source: string): Generator<readonly [string, Segment], undefined, undefined> {
|
|
41
|
-
if (!validate(source, MAX_INPUT_SIZE)) return yield [`${Command.Error}Too large input over ${MAX_INPUT_SIZE.toLocaleString('en')} bytes.\n${source.slice(0, 1001)}`, Segment.unknown];
|
|
41
|
+
export function* segment(source: string, initial = true): Generator<readonly [string, Segment], undefined, undefined> {
|
|
42
|
+
if (initial && !validate(source, MAX_INPUT_SIZE)) return yield [`${Command.Error}Too large input over ${MAX_INPUT_SIZE.toLocaleString('en')} bytes.\n${source.slice(0, 1001)}`, Segment.unknown];
|
|
42
43
|
assert(source.length < Number.MAX_SAFE_INTEGER);
|
|
43
44
|
for (let position = 0; position < source.length;) {
|
|
44
45
|
const context = new Context({ source, position });
|
|
@@ -52,14 +53,14 @@ export function* segment(source: string): Generator<readonly [string, Segment],
|
|
|
52
53
|
position = context.position;
|
|
53
54
|
for (let i = 0; i < segs.length; ++i) {
|
|
54
55
|
const seg = segs[i];
|
|
55
|
-
validate(seg, MAX_SEGMENT_SIZE)
|
|
56
|
-
? yield [seg
|
|
57
|
-
: yield [
|
|
56
|
+
initial && !validate(seg, MAX_SEGMENT_SIZE)
|
|
57
|
+
? yield [`${Command.Error}Too large segment over ${MAX_SEGMENT_SIZE.toLocaleString('en')} bytes.\n${seg}`, Segment.unknown]
|
|
58
|
+
: yield [initial ? normalize(seg) : seg, context.segment];
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function validate(source: string, size: number): boolean {
|
|
63
|
-
return source.length <= size /
|
|
64
|
+
return source.length <= size / 2
|
|
64
65
|
|| source.length <= size && new Blob([source]).size <= size;
|
|
65
66
|
}
|
|
@@ -10,7 +10,7 @@ export const anyline: AnyLineParser = input => {
|
|
|
10
10
|
return new List();
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
const regEmptyline = /[^\S\n]*(?:$|\n)/y;
|
|
13
|
+
const regEmptyline = /[^\S\r\n]*(?:$|\r?\n)/y;
|
|
14
14
|
export const emptyline: EmptyLineParser = input => {
|
|
15
15
|
const context = input;
|
|
16
16
|
const { source, position } = context;
|
|
@@ -36,18 +36,20 @@ export const emptysegment: EmptySegmentParser = input => {
|
|
|
36
36
|
return new List();
|
|
37
37
|
};
|
|
38
38
|
function eoel(source: string, position: number): number {
|
|
39
|
-
|
|
39
|
+
const char = source[position];
|
|
40
|
+
if (char === '\n' || char === '\r' && source[position + 1] === '\n') return position + 1;
|
|
40
41
|
regEmptyline.lastIndex = position;
|
|
41
42
|
regEmptyline.test(source);
|
|
42
43
|
return regEmptyline.lastIndex || position;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
const regContentline = /[^\S\n]*\S[^\n]*(?:$|\n)/y;
|
|
46
|
+
const regContentline = /[^\S\r\n]*\S[^\r\n]*(?:$|\r?\n)/y;
|
|
46
47
|
export const contentline: ContentLineParser = input => {
|
|
47
48
|
const context = input;
|
|
48
49
|
const { source, position } = context;
|
|
49
50
|
if (position === source.length) return;
|
|
50
|
-
|
|
51
|
+
const char = source[position];
|
|
52
|
+
if (char === '\n' || char === '\r' && source[position + 1] === '\n') return;
|
|
51
53
|
regContentline.lastIndex = position;
|
|
52
54
|
regContentline.test(source);
|
|
53
55
|
const i = regContentline.lastIndex;
|