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.
Files changed (39) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/index.js +140 -168
  3. package/markdown.d.ts +43 -43
  4. package/package.json +1 -1
  5. package/src/combinator/control/constraint/line.ts +1 -1
  6. package/src/combinator/control/manipulation/match.ts +1 -1
  7. package/src/combinator/data/delimiter.ts +42 -37
  8. package/src/combinator/data/parser/context.ts +4 -2
  9. package/src/combinator/data/parser/inits.ts +1 -1
  10. package/src/combinator/data/parser/sequence.ts +1 -1
  11. package/src/combinator/data/parser/some.ts +4 -3
  12. package/src/parser/api/bind.ts +6 -8
  13. package/src/parser/api/header.ts +1 -1
  14. package/src/parser/api/parse.ts +5 -6
  15. package/src/parser/block/codeblock.ts +1 -1
  16. package/src/parser/block/extension/fig.ts +1 -1
  17. package/src/parser/block/extension/figure.ts +1 -1
  18. package/src/parser/block/extension/message.ts +1 -1
  19. package/src/parser/block/extension/placeholder.ts +1 -1
  20. package/src/parser/block/extension/table.ts +1 -1
  21. package/src/parser/block/heading.ts +1 -1
  22. package/src/parser/block/mathblock.ts +1 -1
  23. package/src/parser/block.ts +5 -6
  24. package/src/parser/header.test.ts +1 -0
  25. package/src/parser/header.ts +3 -3
  26. package/src/parser/inline/annotation.ts +26 -10
  27. package/src/parser/inline/autolink.ts +3 -3
  28. package/src/parser/inline/bracket.test.ts +5 -2
  29. package/src/parser/inline/bracket.ts +36 -30
  30. package/src/parser/inline/html.test.ts +5 -1
  31. package/src/parser/inline/html.ts +61 -53
  32. package/src/parser/inline.ts +8 -2
  33. package/src/parser/processor/figure.ts +5 -3
  34. package/src/parser/processor/note.test.ts +2 -2
  35. package/src/parser/processor/note.ts +21 -15
  36. package/src/parser/segment.ts +8 -7
  37. package/src/parser/source/escapable.ts +0 -1
  38. package/src/parser/source/line.ts +6 -4
  39. 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([new Node(html('span', { class: 'bracket' }, ['(', html('span', { class: 'bracket' }, defrag(unwrap(ns))), ')']))]);
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([new Node(html('sup', { class: 'annotation' }, [html('span', defrag(unwrap(trimBlankNodeEnd(ns))))]))]);
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, range, linebreak, recursion, resources } = context;
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([new Node(html('span', { class: 'bracket' }, ['(', html('sup', { class: 'annotation' }, [html('span', bs.head.value.childNodes)])]))]);
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([new Node(html('span', { class: 'bracket' }, ['(', html('sup', { class: 'annotation' }, [html('span', [bs.head.value])])]))]);
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
- const str = linebreak === 0 ? source.slice(position - range + 2, position) : '';
81
- bs = linebreak === 0 && (str === '' || indexA.test(str))
82
- ? new List([new Node(html('span', { class: 'paren' }, defrag(unwrap(bs))))])
83
- : new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(bs))))]);
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 fst = source[position];
18
- switch (fst) {
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(fst)) return email(input);
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(indexA.source.replace(', ', '[,、]')
13
- .replace(/[09AZaz.]|\-(?!\w)/g, c => String.fromCodePoint(c.codePointAt(0)! + 0xFEE0)));
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
- ])) as any;
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 = [], cs], { source, position, range, linebreak }) => {
43
- const str = linebreak === 0 ? source.slice(position - range + 1, position - 1) : '';
44
- return linebreak === 0 && (str === '' || indexA.test(str))
45
- ? new List([new Node(html('span', { class: 'paren' }, `(${str})`))])
46
- : new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(as.import(bs as List<Node<string>>).import(cs)))))]);
47
- },
48
- ([as, bs = new List()], context) => {
49
- const { source, position, range, linebreak } = context;
50
- const str = linebreak === 0 ? source.slice(position - range + 1, position) : '';
51
- return linebreak === 0 && (str === '' || indexA.test(str))
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 = [], cs], { source, position, range, linebreak }) => {
62
- const str = linebreak === 0 ? source.slice(position - range + 1, position - 1) : '';
63
- return linebreak === 0 && (str === '' || indexF.test(str))
64
- ? new List([new Node(html('span', { class: 'paren' }, `(${str})`))])
65
- : new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(as.import(bs as List<Node<string>>).import(cs)))))]);
66
- },
67
- ([as, bs = new List()], context) => {
68
- const { source, position, range, linebreak } = context;
69
- const str = linebreak === 0 ? source.slice(position - range + 1, position) : '';
70
- return linebreak === 0 && (str === '' || indexF.test(str))
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">&lt;script&gt;</span>'], '']);
13
17
  assert.deepStrictEqual(inspect(parser, input('<script>alert()<script>', new Context())), [['<span class="invalid">&lt;script&gt;alert()&lt;script&gt;</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, validate, surround, open, match, lazy } from '../../combinator';
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(() => validate(/<[a-z]+(?=[ >])/yi,
24
- union([
25
- surround(
26
- // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
27
- str(/<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[ >])/y),
28
- precedence(9, some(union([attribute]))),
29
- open(str(/ ?/y), str('>'), true),
30
- true, [],
31
- ([as, bs = new List(), cs], context) =>
32
- 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)]),
33
- ([as, bs = new List()], context) =>
34
- new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
35
- match(
36
- new RegExp(String.raw`<(${TAGS.join('|')})(?=[ >])`, 'y'),
37
- memoize(
38
- ([, tag]) =>
39
- surround<HTMLParser.TagParser, string>(
40
- surround(
41
- str(`<${tag}`),
42
- precedence(9, some(attribute)),
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], context) =>
59
- new List([new Node(elem(tag, true, [...unwrap(as)], bs, cs, context))]),
60
- ([as, bs = new List()], context) =>
61
- new List([new Node(elem(tag, true, [...unwrap(as)], bs, new List(), context))])),
62
- ([, tag]) => tag,
63
- new Map())),
64
- surround(
65
- // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
66
- str(/<[a-z]+(?=[ >])/yi),
67
- precedence(9, some(union([attribute]))),
68
- open(str(/ ?/y), str('>'), true),
69
- true, [],
70
- ([as, bs = new List(), cs], context) =>
71
- new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context))]),
72
- ([as, bs = new List()], context) =>
73
- new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
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(''));
@@ -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
- ])) as any;
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.querySelectorAll(`${scope}:is(figure[data-label], h1, h2)`),
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
- if (!scope && def.parentNode !== target) continue;
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 = html('blockquote', parse([
119
+ const target = parse([
120
120
  '!>> ((1))\n> ((2))\n~~~',
121
121
  '~~~~example/markdown\n((3))\n~~~~',
122
122
  '((4))',
123
- ].join('\n\n')).children);
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) .annotation, .local)',
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) .reference, .local)',
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
- query: string,
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>(query);
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 scope = target instanceof Element ? ':scope > ' : '';
113
- const splitters = splitter ? target.querySelectorAll(`${scope}:is(${splitter})`) : [];
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) && defs.size === 0) {
125
- assert(splitter.matches(`.${list}`));
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(defs, note);
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
- yield* proc(defs, note ?? (splitter?.classList.contains(list)
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(defs: Map<string, HTMLLIElement>, note: HTMLOListElement): Generator<HTMLLIElement | undefined, undefined, undefined> {
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(children[i] as HTMLLIElement);
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
  }
@@ -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
- ]) as any;
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, context.segment]
57
- : yield [`${Command.Error}Too large segment over ${MAX_SEGMENT_SIZE.toLocaleString('en')} bytes.\n${seg}`, Segment.unknown];
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 / 4
64
+ return source.length <= size / 2
64
65
  || source.length <= size && new Blob([source]).size <= size;
65
66
  }
@@ -31,7 +31,6 @@ export const escsource: EscapableSourceParser = context => {
31
31
  return new List([new Node(source.slice(position, position + 2))]);
32
32
  }
33
33
  case '\r':
34
- consume(-1, context);
35
34
  return new List();
36
35
  case '\n':
37
36
  context.linebreak ||= source.length - position;
@@ -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
- if (source[position] === '\n') return position + 1;
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
- if (source[position] === '\n') return;
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;