securemark 0.290.2 → 0.291.0

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 (51) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/design.md +48 -2
  3. package/dist/index.js +215 -121
  4. package/markdown.d.ts +6 -17
  5. package/package.json +1 -1
  6. package/src/combinator/control/manipulation/surround.ts +17 -13
  7. package/src/combinator/data/parser/context/delimiter.ts +2 -2
  8. package/src/combinator/data/parser/context.ts +4 -3
  9. package/src/combinator/data/parser/some.ts +3 -3
  10. package/src/combinator/data/parser.ts +5 -3
  11. package/src/parser/api/parse.test.ts +34 -30
  12. package/src/parser/block/dlist.ts +1 -1
  13. package/src/parser/block/heading.ts +2 -2
  14. package/src/parser/block/mediablock.ts +2 -2
  15. package/src/parser/block/pagebreak.ts +1 -1
  16. package/src/parser/block/reply.ts +1 -1
  17. package/src/parser/block.ts +3 -1
  18. package/src/parser/header.ts +1 -1
  19. package/src/parser/inline/annotation.ts +4 -5
  20. package/src/parser/inline/autolink/account.ts +1 -1
  21. package/src/parser/inline/autolink/anchor.ts +1 -1
  22. package/src/parser/inline/autolink/channel.ts +1 -1
  23. package/src/parser/inline/autolink/email.ts +1 -1
  24. package/src/parser/inline/autolink/hashnum.ts +1 -1
  25. package/src/parser/inline/autolink/hashtag.ts +1 -1
  26. package/src/parser/inline/autolink/url.test.ts +8 -2
  27. package/src/parser/inline/autolink/url.ts +5 -6
  28. package/src/parser/inline/bracket.ts +25 -3
  29. package/src/parser/inline/code.ts +2 -2
  30. package/src/parser/inline/extension/index.ts +42 -26
  31. package/src/parser/inline/extension/indexee.ts +6 -3
  32. package/src/parser/inline/extension/label.ts +1 -1
  33. package/src/parser/inline/html.test.ts +22 -19
  34. package/src/parser/inline/html.ts +82 -78
  35. package/src/parser/inline/link.ts +12 -9
  36. package/src/parser/inline/mark.ts +1 -1
  37. package/src/parser/inline/math.test.ts +1 -0
  38. package/src/parser/inline/math.ts +18 -9
  39. package/src/parser/inline/media.test.ts +3 -3
  40. package/src/parser/inline/media.ts +14 -6
  41. package/src/parser/inline/reference.ts +67 -10
  42. package/src/parser/inline/ruby.ts +6 -5
  43. package/src/parser/inline/shortmedia.ts +1 -1
  44. package/src/parser/inline.ts +4 -19
  45. package/src/parser/segment.test.ts +3 -2
  46. package/src/parser/segment.ts +2 -2
  47. package/src/parser/source/escapable.ts +1 -1
  48. package/src/parser/source/text.ts +2 -2
  49. package/src/parser/source/unescapable.ts +1 -1
  50. package/src/parser/util.ts +14 -4
  51. package/src/parser/visibility.ts +1 -0
@@ -1,12 +1,12 @@
1
1
  import { CodeParser } from '../inline';
2
- import { validate, getBacktrack, setBacktrack, match } from '../../combinator';
2
+ import { validate, isBacktrack, setBacktrack, match } from '../../combinator';
3
3
  import { Backtrack } from '../context';
4
4
  import { html } from 'typed-dom/dom';
5
5
 
6
6
  export const code: CodeParser = validate(
7
7
  ({ source, context }) =>
8
8
  source[0] === '`' &&
9
- !getBacktrack(context, [1 | Backtrack.bracket], source),
9
+ !isBacktrack(context, [1 | Backtrack.bracket], source),
10
10
  match(
11
11
  /^(`+)(?!`)([^\n]*?)(?:((?<!`)\1(?!`))|$|\n)/,
12
12
  ([whole, , body, closer]) => ({ source, context }) =>
@@ -5,50 +5,66 @@ import { union, inits, some, precedence, state, constraint, validate, surround,
5
5
  import { inline } from '../../inline';
6
6
  import { indexee, identity } from './indexee';
7
7
  import { unsafehtmlentity } from '../htmlentity';
8
- import { text, str } from '../../source';
8
+ import { txt, str } from '../../source';
9
9
  import { tightStart, trimBlankNodeEnd } from '../../visibility';
10
- import { unshift } from 'spica/array';
10
+ import { unshift, push } from 'spica/array';
11
11
  import { html, define, defrag } from 'typed-dom/dom';
12
12
 
13
13
  import IndexParser = ExtensionParser.IndexParser;
14
14
 
15
- export const index: IndexParser = lazy(() => constraint(State.index, false, fmap(indexee(surround(
16
- '[#',
17
- precedence(1, state(State.linkers | State.media,
15
+ export const index: IndexParser = lazy(() => constraint(State.index, fmap(indexee(surround(
16
+ str('[#'),
17
+ precedence(1, state(State.linkers,
18
18
  tightStart(
19
19
  some(inits([
20
20
  inline,
21
21
  signature,
22
22
  ]), ']', [[']', 1]])))),
23
- ']',
23
+ str(']'),
24
24
  false,
25
- ([, ns], rest, context) =>
26
- context.linebreak === undefined &&
27
- trimBlankNodeEnd(ns).length > 0
28
- ? [[html('a', { 'data-index': dataindex(ns) }, defrag(ns))], rest]
29
- : undefined,
30
- undefined,
25
+ ([as, bs, cs], rest, context) => {
26
+ if (context.linebreak === 0 && trimBlankNodeEnd(bs).length > 0) {
27
+ return [[html('a', { 'data-index': dataindex(bs) }, defrag(bs))], rest];
28
+ }
29
+ return (context.state! & State.linkers) === State.linkers
30
+ ? [push(push(unshift(as, bs), cs), ['']), rest]
31
+ : undefined;
32
+ },
33
+ ([as, bs], rest, context) => {
34
+ return (context.state! & State.linkers) === State.linkers
35
+ ? [push(unshift(as, bs), ['']), rest]
36
+ : undefined;
37
+ },
31
38
  [3 | Backtrack.bracket])),
32
- ([el]: [HTMLAnchorElement]) => [
33
- define(el,
34
- {
35
- id: el.id ? null : undefined,
36
- class: 'index',
37
- href: el.id ? `#${el.id}` : undefined,
38
- }),
39
- ])));
39
+ ns => {
40
+ if (ns.length === 1) {
41
+ const el = ns[0] as HTMLElement;
42
+ return [
43
+ define(el, {
44
+ id: el.id ? null : undefined,
45
+ class: 'index',
46
+ href: el.id ? `#${el.id}` : undefined,
47
+ })
48
+ ];
49
+ }
50
+ else {
51
+ assert(ns.at(-1) === '');
52
+ ns.pop();
53
+ return ns;
54
+ }
55
+ })));
40
56
 
41
57
  export const signature: IndexParser.SignatureParser = lazy(() => validate('|', surround(
42
58
  str(/^\|(?!\\?\s)/),
43
- tightStart(some(union([inline]), ']')),
59
+ tightStart(some(union([inline]), ']', [[']', 1]])),
44
60
  /^(?=])/,
45
61
  false,
46
62
  (_, rest, context) => {
47
63
  //context.offset ??= 0;
48
64
  //context.offset += rest.length;
49
- const sig = eval(parser({ source: context.recent![1], context }), []).join('');
65
+ const text = eval(sig({ source: context.recent![1], context }), []).join('');
50
66
  //context.offset -= rest.length;
51
- const index = identity('index', undefined, sig)?.slice(7);
67
+ const index = identity('index', undefined, text)?.slice(7);
52
68
  return index
53
69
  ? [[html('span', { class: 'indexer', 'data-index': index })], rest]
54
70
  : undefined;
@@ -57,7 +73,7 @@ export const signature: IndexParser.SignatureParser = lazy(() => validate('|', s
57
73
 
58
74
  export function dataindex(ns: readonly (string | HTMLElement)[]): string | undefined {
59
75
  if (ns.length === 0) return;
60
- for (let i = ns.length - 1; i >= 0; --i) {
76
+ for (let i = ns.length; i--;) {
61
77
  const node = ns[i];
62
78
  if (typeof node === 'string') return;
63
79
  if (i === ns.length - 1 && ['UL', 'OL'].includes(node.tagName)) continue;
@@ -66,7 +82,7 @@ export function dataindex(ns: readonly (string | HTMLElement)[]): string | undef
66
82
  }
67
83
  }
68
84
 
69
- const parser = some(union([
85
+ const sig: IndexParser.SignatureParser.InternalParser = some(union([
70
86
  unsafehtmlentity,
71
- text,
87
+ txt,
72
88
  ]));
@@ -5,7 +5,10 @@ import { define } from 'typed-dom/dom';
5
5
 
6
6
  export function indexee<P extends Parser<unknown, MarkdownParser.Context>>(parser: P): P;
7
7
  export function indexee(parser: Parser<HTMLElement, MarkdownParser.Context>): Parser<HTMLElement> {
8
- return fmap(parser, ([el], _, { id }) => [define(el, { id: identity('index', id, el), 'data-index': null })]);
8
+ return fmap(parser, (ns, _, { id }) =>
9
+ ns.length === 1
10
+ ? [define(ns[0], { id: identity('index', id, ns[0]), 'data-index': null })]
11
+ : ns);
9
12
  }
10
13
 
11
14
  const MAX = 60;
@@ -130,8 +133,8 @@ export function signature(source: Element | DocumentFragment): string {
130
133
  case 'label':
131
134
  el.replaceWith(`[$${el.getAttribute('data-label')!.replace('$', '')}]`);
132
135
  continue;
133
- case 'remark':
134
136
  case 'checkbox':
137
+ case 'remark':
135
138
  case 'annotation':
136
139
  case 'reference':
137
140
  el.remove();
@@ -163,8 +166,8 @@ export function text(source: Element | DocumentFragment): string {
163
166
  case 'math':
164
167
  el.replaceWith(el.getAttribute('data-src')!);
165
168
  continue;
166
- case 'remark':
167
169
  case 'checkbox':
170
+ case 'remark':
168
171
  case 'annotation':
169
172
  case 'reference':
170
173
  el.remove();
@@ -11,7 +11,7 @@ export const segment: ExtensionParser.LabelParser.SegmentParser = clear(union([
11
11
  body,
12
12
  ]));
13
13
 
14
- export const label: ExtensionParser.LabelParser = constraint(State.label, false, fmap(
14
+ export const label: ExtensionParser.LabelParser = constraint(State.label, fmap(
15
15
  union([
16
16
  surround('[', body, ']', false, undefined, undefined, [1 | Backtrack.bracket, 1]),
17
17
  body,
@@ -23,6 +23,8 @@ describe('Unit: parser/inline/html', () => {
23
23
  assert.deepStrictEqual(inspect(parser('<a,b>')), undefined);
24
24
  assert.deepStrictEqual(inspect(parser('<a, b>')), undefined);
25
25
  assert.deepStrictEqual(inspect(parser('<T>')), [['<span class="invalid">&lt;T&gt;</span>'], '']);
26
+ assert.deepStrictEqual(inspect(parser('<wbr/>')), undefined);
27
+ assert.deepStrictEqual(inspect(parser('<wbr />')), [['<span class="invalid">&lt;wbr /&gt;</span>'], '']);
26
28
  assert.deepStrictEqual(inspect(parser('<bdi>')), [['<span class="invalid">&lt;bdi&gt;</span>'], '']);
27
29
  assert.deepStrictEqual(inspect(parser('<bdi>z')), [['<span class="invalid">&lt;bdi&gt;z</span>'], '']);
28
30
  assert.deepStrictEqual(inspect(parser('<bdi></bdi>')), [['<span class="invalid">&lt;bdi&gt;&lt;/bdi&gt;</span>'], '']);
@@ -41,23 +43,29 @@ describe('Unit: parser/inline/html', () => {
41
43
  assert.deepStrictEqual(inspect(parser('<BDI>a</bdi>')), [['<span class="invalid">&lt;BDI&gt;</span>'], 'a</bdi>']);
42
44
  assert.deepStrictEqual(inspect(parser('</bdi>')), undefined);
43
45
  assert.deepStrictEqual(inspect(parser('<bdi/>')), undefined);
46
+ assert.deepStrictEqual(inspect(parser('<bdi />')), [['<span class="invalid">&lt;bdi /&gt;</span>'], '']);
44
47
  assert.deepStrictEqual(inspect(parser('<b><b><b>a</b></b></b>')), [['<span class="invalid">&lt;b&gt;<span class="invalid">&lt;b&gt;<span class="invalid">&lt;b&gt;a&lt;/b&gt;</span>&lt;/b&gt;</span>&lt;/b&gt;</span>'], '']);
45
48
  assert.deepStrictEqual(inspect(parser('<bdi><bdi><bdi>a</bdi></bdi></bdi>')), [['<bdi><bdi><bdi>a</bdi></bdi></bdi>'], '']);
46
- assert.deepStrictEqual(inspect(parser('<x a="*b*"')), undefined);
49
+ assert.deepStrictEqual(inspect(parser('<x a="*b*"')), [['<span class="invalid">&lt;x a="*b*"</span>'], '']);
47
50
  assert.deepStrictEqual(inspect(parser('<x a="*b*">')), [['<span class="invalid">&lt;x a="*b*"&gt;</span>'], '']);
48
51
  assert.deepStrictEqual(inspect(parser('<x a="*b*">c')), [['<span class="invalid">&lt;x a="*b*"&gt;</span>'], 'c']);
49
- assert.deepStrictEqual(inspect(parser('<bdi a="*b*"')), undefined);
52
+ assert.deepStrictEqual(inspect(parser('<bdi a="*b*"')), [['<span class="invalid">&lt;bdi a="*b*"</span>'], '']);
50
53
  assert.deepStrictEqual(inspect(parser('<bdi a="*b*">')), [['<span class="invalid">&lt;bdi a="*b*"&gt;</span>'], '']);
51
54
  assert.deepStrictEqual(inspect(parser('<bdi a="*b*">c')), [['<span class="invalid">&lt;bdi a="*b*"&gt;c</span>'], '']);
52
- assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*')), undefined);
53
- assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*>')), undefined);
54
- assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*>d</bdi>')), undefined);
55
- assert.deepStrictEqual(inspect(parser('<bdi a b="*" *>*c*')), undefined);
56
- assert.deepStrictEqual(inspect(parser('<bdi a b="*" *>*c*</bdi>')), undefined);
55
+ assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*')), [['<span class="invalid">&lt;bdi a b="*" *c*</span>'], '']);
56
+ assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*>')), [['<span class="invalid">&lt;bdi a b="*" *c*&gt;</span>'], '']);
57
+ assert.deepStrictEqual(inspect(parser('<bdi a b="*" *c*>d</bdi>')), [['<span class="invalid">&lt;bdi a b="*" *c*&gt;d&lt;/bdi&gt;</span>'], '']);
58
+ assert.deepStrictEqual(inspect(parser('<bdi a b="*" *>*c*')), [['<span class="invalid">&lt;bdi a b="*" *&gt;<em>c</em></span>'], '']);
59
+ assert.deepStrictEqual(inspect(parser('<bdi a b="*" *>*c*</bdi>')), [['<span class="invalid">&lt;bdi a b="*" *&gt;<em>c</em>&lt;/bdi&gt;</span>'], '']);
57
60
  assert.deepStrictEqual(inspect(parser(' <bdi>a</bdi>')), undefined);
58
61
  });
59
62
 
60
63
  it('basic', () => {
64
+ assert.deepStrictEqual(inspect(parser('<wbr>')), [['<wbr>'], '']);
65
+ assert.deepStrictEqual(inspect(parser('<wbr >')), [['<wbr>'], '']);
66
+ assert.deepStrictEqual(inspect(parser('<wbr>a')), [['<wbr>'], 'a']);
67
+ assert.deepStrictEqual(inspect(parser('<bdi >a</bdi>')), [['<bdi>a</bdi>'], '']);
68
+ assert.deepStrictEqual(inspect(parser('<bdi >a</bdi>')), [['<bdi>a</bdi>'], '']);
61
69
  assert.deepStrictEqual(inspect(parser('<bdi> a</bdi>')), [['<bdi> a</bdi>'], '']);
62
70
  assert.deepStrictEqual(inspect(parser('<bdi> a </bdi>')), [['<bdi> a </bdi>'], '']);
63
71
  assert.deepStrictEqual(inspect(parser('<bdi> a </bdi>')), [['<bdi> a </bdi>'], '']);
@@ -71,7 +79,6 @@ describe('Unit: parser/inline/html', () => {
71
79
  assert.deepStrictEqual(inspect(parser('<bdi>a\n </bdi>')), [['<bdi>a </bdi>'], '']);
72
80
  assert.deepStrictEqual(inspect(parser('<bdi>a\n<wbr></bdi>')), [['<bdi>a<wbr></bdi>'], '']);
73
81
  assert.deepStrictEqual(inspect(parser('<bdi>a\nb</bdi>')), [['<bdi>a<br>b</bdi>'], '']);
74
- assert.deepStrictEqual(inspect(parser('<wbr>a')), [['<wbr>'], 'a']);
75
82
  });
76
83
 
77
84
  it('nest', () => {
@@ -87,31 +94,27 @@ describe('Unit: parser/inline/html', () => {
87
94
  assert.deepStrictEqual(inspect(parser('<bdi>a<a>b</a>c</bdi>')), [['<bdi>a<span class="invalid">&lt;a&gt;b&lt;/a&gt;</span>c</bdi>'], '']);
88
95
  assert.deepStrictEqual(inspect(parser('<img>')), [['<span class="invalid">&lt;img&gt;</span>'], '']);
89
96
  assert.deepStrictEqual(inspect(parser('<bdi><img></bdi>')), [['<bdi><span class="invalid">&lt;img&gt;</span></bdi>'], '']);
90
- assert.deepStrictEqual(inspect(parser('<img />')), undefined);
91
- assert.deepStrictEqual(inspect(parser('<bdi><img /></bdi>')), [['<bdi>&lt;img /&gt;</bdi>'], '']);
92
97
  });
93
98
 
94
99
  it('attribute', () => {
95
100
  assert.deepStrictEqual(inspect(parser('<bdi\n>a</bdi>')), undefined);
96
- assert.deepStrictEqual(inspect(parser('<bdi >a</bdi>')), [['<bdi>a</bdi>'], '']);
97
- assert.deepStrictEqual(inspect(parser('<bdi \n>a</bdi>')), undefined);
98
- assert.deepStrictEqual(inspect(parser('<bdi >a</bdi>')), [['<bdi>a</bdi>'], '']);
99
- assert.deepStrictEqual(inspect(parser('<bdi __proto__>a</bdi>')), undefined);
101
+ assert.deepStrictEqual(inspect(parser('<bdi \n>a</bdi>')), [['<span class="invalid">&lt;bdi <br>&gt;a&lt;/bdi&gt;</span>'], '']);
102
+ assert.deepStrictEqual(inspect(parser('<bdi __proto__>a</bdi>')), [['<span class="invalid">&lt;bdi __proto__&gt;a&lt;/bdi&gt;</span>'], '']);
100
103
  assert.deepStrictEqual(inspect(parser('<bdi constructor>a</bdi>')), [['<span class="invalid">&lt;bdi constructor&gt;a&lt;/bdi&gt;</span>'], '']);
101
104
  assert.deepStrictEqual(inspect(parser('<bdi toString>a</bdi>')), [['<span class="invalid">&lt;bdi toString&gt;a&lt;/bdi&gt;</span>'], '']);
102
105
  assert.deepStrictEqual(inspect(parser('<bdi X>a</bdi>')), [['<span class="invalid">&lt;bdi X&gt;a&lt;/bdi&gt;</span>'], '']);
103
106
  assert.deepStrictEqual(inspect(parser('<bdi x>a</bdi>')), [['<span class="invalid">&lt;bdi x&gt;a&lt;/bdi&gt;</span>'], '']);
104
107
  assert.deepStrictEqual(inspect(parser('<bdo>a</bdo>')), [['<span class="invalid">&lt;bdo&gt;a&lt;/bdo&gt;</span>'], '']);
105
108
  assert.deepStrictEqual(inspect(parser('<bdo >a</bdo>')), [['<span class="invalid">&lt;bdo &gt;a&lt;/bdo&gt;</span>'], '']);
106
- assert.deepStrictEqual(inspect(parser('<bdo __proto__>a</bdo>')), undefined);
109
+ assert.deepStrictEqual(inspect(parser('<bdo __proto__>a</bdo>')), [['<span class="invalid">&lt;bdo __proto__&gt;a&lt;/bdo&gt;</span>'], '']);
107
110
  assert.deepStrictEqual(inspect(parser('<bdo constructor>a</bdo>')), [['<span class="invalid">&lt;bdo constructor&gt;a&lt;/bdo&gt;</span>'], '']);
108
111
  assert.deepStrictEqual(inspect(parser('<bdo toString>a</bdo>')), [['<span class="invalid">&lt;bdo toString&gt;a&lt;/bdo&gt;</span>'], '']);
109
112
  assert.deepStrictEqual(inspect(parser('<bdo X>a</bdo>')), [['<span class="invalid">&lt;bdo X&gt;a&lt;/bdo&gt;</span>'], '']);
110
113
  assert.deepStrictEqual(inspect(parser('<bdo x>a</bdo>')), [['<span class="invalid">&lt;bdo x&gt;a&lt;/bdo&gt;</span>'], '']);
111
114
  assert.deepStrictEqual(inspect(parser('<bdo dir>a</bdo>')), [['<span class="invalid">&lt;bdo dir&gt;a&lt;/bdo&gt;</span>'], '']);
112
- assert.deepStrictEqual(inspect(parser('<bdo dir=>a</bdo>')), undefined);
113
- assert.deepStrictEqual(inspect(parser('<bdo dir=rtl>a</bdo>')), undefined);
114
- assert.deepStrictEqual(inspect(parser('<bdo dir=">a</bdo>')), undefined);
115
+ assert.deepStrictEqual(inspect(parser('<bdo dir=>a</bdo>')), [['<span class="invalid">&lt;bdo dir=&gt;a&lt;/bdo&gt;</span>'], '']);
116
+ assert.deepStrictEqual(inspect(parser('<bdo dir=rtl>a</bdo>')), [['<span class="invalid">&lt;bdo dir=rtl&gt;a&lt;/bdo&gt;</span>'], '']);
117
+ assert.deepStrictEqual(inspect(parser('<bdo dir=">a</bdo>')), [['<span class="invalid">&lt;bdo dir="&gt;a&lt;/bdo&gt;</span>'], '']);
115
118
  assert.deepStrictEqual(inspect(parser('<bdo dir="">a</bdo>')), [['<span class="invalid">&lt;bdo dir=""&gt;a&lt;/bdo&gt;</span>'], '']);
116
119
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl" dir="rtl">a</bdo>')), [['<span class="invalid">&lt;bdo dir="rtl" dir="rtl"&gt;a&lt;/bdo&gt;</span>'], '']);
117
120
  assert.deepStrictEqual(inspect(parser('<bdo diR="rtl">a</bdo>')), [['<span class="invalid">&lt;bdo diR="rtl"&gt;a&lt;/bdo&gt;</span>'], '']);
@@ -120,7 +123,7 @@ describe('Unit: parser/inline/html', () => {
120
123
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl" >a</bdo>')), [['<bdo dir="rtl">a</bdo>'], '']);
121
124
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl">a</bdo>')), [['<bdo dir="rtl">a</bdo>'], '']);
122
125
  assert.deepStrictEqual(inspect(parser('<wbr\n>')), undefined);
123
- assert.deepStrictEqual(inspect(parser('<wbr >')), [['<wbr>'], '']);
126
+ assert.deepStrictEqual(inspect(parser('<wbr \n>')), [['<span class="invalid">&lt;wbr </span>'], '\n>']);
124
127
  assert.deepStrictEqual(inspect(parser('<wbr constructor>')), [['<span class="invalid">&lt;wbr constructor&gt;</span>'], '']);
125
128
  assert.deepStrictEqual(inspect(parser('<wbr X>')), [['<span class="invalid">&lt;wbr X&gt;</span>'], '']);
126
129
  assert.deepStrictEqual(inspect(parser('<wbr x>')), [['<span class="invalid">&lt;wbr x&gt;</span>'], '']);
@@ -1,5 +1,5 @@
1
1
  import { HTMLParser } from '../inline';
2
- import { Recursion, Backtrack } from '../context';
2
+ import { Recursion } from '../context';
3
3
  import { union, subsequence, some, recursion, precedence, validate, focus, surround, open, match, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
@@ -9,8 +9,9 @@ import { memoize } from 'spica/memoize';
9
9
  import { unshift, push, splice } from 'spica/array';
10
10
  import { html as h, defrag } from 'typed-dom/dom';
11
11
 
12
- const tags = Object.freeze(['bdo', 'bdi']);
12
+ const tags = Object.freeze(['wbr', 'bdo', 'bdi']);
13
13
  const attrspecs = {
14
+ wbr: {},
14
15
  bdo: {
15
16
  dir: Object.freeze(['ltr', 'rtl']),
16
17
  },
@@ -24,48 +25,110 @@ export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i,
24
25
  // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
25
26
  str(/^<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[^\S\n]|>)/i),
26
27
  some(union([attribute])),
27
- str(/^[^\S\n]*>/), true,
28
+ open(str(/^[^\S\n]*/), str('>'), true),
29
+ true,
28
30
  ([as, bs = [], cs], rest) =>
29
- as[0].slice(1) === 'wbr' && bs.length === 0
30
- ? [[h(as[0].slice(1) as 'wbr')], rest]
31
- : [[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest],
32
- undefined,
33
- [3 | Backtrack.bracket]),
31
+ [[elem(as[0].slice(1), false, push(unshift(as, bs), cs), [], [])], rest],
32
+ ([as, bs = []], rest) =>
33
+ [[elem(as[0].slice(1), false, unshift(as, bs), [], [])], rest]),
34
34
  match(
35
35
  new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`),
36
36
  memoize(
37
37
  ([, tag]) =>
38
38
  surround<HTMLParser.TagParser, string>(
39
39
  surround(
40
- str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true,
41
- undefined, undefined, [3 | Backtrack.bracket]),
40
+ str(`<${tag}`), some(attribute), open(str(/^[^\S\n]*/), str('>'), true),
41
+ true,
42
+ ([as, bs = [], cs], rest) => [push(unshift(as, bs), cs), rest],
43
+ ([as, bs = []], rest) => [unshift(as, bs), rest]),
42
44
  precedence(3, recursion(Recursion.inline,
43
45
  subsequence([
44
46
  focus(/^[^\S\n]*\n/, some(inline)),
45
47
  some(open(/^\n?/, some(inline, blankWith('\n', `</${tag}>`), [[blankWith('\n', `</${tag}>`), 3]]), true)),
46
48
  ]))),
47
- str(`</${tag}>`), true,
49
+ str(`</${tag}>`),
50
+ true,
48
51
  ([as, bs = [], cs], rest) =>
49
- [[elem(tag, as, bs, cs)], rest],
52
+ [[elem(tag, true, as, bs, cs)], rest],
50
53
  ([as, bs = []], rest) =>
51
- [[elem(tag, as, bs, [])], rest]),
54
+ [[elem(tag, true, as, bs, [])], rest]),
52
55
  ([, tag]) => tag,
53
56
  new Map())),
54
57
  surround(
55
58
  // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
56
- str(/^<[a-z]+(?=[^\S\n]|>)/i),
59
+ str(/^<[a-z]{1,16}(?=[^\S\n]|>)/i),
57
60
  some(union([attribute])),
58
- str(/^[^\S\n]*>/), true,
61
+ open(str(/^[^\S\n]*/), str('>'), true),
62
+ true,
59
63
  ([as, bs = [], cs], rest) =>
60
- [[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest],
61
- undefined,
62
- [3 | Backtrack.bracket]),
64
+ [[elem(as[0].slice(1), false, push(unshift(as, bs), cs), [], [])], rest],
65
+ ([as, bs = []], rest) =>
66
+ [[elem(as[0].slice(1), false, unshift(as, bs), [], [])], rest]),
63
67
  ])));
64
68
 
65
69
  export const attribute: HTMLParser.AttributeParser = union([
66
- str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="[^"\n]*")?(?=[^\S\n]|>)/i),
70
+ str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|>)/i),
71
+ str(/^[^\S\n]+[^\s<>]+/),
67
72
  ]);
68
73
 
74
+ function elem(tag: string, content: boolean, as: string[], bs: (HTMLElement | string)[], cs: string[]): HTMLElement {
75
+ assert(as.length > 0);
76
+ assert(as[0][0] === '<');
77
+ if (!tags.includes(tag)) return ielem('tag', `Invalid HTML tag name "${tag}"`, as, bs, cs);
78
+ if (content) {
79
+ if (cs.length === 0) return ielem('tag', `Missing the closing HTML tag "</${tag}>"`, as, bs, cs);
80
+ if (bs.length === 0) return ielem('content', `Missing the content`, as, bs, cs);
81
+ if (!isLooseNodeStart(bs)) return ielem('content', `Missing the visible content in the same line`, as, bs, cs);
82
+ }
83
+ const attrs = attributes('html', attrspecs[tag], as.slice(1, as.at(-1) === '>' ? -2 : as.length));
84
+ if (/(?<!\S)invalid(?!\S)/.test(attrs['class'] ?? '')) return ielem('attribute', 'Invalid HTML attribute', as, bs, cs)
85
+ if (as.at(-1) !== '>') return ielem('tag', `Missing the closing bracket ">"`, as, bs, cs);
86
+ return h(tag as 'span', attrs, defrag(bs));
87
+ }
88
+
89
+ function ielem(type: string, message: string, as: (HTMLElement | string)[], bs: (HTMLElement | string)[], cs: (HTMLElement | string)[]): HTMLElement {
90
+ return h('span',
91
+ { class: 'invalid', ...invalid('html', type, message) },
92
+ defrag(push(unshift(as, bs), cs)));
93
+ }
94
+
95
+ const requiredAttributes = memoize(
96
+ (spec: Readonly<Record<string, readonly (string | undefined)[] | undefined>>) =>
97
+ Object.entries(spec).flatMap(([k, v]) => v && Object.isFrozen(v) ? [k] : []),
98
+ new WeakMap());
99
+
100
+ export function attributes(
101
+ syntax: string,
102
+ spec: Readonly<Record<string, readonly (string | undefined)[] | undefined>> | undefined,
103
+ params: string[],
104
+ ): Record<string, string | undefined> {
105
+ assert(spec instanceof Object === false);
106
+ assert(!spec?.['__proto__']);
107
+ assert(!spec?.toString);
108
+ let invalidation = false;
109
+ const attrs: Record<string, string | undefined> = {};
110
+ for (let i = 0; i < params.length; ++i) {
111
+ const param = params[i].trimStart();
112
+ const name = param.split('=', 1)[0];
113
+ const value = param !== name
114
+ ? param.slice(name.length + 2, -1).replace(/\\(.?)/g, '$1')
115
+ : undefined;
116
+ invalidation ||= !spec || name in attrs;
117
+ if (spec && name in spec && !spec[name]) continue;
118
+ spec?.[name]?.includes(value) || spec?.[name]?.length === 0 && value !== undefined
119
+ ? attrs[name] = value ?? ''
120
+ : invalidation ||= !!spec;
121
+ assert(!(name in {} && attrs.hasOwnProperty(name)));
122
+ splice(params, i--, 1);
123
+ }
124
+ invalidation ||= !!spec && !requiredAttributes(spec).every(name => name in attrs);
125
+ if (invalidation) {
126
+ attrs['class'] = 'invalid';
127
+ Object.assign(attrs, invalid(syntax, 'argument', 'Invalid argument'));
128
+ }
129
+ return attrs;
130
+ }
131
+
69
132
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
70
133
  // [...document.querySelectorAll('tbody > tr > td:first-child')].map(el => el.textContent.slice(1, -1))
71
134
  const TAGS = Object.freeze([
@@ -206,62 +269,3 @@ const TAGS = Object.freeze([
206
269
  "tt",
207
270
  "xmp",
208
271
  ]);
209
-
210
- function elem(tag: string, as: string[], bs: (HTMLElement | string)[], cs: string[]): HTMLElement {
211
- assert(as.length > 0);
212
- assert(as[0][0] === '<' && as.at(-1)!.slice(-1) === '>');
213
- if (!tags.includes(tag)) return ielem('tag', `Invalid HTML tag name "${tag}"`, as, bs, cs);
214
- if (cs.length === 0) return ielem('tag', `Missing the closing HTML tag "</${tag}>"`, as, bs, cs);
215
- if (bs.length === 0) return ielem('content', `Missing the content`, as, bs, cs);
216
- if (!isLooseNodeStart(bs)) return ielem('content', `Missing the visible content in the same line`, as, bs, cs);
217
- const attrs = attributes('html', [], attrspecs[tag], as.slice(1, -1));
218
- return /(?<!\S)invalid(?!\S)/.test(attrs['class'] ?? '')
219
- ? ielem('attribute', 'Invalid HTML attribute', as, bs, cs)
220
- : h(tag as 'span', attrs, defrag(bs));
221
- }
222
-
223
- function ielem(type: string, message: string, as: (HTMLElement | string)[], bs: (HTMLElement | string)[], cs: (HTMLElement | string)[]): HTMLElement {
224
- return h('span',
225
- { class: 'invalid', ...invalid('html', type, message) },
226
- defrag(push(unshift(as, bs), cs)));
227
- }
228
-
229
- const requiredAttributes = memoize(
230
- (spec: Readonly<Record<string, readonly (string | undefined)[] | undefined>>) =>
231
- Object.entries(spec).flatMap(([k, v]) => v && Object.isFrozen(v) ? [k] : []),
232
- new WeakMap());
233
-
234
- export function attributes(
235
- syntax: string,
236
- classes: readonly string[],
237
- spec: Readonly<Record<string, readonly (string | undefined)[] | undefined>> | undefined,
238
- params: string[],
239
- ): Record<string, string | undefined> {
240
- assert(spec instanceof Object === false);
241
- assert(!spec?.['__proto__']);
242
- assert(!spec?.toString);
243
- let invalidation = false;
244
- const attrs: Record<string, string | undefined> = {};
245
- for (let i = 0; i < params.length; ++i) {
246
- const param = params[i].trim();
247
- const name = param.split('=', 1)[0];
248
- const value = param !== name
249
- ? param.slice(name.length + 2, -1).replace(/\\(.?)/g, '$1')
250
- : undefined;
251
- invalidation ||= !spec || name in attrs;
252
- if (spec && name in spec && !spec[name]) continue;
253
- spec?.[name]?.includes(value) || spec?.[name]?.length === 0 && value !== undefined
254
- ? attrs[name] = value ?? ''
255
- : invalidation ||= !!spec;
256
- assert(!(name in {} && attrs.hasOwnProperty(name)));
257
- splice(params, i--, 1);
258
- }
259
- invalidation ||= !!spec && !requiredAttributes(spec).every(name => name in attrs);
260
- if (invalidation) {
261
- attrs['class'] = classes.length === 0
262
- ? 'invalid'
263
- : `${classes.join(' ')}${classes.includes('invalid') ? '' : ' invalid'}`;
264
- Object.assign(attrs, invalid(syntax, 'argument', 'Invalid argument'));
265
- }
266
- return attrs;
267
- }
@@ -16,8 +16,8 @@ const optspec = {
16
16
  } as const;
17
17
  Object.setPrototypeOf(optspec, null);
18
18
 
19
- export const textlink: LinkParser.TextLinkParser = lazy(() => constraint(State.link, false, creation(10,
20
- precedence(1, state(State.linkers | State.media,
19
+ export const textlink: LinkParser.TextLinkParser = lazy(() => constraint(State.link, creation(10,
20
+ precedence(1, state(State.linkers,
21
21
  bind(subsequence([
22
22
  dup(surround(
23
23
  '[',
@@ -25,11 +25,13 @@ export const textlink: LinkParser.TextLinkParser = lazy(() => constraint(State.l
25
25
  ']',
26
26
  true,
27
27
  ([, ns = []], rest, context) =>
28
- context.linebreak === undefined
28
+ context.linebreak === 0
29
29
  ? [push(ns, [Command.Escape]), rest]
30
30
  : undefined,
31
31
  undefined,
32
- [3 | Backtrack.link, 3 | Backtrack.bracket])),
32
+ [3 | Backtrack.link, 2 | Backtrack.ruby, 3 | Backtrack.bracket])),
33
+ // `{ `と`{`で個別にバックトラックが発生し+1nされる。
34
+ // 自己再帰的にパースしてもオプションの不要なパースによる計算量の増加により相殺される。
33
35
  dup(surround(
34
36
  /^{(?![{}])/,
35
37
  inits([uri, some(option)]),
@@ -41,7 +43,8 @@ export const textlink: LinkParser.TextLinkParser = lazy(() => constraint(State.l
41
43
  if (content.at(-1) === Command.Escape) {
42
44
  content.pop();
43
45
  if (params === undefined) {
44
- return void setBacktrack(context, [2 | Backtrack.link], context.recent!.reduce((a, b) => a + b.length, 0));
46
+ const head = context.recent!.reduce((a, b) => a + b.length, rest.length);
47
+ return void setBacktrack(context, [2 | Backtrack.link], head);
45
48
  }
46
49
  }
47
50
  else {
@@ -54,7 +57,7 @@ export const textlink: LinkParser.TextLinkParser = lazy(() => constraint(State.l
54
57
  return [[parse(defrag(content), params, context)], rest];
55
58
  }))))));
56
59
 
57
- export const medialink: LinkParser.MediaLinkParser = lazy(() => constraint(State.link | State.media, false, validate(['[', '{'], creation(10,
60
+ export const medialink: LinkParser.MediaLinkParser = lazy(() => constraint(State.link | State.media, validate(['[', '{'], creation(10,
58
61
  state(State.linkers,
59
62
  bind(reverse(sequence([
60
63
  dup(surround(
@@ -90,8 +93,8 @@ export const uri: LinkParser.ParameterParser.UriParser = union([
90
93
 
91
94
  export const option: LinkParser.ParameterParser.OptionParser = union([
92
95
  fmap(str(/^[^\S\n]+nofollow(?=[^\S\n]|})/), () => [` rel="nofollow"`]),
93
- str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|})/),
94
- fmap(str(/^[^\S\n]+[^\s{}]+/), opt => [` \\${opt.slice(1)}`]),
96
+ str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|})/i),
97
+ str(/^[^\S\n]+[^\s{}]+/),
95
98
  ]);
96
99
 
97
100
  function parse(
@@ -119,7 +122,7 @@ function parse(
119
122
  context.host?.origin || location.origin);
120
123
  return el.classList.contains('invalid')
121
124
  ? el
122
- : define(el, attributes('link', [], optspec, params));
125
+ : define(el, attributes('link', optspec, params));
123
126
  }
124
127
 
125
128
  function elem(
@@ -8,7 +8,7 @@ import { repeat } from '../util';
8
8
  import { push } from 'spica/array';
9
9
  import { html, define, defrag } from 'typed-dom/dom';
10
10
 
11
- export const mark: MarkParser = lazy(() => constraint(State.linkers & ~State.mark, false, validate('==',
11
+ export const mark: MarkParser = lazy(() => constraint(State.linkers & ~State.mark, validate('==',
12
12
  precedence(0, state(State.mark, repeat('==', surround(
13
13
  '',
14
14
  recursion(Recursion.inline,
@@ -83,6 +83,7 @@ describe('Unit: parser/inline/math', () => {
83
83
  assert.deepStrictEqual(inspect(parser('$\\huge0$')), [['<span class="invalid" translate="no">$\\huge0$</span>'], '']);
84
84
  assert.deepStrictEqual(inspect(parser('$\\Begin{}$')), [['<span class="invalid" translate="no">$\\Begin{}$</span>'], '']);
85
85
  assert.deepStrictEqual(inspect(parser('${\\begin}$')), [['<span class="invalid" translate="no">${\\begin}$</span>'], '']);
86
+ assert.deepStrictEqual(inspect(parser('$http://host$')), undefined);
86
87
  assert.deepStrictEqual(inspect(parser(' ${a}$')), undefined);
87
88
  });
88
89
 
@@ -1,7 +1,7 @@
1
1
  import { MathParser } from '../inline';
2
- import { Recursion } from '../context';
2
+ import { Backtrack, Recursion } from '../context';
3
3
  import { union, some, recursion, precedence, validate, focus, rewrite, surround, lazy } from '../../combinator';
4
- import { escsource, unescsource, str } from '../source';
4
+ import { escsource, str } from '../source';
5
5
  import { invalid } from '../util';
6
6
  import { html } from 'typed-dom/dom';
7
7
 
@@ -9,14 +9,23 @@ const forbiddenCommand = /\\(?:begin|tiny|huge|large)(?![a-z])/i;
9
9
 
10
10
  export const math: MathParser = lazy(() => validate('$', rewrite(
11
11
  union([
12
- surround('$', precedence(5, bracket), '$'),
12
+ surround(
13
+ /^\$(?={)/,
14
+ precedence(5, bracket),
15
+ '$',
16
+ false, undefined, undefined, [3 | Backtrack.bracket]),
13
17
  surround(
14
18
  /^\$(?![\s{}])/,
15
19
  precedence(2, some(union([
16
- bracket,
17
- focus(/^(?:[ ([](?!\$)|\\[\\{}$]?|[!#%&')\x2A-\x5A\]^_\x61-\x7A|~])+/, some(unescsource)),
20
+ precedence(5, bracket),
21
+ some(focus(
22
+ /^(?:[ ([](?!\$)|\\[\\{}$#]?|[!%&')\x2A-\x5A\]^_\x61-\x7A|~])/,
23
+ escsource,
24
+ false),
25
+ '://'),
18
26
  ]))),
19
- /^\$(?![0-9A-Za-z])/),
27
+ /^\$(?![0-9A-Za-z])/,
28
+ false, undefined, undefined, [3 | Backtrack.bracket]),
20
29
  ]),
21
30
  ({ source, context: { caches: { math: cache } = {} } }) => [[
22
31
  cache?.get(source)?.cloneNode(true) ||
@@ -26,8 +35,8 @@ export const math: MathParser = lazy(() => validate('$', rewrite(
26
35
  : {
27
36
  class: 'invalid',
28
37
  translate: 'no',
29
- ...invalid('math', 'content',
30
- `"${source.match(forbiddenCommand)![0]}" command is forbidden`),
38
+ ...invalid('math', 'content',
39
+ `"${source.match(forbiddenCommand)![0]}" command is forbidden`),
31
40
  },
32
41
  source)
33
42
  ], ''])));
@@ -37,7 +46,7 @@ const bracket: MathParser.BracketParser = lazy(() => surround(
37
46
  recursion(Recursion.terminal,
38
47
  some(union([
39
48
  bracket,
40
- some(escsource, /^[{}$\n]/),
49
+ some(escsource, /^[{}$#\n]/),
41
50
  ]))),
42
51
  str('}'),
43
52
  true));