securemark 0.217.0 → 0.218.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 (54) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/securemark.js +166 -143
  3. package/markdown.d.ts +7 -6
  4. package/package-lock.json +7 -7
  5. package/package.json +1 -1
  6. package/src/parser/api/bind.test.ts +5 -5
  7. package/src/parser/api/bind.ts +2 -2
  8. package/src/parser/api/parse.test.ts +18 -0
  9. package/src/parser/api/parse.ts +2 -2
  10. package/src/parser/block/blockquote.test.ts +1 -1
  11. package/src/parser/block/extension/example.test.ts +1 -1
  12. package/src/parser/block/extension/fig.ts +1 -1
  13. package/src/parser/block/extension/table.ts +2 -2
  14. package/src/parser/block/heading.test.ts +10 -12
  15. package/src/parser/block/ilist.ts +8 -1
  16. package/src/parser/block/paragraph.test.ts +7 -0
  17. package/src/parser/function/footnote.test.ts +16 -16
  18. package/src/parser/function/footnote.ts +1 -1
  19. package/src/parser/inline/annotation.test.ts +5 -2
  20. package/src/parser/inline/annotation.ts +4 -5
  21. package/src/parser/inline/comment.ts +1 -1
  22. package/src/parser/inline/deletion.test.ts +1 -0
  23. package/src/parser/inline/deletion.ts +2 -1
  24. package/src/parser/inline/emphasis.ts +5 -5
  25. package/src/parser/inline/emstrong.ts +4 -4
  26. package/src/parser/inline/extension/index.test.ts +25 -7
  27. package/src/parser/inline/extension/index.ts +17 -14
  28. package/src/parser/inline/extension/indexee.ts +1 -1
  29. package/src/parser/inline/extension/indexer.test.ts +2 -0
  30. package/src/parser/inline/extension/indexer.ts +5 -3
  31. package/src/parser/inline/extension/placeholder.test.ts +10 -10
  32. package/src/parser/inline/extension/placeholder.ts +13 -9
  33. package/src/parser/inline/html.test.ts +2 -1
  34. package/src/parser/inline/html.ts +16 -22
  35. package/src/parser/inline/insertion.test.ts +1 -0
  36. package/src/parser/inline/insertion.ts +2 -1
  37. package/src/parser/inline/link.test.ts +3 -7
  38. package/src/parser/inline/link.ts +9 -9
  39. package/src/parser/inline/mark.test.ts +1 -0
  40. package/src/parser/inline/mark.ts +2 -2
  41. package/src/parser/inline/math.ts +2 -2
  42. package/src/parser/inline/media.test.ts +3 -7
  43. package/src/parser/inline/media.ts +5 -5
  44. package/src/parser/inline/reference.test.ts +24 -9
  45. package/src/parser/inline/reference.ts +9 -9
  46. package/src/parser/inline/ruby.test.ts +2 -1
  47. package/src/parser/inline/ruby.ts +12 -10
  48. package/src/parser/inline/strong.ts +5 -5
  49. package/src/parser/segment.ts +11 -17
  50. package/src/parser/source/escapable.ts +1 -0
  51. package/src/parser/source/str.ts +3 -5
  52. package/src/parser/source/text.ts +8 -6
  53. package/src/parser/source/unescapable.ts +1 -0
  54. package/src/parser/util.ts +85 -60
@@ -27,19 +27,13 @@ describe('Unit: parser/inline/media', () => {
27
27
  assert.deepStrictEqual(inspect(parser('![]{ }')), undefined);
28
28
  assert.deepStrictEqual(inspect(parser('![]{ }')), undefined);
29
29
  assert.deepStrictEqual(inspect(parser('![]{ }')), undefined);
30
- assert.deepStrictEqual(inspect(parser('![]{/ }')), [['<a href="/" target="_blank"><img class="media invalid" data-src="/" alt=""></a>'], '']);
31
- assert.deepStrictEqual(inspect(parser('![]{/ /}')), [['<a href="/" target="_blank"><img class="media invalid" data-src="/" alt=""></a>'], '']);
32
30
  assert.deepStrictEqual(inspect(parser('![]]{/}')), undefined);
33
31
  assert.deepStrictEqual(inspect(parser('![]{{}')), undefined);
34
32
  assert.deepStrictEqual(inspect(parser('![]{{a}}')), undefined);
35
- assert.deepStrictEqual(inspect(parser('![]{a }')), [['<a href="a" target="_blank"><img class="media invalid" data-src="a" alt=""></a>'], '']);
36
33
  assert.deepStrictEqual(inspect(parser('![]{a\nb}')), undefined);
37
34
  assert.deepStrictEqual(inspect(parser('![]{a\\\nb}')), undefined);
38
35
  assert.deepStrictEqual(inspect(parser('![]{ a}')), undefined);
39
- assert.deepStrictEqual(inspect(parser('![]{ a }')), [['<a href="a" target="_blank"><img class="media invalid" data-src="a" alt=""></a>'], '']);
40
36
  assert.deepStrictEqual(inspect(parser('![]{ a\n}')), undefined);
41
- assert.deepStrictEqual(inspect(parser('![]{\ta }')), undefined);
42
- assert.deepStrictEqual(inspect(parser('![]{\ta\t}')), undefined);
43
37
  assert.deepStrictEqual(inspect(parser('![ ]{#}')), undefined);
44
38
  assert.deepStrictEqual(inspect(parser('![ ]{#}')), undefined);
45
39
  assert.deepStrictEqual(inspect(parser('![\\ ]{#}')), undefined);
@@ -48,7 +42,6 @@ describe('Unit: parser/inline/media', () => {
48
42
  assert.deepStrictEqual(inspect(parser('![ a]{#}')), undefined);
49
43
  assert.deepStrictEqual(inspect(parser('![ a ]{#}')), undefined);
50
44
  assert.deepStrictEqual(inspect(parser('![\\ a ]{#}')), undefined);
51
- assert.deepStrictEqual(inspect(parser('![a ]{#}')), undefined);
52
45
  assert.deepStrictEqual(inspect(parser('![a\nb]{#}')), undefined);
53
46
  assert.deepStrictEqual(inspect(parser('![a\\\nb]{#}')), undefined);
54
47
  assert.deepStrictEqual(inspect(parser('![]{ttp://host}')), [['<img class="media invalid" data-src="ttp://host" alt="">'], '']);
@@ -62,13 +55,16 @@ describe('Unit: parser/inline/media', () => {
62
55
  it('basic', () => {
63
56
  assert.deepStrictEqual(inspect(parser('![]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
64
57
  assert.deepStrictEqual(inspect(parser('![]{b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
58
+ assert.deepStrictEqual(inspect(parser('![]{b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
65
59
  assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
60
+ assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
66
61
  assert.deepStrictEqual(inspect(parser('![]{\\}')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
67
62
  assert.deepStrictEqual(inspect(parser('![]{\\ }')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
68
63
  assert.deepStrictEqual(inspect(parser('![]{\\b}')), [['<a href="\\b" target="_blank"><img class="media" data-src="\\b" alt=""></a>'], '']);
69
64
  assert.deepStrictEqual(inspect(parser('![]{./b}')), [['<a href="./b" target="_blank"><img class="media" data-src="./b" alt=""></a>'], '']);
70
65
  assert.deepStrictEqual(inspect(parser('![]{^/b}')), [[`<a href="/b" target="_blank"><img class="media" data-src="/b" alt=""></a>`], '']);
71
66
  assert.deepStrictEqual(inspect(parser('![a ]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
67
+ assert.deepStrictEqual(inspect(parser('![a ]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
72
68
  assert.deepStrictEqual(inspect(parser('![a b]{c}')), [['<a href="c" target="_blank"><img class="media" data-src="c" alt="a b"></a>'], '']);
73
69
  assert.deepStrictEqual(inspect(parser('!{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
74
70
  assert.deepStrictEqual(inspect(parser('!{ ][ }')), [['<a href="][" target="_blank"><img class="media" data-src="][" alt=""></a>'], '']);
@@ -6,7 +6,7 @@ import { link, uri, option as linkoption, resolve } from './link';
6
6
  import { attributes } from './html';
7
7
  import { htmlentity } from './htmlentity';
8
8
  import { txt, str } from '../source';
9
- import { isStartTight, isEndTight } from '../util';
9
+ import { verifyStartTight } from '../util';
10
10
  import { html, define } from 'typed-dom';
11
11
  import { ReadonlyURL } from 'spica/url';
12
12
  import { unshift, push, join } from 'spica/array';
@@ -25,10 +25,10 @@ export const media: MediaParser = lazy(() => creator(10, bind(verify(fmap(open(
25
25
  guard(context => context.syntax?.inline?.media ?? true,
26
26
  tails([
27
27
  dup(surround(/^\[(?!\\?\s)/, some(union([htmlentity, bracket, txt]), ']', /^\\?\n/), ']', true)),
28
- dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^ ?}/)),
28
+ dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]?}/)),
29
29
  ])))),
30
30
  ([as, bs]) => bs ? [[join(as)], bs] : [[''], as]),
31
- ([[text]]) => isStartTight([text || '-']) && isEndTight([text])),
31
+ ([[text]]) => verifyStartTight([text || '-'])),
32
32
  ([[text], params], rest, context) => {
33
33
  const INSECURE_URI = params.shift()!;
34
34
  assert(INSECURE_URI === INSECURE_URI.trim());
@@ -60,8 +60,8 @@ const bracket: MediaParser.TextParser.BracketParser = lazy(() => union([
60
60
  ]));
61
61
 
62
62
  const option: MediaParser.ParameterParser.OptionParser = union([
63
- fmap(str(/^ [1-9][0-9]*x[1-9][0-9]*(?=[ }])/), ([opt]) => [` width="${opt.slice(1).split('x')[0]}"`, ` height="${opt.slice(1).split('x')[1]}"`]),
64
- fmap(str(/^ [1-9][0-9]*:[1-9][0-9]*(?=[ }])/), ([opt]) => [` aspect-ratio="${opt.slice(1).split(':').join('/')}"`]),
63
+ fmap(str(/^[^\S\n]+[1-9][0-9]*x[1-9][0-9]*(?=[^\S\n]|})/), ([opt]) => [` width="${opt.slice(1).split('x')[0]}"`, ` height="${opt.slice(1).split('x')[1]}"`]),
64
+ fmap(str(/^[^\S\n]+[1-9][0-9]*:[1-9][0-9]*(?=[^\S\n]|})/), ([opt]) => [` aspect-ratio="${opt.slice(1).split(':').join('/')}"`]),
65
65
  linkoption,
66
66
  ]);
67
67
 
@@ -20,11 +20,6 @@ describe('Unit: parser/inline/reference', () => {
20
20
  assert.deepStrictEqual(inspect(parser('[[\na]]')), undefined);
21
21
  assert.deepStrictEqual(inspect(parser('[[\\ a]]')), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('[[\\\na]]')), undefined);
23
- assert.deepStrictEqual(inspect(parser('[[a ]]')), undefined);
24
- assert.deepStrictEqual(inspect(parser('[[^a ]]')), undefined);
25
- assert.deepStrictEqual(inspect(parser('[[^a| b ]]')), undefined);
26
- assert.deepStrictEqual(inspect(parser('[[^a| ]]')), undefined);
27
- assert.deepStrictEqual(inspect(parser('[[^a| b]]')), undefined);
28
23
  assert.deepStrictEqual(inspect(parser('[[a\n]]')), undefined);
29
24
  assert.deepStrictEqual(inspect(parser('[[a\\\n]]')), undefined);
30
25
  assert.deepStrictEqual(inspect(parser('[[a\nb]]')), undefined);
@@ -40,7 +35,11 @@ describe('Unit: parser/inline/reference', () => {
40
35
 
41
36
  it('basic', () => {
42
37
  assert.deepStrictEqual(inspect(parser('[[a]]')), [['<sup class="reference">a</sup>'], '']);
43
- assert.deepStrictEqual(inspect(parser('[[a ]]')), [['<sup class="reference">a </sup>'], '']);
38
+ assert.deepStrictEqual(inspect(parser('[[a ]]')), [['<sup class="reference">a</sup>'], '']);
39
+ assert.deepStrictEqual(inspect(parser('[[a ]]')), [['<sup class="reference">a</sup>'], '']);
40
+ assert.deepStrictEqual(inspect(parser('[[a &nbsp;]]')), [['<sup class="reference">a</sup>'], '']);
41
+ assert.deepStrictEqual(inspect(parser('[[a <wbr>]]')), [['<sup class="reference">a</sup>'], '']);
42
+ assert.deepStrictEqual(inspect(parser('[[a [# b #]]]')), [['<sup class="reference">a <sup class="comment" title="b"></sup></sup>'], '']);
44
43
  assert.deepStrictEqual(inspect(parser('[[ab]]')), [['<sup class="reference">ab</sup>'], '']);
45
44
  });
46
45
 
@@ -57,18 +56,28 @@ describe('Unit: parser/inline/reference', () => {
57
56
  it('abbr', () => {
58
57
  assert.deepStrictEqual(inspect(parser('[[^]]')), [['<sup class="reference invalid">^</sup>'], '']);
59
58
  assert.deepStrictEqual(inspect(parser('[[^a]]')), [['<sup class="reference" data-abbr="a"></sup>'], '']);
59
+ assert.deepStrictEqual(inspect(parser('[[^a,]]')), [['<sup class="reference" data-abbr="a,"></sup>'], '']);
60
+ assert.deepStrictEqual(inspect(parser('[[^a, ]]')), [['<sup class="reference" data-abbr="a,"></sup>'], '']);
60
61
  assert.deepStrictEqual(inspect(parser('[[^a ]]')), [['<sup class="reference" data-abbr="a"></sup>'], '']);
62
+ assert.deepStrictEqual(inspect(parser('[[^a ]]')), [['<sup class="reference invalid">^a</sup>'], '']);
61
63
  assert.deepStrictEqual(inspect(parser('[[^a b]]')), [['<sup class="reference" data-abbr="a b"></sup>'], '']);
62
64
  assert.deepStrictEqual(inspect(parser('[[^a b]]')), [['<sup class="reference invalid">^a b</sup>'], '']);
63
65
  assert.deepStrictEqual(inspect(parser('[[^a|]]')), [['<sup class="reference" data-abbr="a"></sup>'], '']);
66
+ assert.deepStrictEqual(inspect(parser('[[^a,|]]')), [['<sup class="reference" data-abbr="a,"></sup>'], '']);
64
67
  assert.deepStrictEqual(inspect(parser('[[^a |]]')), [['<sup class="reference" data-abbr="a"></sup>'], '']);
65
68
  assert.deepStrictEqual(inspect(parser('[[^a|b]]')), [['<sup class="reference invalid">^a|b</sup>'], '']);
66
69
  assert.deepStrictEqual(inspect(parser('[[^a| ]]')), [['<sup class="reference" data-abbr="a"></sup>'], '']);
67
70
  assert.deepStrictEqual(inspect(parser('[[^a| b]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
68
- assert.deepStrictEqual(inspect(parser('[[^a| b ]]')), [['<sup class="reference" data-abbr="a">b </sup>'], '']);
71
+ assert.deepStrictEqual(inspect(parser('[[^a| b ]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
72
+ assert.deepStrictEqual(inspect(parser('[[^a| b ]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
73
+ assert.deepStrictEqual(inspect(parser('[[^a| ]]')), [['<sup class="reference" data-abbr="a"></sup>'], '']);
74
+ assert.deepStrictEqual(inspect(parser('[[^a| b]]')), [['<sup class="reference" data-abbr="a">b</sup>'], '']);
75
+ assert.deepStrictEqual(inspect(parser('[[^a| <wbr>]]')), [['<sup class="reference invalid">^a|</sup>'], '']);
76
+ assert.deepStrictEqual(inspect(parser('[[^a| <wbr>b]]')), [['<sup class="reference invalid">^a| <wbr>b</sup>'], '']);
77
+ assert.deepStrictEqual(inspect(parser('[[^a| ^b]]')), [['<sup class="reference" data-abbr="a">^b</sup>'], '']);
69
78
  assert.deepStrictEqual(inspect(parser('[[^1]]')), [['<sup class="reference invalid">^1</sup>'], '']);
70
79
  assert.deepStrictEqual(inspect(parser('[[^1a]]')), [['<sup class="reference" data-abbr="1a"></sup>'], '']);
71
- assert.deepStrictEqual(inspect(parser('[[^1 ]]')), [['<sup class="reference invalid">^1 </sup>'], '']);
80
+ assert.deepStrictEqual(inspect(parser('[[^1 ]]')), [['<sup class="reference invalid">^1</sup>'], '']);
72
81
  assert.deepStrictEqual(inspect(parser('[[^1 a]]')), [['<sup class="reference" data-abbr="1 a"></sup>'], '']);
73
82
  assert.deepStrictEqual(inspect(parser('[[^1|]]')), [['<sup class="reference invalid">^1|</sup>'], '']);
74
83
  assert.deepStrictEqual(inspect(parser('[[^1 |]]')), [['<sup class="reference invalid">^1 |</sup>'], '']);
@@ -76,9 +85,15 @@ describe('Unit: parser/inline/reference', () => {
76
85
  assert.deepStrictEqual(inspect(parser('[[^Xyz 2020]]')), [['<sup class="reference" data-abbr="Xyz 2020"></sup>'], '']);
77
86
  assert.deepStrictEqual(inspect(parser('[[^Xyz, 2020, p1-2]]')), [['<sup class="reference" data-abbr="Xyz, 2020, p1-2"></sup>'], '']);
78
87
  assert.deepStrictEqual(inspect(parser('[[^X. Y., Z et al., 2020, p1-2]]')), [['<sup class="reference" data-abbr="X. Y., Z et al., 2020, p1-2"></sup>'], '']);
88
+ assert.deepStrictEqual(inspect(parser(`[[^A's, Aces']]`)), [[`<sup class="reference" data-abbr="A's, Aces'"></sup>`], '']);
79
89
  assert.deepStrictEqual(inspect(parser('[[^^]]')), [['<sup class="reference invalid">^^</sup>'], '']);
80
90
  assert.deepStrictEqual(inspect(parser('[[\\^]]')), [['<sup class="reference">^</sup>'], '']);
81
- assert.deepStrictEqual(inspect(parser('[[^a| ^b]]')), [['<sup class="reference" data-abbr="a">^b</sup>'], '']);
91
+ assert.deepStrictEqual(inspect(parser('[[^ ]]')), [['<sup class="reference invalid">^</sup>'], '']);
92
+ assert.deepStrictEqual(inspect(parser('[[^ a]]')), [['<sup class="reference invalid">^ a</sup>'], '']);
93
+ assert.deepStrictEqual(inspect(parser('[[^ |]]')), [['<sup class="reference invalid">^ |</sup>'], '']);
94
+ assert.deepStrictEqual(inspect(parser('[[^ |b]]')), [['<sup class="reference invalid">^ |b</sup>'], '']);
95
+ assert.deepStrictEqual(inspect(parser('[[^ | ]]')), [['<sup class="reference invalid">^ |</sup>'], '']);
96
+ assert.deepStrictEqual(inspect(parser('[[^ | b]]')), [['<sup class="reference invalid">^ | b</sup>'], '']);
82
97
  });
83
98
 
84
99
  });
@@ -3,10 +3,10 @@ import { ReferenceParser } from '../inline';
3
3
  import { union, subsequence, some, validate, verify, focus, guard, context, creator, surround, lazy, fmap } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { str } from '../source';
6
- import { startTight, isEndTight, stringify } from '../util';
6
+ import { startTight, isStartTight, trimEnd, stringify } from '../util';
7
7
  import { html, defrag } from 'typed-dom';
8
8
 
9
- export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]', '\n', fmap(verify(surround(
9
+ export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]', '\n', fmap(surround(
10
10
  '[[',
11
11
  guard(context => context.syntax?.inline?.reference ?? true,
12
12
  startTight(
@@ -23,23 +23,23 @@ export const reference: ReferenceParser = lazy(() => creator(validate('[[', ']]'
23
23
  subsequence([
24
24
  abbr,
25
25
  focus('^', c => [['', c], '']),
26
- startTight(some(inline, ']', /^\\?\n/)),
26
+ some(inline, ']', /^\\?\n/),
27
27
  ])))),
28
28
  ']]'),
29
- isEndTight),
30
- ns => [html('sup', attributes(ns), defrag(ns))]))));
29
+ ns => [html('sup', attributes(ns), trimEnd(defrag(ns)))]))));
31
30
 
32
- const abbr: ReferenceParser.AbbrParser = creator(fmap(surround(
31
+ const abbr: ReferenceParser.AbbrParser = creator(fmap(verify(surround(
33
32
  '^',
34
- union([str(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:['-]|[.,]? |\., )[0-9A-Za-z]+)*/)]),
35
- /^[^\S\n]?\|?(?=]])|^\|[^\S\n]/),
33
+ union([str(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:-|(?=\W)(?!'\d)'?(?!\.\d)\.?(?!,\S),? ?)[0-9A-Za-z]+)*(?:-|'?\.?,? ?)?/)]),
34
+ /^\|?(?=]])|^\|[^\S\n]+/),
35
+ (_, rest, context) => isStartTight(rest, context)),
36
36
  ([source]) => [html('abbr', source)]));
37
37
 
38
38
  function attributes(ns: (string | HTMLElement)[]): Record<string, string | undefined> {
39
39
  return typeof ns[0] === 'object' && ns[0].tagName === 'ABBR'
40
40
  ? {
41
41
  class: 'reference',
42
- 'data-abbr': stringify([ns.shift()!]),
42
+ 'data-abbr': stringify([ns.shift()!]).trimEnd(),
43
43
  }
44
44
  : ns[0] === ''
45
45
  ? {
@@ -16,7 +16,6 @@ describe('Unit: parser/inline/ruby', () => {
16
16
  assert.deepStrictEqual(inspect(parser('[&Tab; a](b)')), undefined);
17
17
  assert.deepStrictEqual(inspect(parser('[a]()')), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('[a]( )')), undefined);
19
- assert.deepStrictEqual(inspect(parser('[a ](b)')), undefined);
20
19
  assert.deepStrictEqual(inspect(parser('[a\nb](c)')), undefined);
21
20
  assert.deepStrictEqual(inspect(parser('[a](b\nc)')), undefined);
22
21
  assert.deepStrictEqual(inspect(parser(' [a](b)')), undefined);
@@ -29,6 +28,8 @@ describe('Unit: parser/inline/ruby', () => {
29
28
  assert.deepStrictEqual(inspect(parser('[A ](a)')), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp></ruby>'], '']);
30
29
  assert.deepStrictEqual(inspect(parser('[A ](a b)')), [['<ruby>A<rp>(</rp><rt>a b</rt><rp>)</rp></ruby>'], '']);
31
30
  assert.deepStrictEqual(inspect(parser('[A ](a b )')), [['<ruby>A<rp>(</rp><rt>a b</rt><rp>)</rp></ruby>'], '']);
31
+ assert.deepStrictEqual(inspect(parser('[A ](a)')), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp><rt></rt></ruby>'], '']);
32
+ assert.deepStrictEqual(inspect(parser('[A \\ ](a)')), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp> <rt></rt></ruby>'], '']);
32
33
  assert.deepStrictEqual(inspect(parser('[AB](a)')), [['<ruby>AB<rp>(</rp><rt>a</rt><rp>)</rp></ruby>'], '']);
33
34
  assert.deepStrictEqual(inspect(parser('[AB](a )')), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rt></rt></ruby>'], '']);
34
35
  assert.deepStrictEqual(inspect(parser('[AB]( b)')), [['<ruby>A<rt></rt>B<rp>(</rp><rt>b</rt><rp>)</rp></ruby>'], '']);
@@ -4,7 +4,7 @@ import { eval, exec } from '../../combinator/data/parser';
4
4
  import { sequence, validate, verify, focus, creator, surround, lazy, bind } from '../../combinator';
5
5
  import { htmlentity } from './htmlentity';
6
6
  import { text as txt } from '../source';
7
- import { isStartTight, isEndTight } from '../util';
7
+ import { verifyStartTight } from '../util';
8
8
  import { html, defrag } from 'typed-dom';
9
9
  import { unshift, push, join } from 'spica/array';
10
10
 
@@ -14,32 +14,34 @@ export const ruby: RubyParser = lazy(() => creator(bind(verify(
14
14
  surround('[', focus(/^(?:\\[^\n]|[^\[\]\n])+(?=]\()/, text), ']'),
15
15
  surround('(', focus(/^(?:\\[^\n]|[^\(\)\n])+(?=\))/, text), ')'),
16
16
  ])),
17
- ([texts]) => isStartTight(texts) && isEndTight(texts)),
17
+ ([texts]) => verifyStartTight(texts)),
18
18
  ([texts, rubies], rest) => {
19
- texts[texts.length - 1] || texts.pop();
20
- assert(texts[texts.length - 1].trim());
19
+ const tail = typeof texts[texts.length - 1] === 'object'
20
+ ? [texts.pop()!]
21
+ : [];
22
+ tail.length === 0 && texts[texts.length - 1] === '' && texts.pop();
21
23
  switch (true) {
22
24
  case rubies.length <= texts.length:
23
- return [[html('ruby', defrag(texts
25
+ return [[html('ruby', defrag(push(texts
24
26
  .reduce((acc, _, i) =>
25
27
  push(acc, unshift([texts[i]],
26
28
  i < rubies.length && rubies[i]
27
29
  ? [html('rp', '('), html('rt', rubies[i]), html('rp', ')')]
28
30
  : [html('rt')]))
29
- , [])))], rest];
31
+ , []), tail)))], rest];
30
32
  case texts.length === 1 && [...texts[0]].length >= rubies.length:
31
- return [[html('ruby', defrag([...texts[0]]
33
+ return [[html('ruby', defrag(push([...texts[0]]
32
34
  .reduce((acc, _, i, texts) =>
33
35
  push(acc, unshift([texts[i]],
34
36
  i < rubies.length && rubies[i]
35
37
  ? [html('rp', '('), html('rt', rubies[i]), html('rp', ')')]
36
38
  : [html('rt')]))
37
- , [])))], rest];
39
+ , []), tail)))], rest];
38
40
  default:
39
41
  assert(rubies.length > 0);
40
- return [[html('ruby', defrag(unshift(
42
+ return [[html('ruby', defrag(push(unshift(
41
43
  [join(texts, ' ')],
42
- [html('rp', '('), html('rt', join(rubies, ' ').trim()), html('rp', ')')])))
44
+ [html('rp', '('), html('rt', join(rubies, ' ').trim()), html('rp', ')')]), tail)))
43
45
  ], rest];
44
46
  }
45
47
  })));
@@ -1,18 +1,18 @@
1
1
  import { StrongParser } from '../inline';
2
- import { union, some, creator, surround, lazy } from '../../combinator';
2
+ import { union, some, creator, surround, close, lazy } from '../../combinator';
3
3
  import { inline } from '../inline';
4
4
  import { emphasis } from './emphasis';
5
5
  import { str } from '../source';
6
- import { startTight, isEndTight, trimEndBR } from '../util';
6
+ import { startTight, verifyEndTight, trimEndBR } from '../util';
7
7
  import { html, defrag } from 'typed-dom';
8
8
  import { unshift } from 'spica/array';
9
9
 
10
- export const strong: StrongParser = lazy(() => creator(surround(
11
- str('**', '*'),
10
+ export const strong: StrongParser = lazy(() => creator(surround(close(
11
+ str('**'), /^(?!\*)/),
12
12
  startTight(some(union([emphasis, some(inline, '*'), str('*')]), '**')),
13
13
  str('**'), false,
14
14
  ([as, bs, cs], rest) =>
15
- isEndTight(bs)
15
+ verifyEndTight(bs)
16
16
  ? [[html('strong', defrag(trimEndBR(bs)))], rest]
17
17
  : [unshift(as, bs), cs[0] + rest],
18
18
  ([as, bs], rest) => [unshift(as, bs), rest])));
@@ -10,20 +10,20 @@ import { contentline, emptyline } from './source';
10
10
 
11
11
  import SegmentParser = MarkdownParser.SegmentParser;
12
12
 
13
- const INPUT_SIZE_LIMIT = 1000 ** 2;
14
- export const SEGMENT_LENGTH_LIMIT = 100 * 1000;
13
+ export const MAX_SEGMENT_SIZE = 100_000; // 100,000 bytes (Max value size of FDB)
14
+ export const MAX_INPUT_SIZE = MAX_SEGMENT_SIZE * 10;
15
15
 
16
16
  const parser: SegmentParser = union([
17
17
  heading,
18
18
  codeblock,
19
19
  mathblock,
20
20
  extension,
21
- some(contentline, SEGMENT_LENGTH_LIMIT),
22
- some(emptyline, SEGMENT_LENGTH_LIMIT),
21
+ some(contentline, MAX_SEGMENT_SIZE * 2),
22
+ some(emptyline, MAX_SEGMENT_SIZE * 2),
23
23
  ]);
24
24
 
25
25
  export function* segment(source: string): Generator<string, undefined, undefined> {
26
- if (!validate(source)) return yield `\0Too large input over ${INPUT_SIZE_LIMIT.toLocaleString('en')} bytes.\n${source.slice(0, 1001)}`;
26
+ if (!validate(source, MAX_INPUT_SIZE)) return yield `\0Too large input over ${MAX_INPUT_SIZE.toLocaleString('en')} bytes.\n${source.slice(0, 1001)}`;
27
27
  assert(source.length < Number.MAX_SAFE_INTEGER);
28
28
  while (source !== '') {
29
29
  const result = parser(source, {})!;
@@ -34,21 +34,15 @@ export function* segment(source: string): Generator<string, undefined, undefined
34
34
  assert(segs.join('') === source.slice(0, source.length - rest.length));
35
35
  for (let i = 0; i < segs.length; ++i) {
36
36
  const seg = segs[i];
37
- seg.length > SEGMENT_LENGTH_LIMIT
38
- ? yield `\0Too large segment over ${SEGMENT_LENGTH_LIMIT.toLocaleString('en')} in length.\n${seg}`
39
- : yield seg;
37
+ validate(source, MAX_SEGMENT_SIZE)
38
+ ? yield seg
39
+ : yield `\0Too large segment over ${MAX_SEGMENT_SIZE.toLocaleString('en')} bytes.\n${seg}`
40
40
  }
41
41
  source = rest;
42
42
  }
43
43
  }
44
44
 
45
- function validate(source: string): boolean {
46
- return source.length <= INPUT_SIZE_LIMIT
47
- && new Blob([source]).size <= INPUT_SIZE_LIMIT;
48
- }
49
-
50
- export function prepare(source: string): string {
51
- return validate(source)
52
- ? source
53
- : source.slice(0, INPUT_SIZE_LIMIT + 1);
45
+ export function validate(source: string, size: number): boolean {
46
+ return source.length <= size / 2
47
+ || source.length <= size && new Blob([source]).size <= size;
54
48
  }
@@ -4,6 +4,7 @@ import { creator } from '../../combinator';
4
4
  const separator = /[\s\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]/;
5
5
 
6
6
  export const escsource: EscapableSourceParser = creator(source => {
7
+ assert(source[0] !== '\x7F');
7
8
  if (source === '') return;
8
9
  const i = source.search(separator);
9
10
  switch (i) {
@@ -3,22 +3,20 @@ import { StrParser } from '../source';
3
3
  import { Parser, Context } from '../../combinator/data/parser';
4
4
  import { creator } from '../../combinator';
5
5
 
6
- export function str(pattern: string | RegExp, not?: string): StrParser;
7
- export function str(pattern: string | RegExp, not?: string): Parser<string, Context<StrParser>, []> {
6
+ export function str(pattern: string | RegExp, mustConsume?: boolean): StrParser;
7
+ export function str(pattern: string | RegExp, mustConsume = true): Parser<string, Context<StrParser>, []> {
8
8
  assert(pattern);
9
9
  return typeof pattern === 'string'
10
10
  ? creator(source => {
11
11
  if (source === '') return;
12
12
  return source.slice(0, pattern.length) === pattern
13
- && !(not && source.slice(pattern.length, pattern.length + not.length) === not)
14
13
  ? [[pattern], source.slice(pattern.length)]
15
14
  : undefined;
16
15
  })
17
16
  : creator(source => {
18
17
  if (source === '') return;
19
18
  const m = source.match(pattern);
20
- return m && m[0].length > 0
21
- && !(not && source.slice(m[0].length, m[0].length + not.length) === not)
19
+ return m && (!mustConsume || m[0].length > 0)
22
20
  ? [[m[0]], source.slice(m[0].length)]
23
21
  : undefined;
24
22
  });
@@ -1,7 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { TextParser, TxtParser, LinebreakParser } from '../source';
3
- import { Parser, Context } from '../../combinator/data/parser';
4
- import { union, focus, creator, fmap } from '../../combinator';
3
+ import { union, focus, creator } from '../../combinator';
5
4
  import { str } from './str';
6
5
  import { html } from 'typed-dom';
7
6
 
@@ -18,6 +17,9 @@ export const text: TextParser = creator((source, context) => {
18
17
  return [[source], ''];
19
18
  case 0:
20
19
  switch (source[0]) {
20
+ case '\x7F':
21
+ assert(source[1] === '\\');
22
+ return [[], source.slice(1)];
21
23
  case '\\':
22
24
  switch (source[1]) {
23
25
  case undefined:
@@ -66,11 +68,11 @@ export const text: TextParser = creator((source, context) => {
66
68
 
67
69
  export const txt: TxtParser = union([
68
70
  text,
69
- ]) as Parser<string, Context<TxtParser>, [TextParser]>;
71
+ ]) as TxtParser;
70
72
 
71
- export const linebreak: LinebreakParser = fmap(
72
- focus('\n', union([text])),
73
- ns => ns as [HTMLBRElement]);
73
+ export const linebreak: LinebreakParser = focus('\n', union([
74
+ text,
75
+ ])) as LinebreakParser;
74
76
 
75
77
  export function isAlphanumeric(char: string): boolean {
76
78
  assert(char.length === 1);
@@ -3,6 +3,7 @@ import { creator } from '../../combinator';
3
3
  import { separator, nonWhitespace, nonAlphanumeric, isAlphanumeric } from './text';
4
4
 
5
5
  export const unescsource: UnescapableSourceParser = creator(source => {
6
+ assert(source[0] !== '\x7F');
6
7
  if (source === '') return;
7
8
  const i = source.search(separator);
8
9
  switch (i) {
@@ -1,10 +1,11 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { MarkdownParser } from '../../markdown';
3
3
  import { Parser, eval } from '../combinator/data/parser';
4
- import { union, verify, convert } from '../combinator';
4
+ import { union, some, verify, clear, convert, trim } from '../combinator';
5
5
  import { comment } from './inline/comment';
6
6
  import { htmlentity } from './inline/htmlentity';
7
- import { pop } from 'spica/array';
7
+ import { linebreak, unescsource, str } from './source';
8
+ import { push, pop } from 'spica/array';
8
9
 
9
10
  // https://dev.w3.org/html5/html-author/charref
10
11
  const invisibleHTMLEntityNames = [
@@ -41,22 +42,20 @@ const invisibleHTMLEntityNames = [
41
42
  'InvisibleComma',
42
43
  'ic',
43
44
  ];
44
- const blankline = new RegExp(String.raw`^(?!\n|$)(?:\\?\s|&(?:${invisibleHTMLEntityNames.join('|')});)*\\?(?:\n|$)`, 'gm');
45
+ const blankline = new RegExp(String.raw`^(?!\n|$)(?:\\?\s|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr>)*\\?(?:\n|$)`, 'gm');
45
46
 
46
- export function visualize<P extends Parser<HTMLElement | string>>(parser: P, message?: string): P;
47
- export function visualize<T extends HTMLElement | string>(parser: Parser<T>, message = '(Empty)'): Parser<T> {
47
+ export function visualize<P extends Parser<HTMLElement | string>>(parser: P): P;
48
+ export function visualize<T extends HTMLElement | string>(parser: Parser<T>): Parser<T> {
48
49
  return justify(union([
49
50
  verify(parser, (ns, rest, context) => !rest && hasVisible(ns, context)),
50
- (source: string) => [[source.trim() || message], ''],
51
+ trim(some(union([clear(str('\x7F\\')), linebreak, unescsource]))),
51
52
  ]));
52
53
  }
53
54
  function justify<P extends Parser<unknown>>(parser: P): P;
54
55
  function justify<T>(parser: Parser<T>): Parser<T> {
55
- return convert(source => source.replace(blankline, visualize), parser);
56
-
57
- function visualize(line: string): string {
58
- return line.replace(/[\\&]/g, '\\$&');
59
- }
56
+ return convert(
57
+ source => source.replace(blankline, line => line.replace(/[\\&<]/g, '\x7F\\$&')),
58
+ parser);
60
59
  }
61
60
  function hasVisible(
62
61
  nodes: readonly (HTMLElement | string)[],
@@ -77,70 +76,71 @@ function hasVisible(
77
76
 
78
77
  export function startTight<P extends Parser<unknown>>(parser: P): P;
79
78
  export function startTight<T>(parser: Parser<T>): Parser<T> {
80
- return (source, context) => {
81
- if (source === '') return;
82
- switch (source[0]) {
83
- case ' ':
84
- case ' ':
85
- case '\t':
86
- case '\n':
87
- return;
88
- case '&':
89
- switch (true) {
90
- case source.length > 2
91
- && source[1] !== ' '
92
- && eval(htmlentity(source, context))?.[0].trimStart() == '':
93
- return;
94
- }
95
- break;
96
- case '[':
97
- switch (true) {
98
- case source.length >= 7
99
- && source[1] === '#'
100
- && !!comment(source, context):
101
- return;
102
- }
103
- break;
104
- case '<':
105
- switch (true) {
106
- case source.length >= 5
107
- && source[1] === 'w'
108
- && source.slice(0, 5) === '<wbr>':
109
- case source.length >= 4
110
- && source[1] === 'b'
111
- && source.slice(0, 4) === '<br>':
112
- return;
113
- }
114
- break;
115
- }
116
- return (source[0] === '\\' ? source[1] : source[0])?.trimStart()
79
+ return (source, context) =>
80
+ isStartTight(source, context)
117
81
  ? parser(source, context)
118
82
  : undefined;
119
- }
120
83
  }
121
84
 
122
- export function isStartTight(nodes: readonly (HTMLElement | string)[]): boolean {
85
+ export function isStartTight(source: string, context: MarkdownParser.Context): boolean {
86
+ if (source === '') return true;
87
+ switch (source[0]) {
88
+ case ' ':
89
+ case ' ':
90
+ case '\t':
91
+ case '\n':
92
+ return false;
93
+ case '\\':
94
+ return source[1]?.trimStart() !== '';
95
+ case '&':
96
+ switch (true) {
97
+ case source.length > 2
98
+ && source[1] !== ' '
99
+ && eval(htmlentity(source, context))?.[0].trimStart() == '':
100
+ return false;
101
+ }
102
+ return true;
103
+ case '<':
104
+ switch (true) {
105
+ case source.length >= 5
106
+ && source[1] === 'w'
107
+ && source.slice(0, 5) === '<wbr>':
108
+ return false;
109
+ }
110
+ return true;
111
+ case '[':
112
+ switch (true) {
113
+ case source.length >= 7
114
+ && source[1] === '#'
115
+ && !!comment(source, context):
116
+ return false;
117
+ }
118
+ return true;
119
+ default:
120
+ return source[0].trimStart() !== '';
121
+ }
122
+ }
123
+ export function verifyStartTight(nodes: readonly (HTMLElement | string)[]): boolean {
123
124
  if (nodes.length === 0) return true;
124
- return isVisible(nodes[0], 'start');
125
+ return isVisible(nodes[0]);
125
126
  }
126
- export function isEndTight(nodes: readonly (HTMLElement | string)[]): boolean {
127
+ export function verifyEndTight(nodes: readonly (HTMLElement | string)[]): boolean {
127
128
  if (nodes.length === 0) return true;
128
129
  const last = nodes.length - 1;
129
130
  return typeof nodes[last] === 'string' && (nodes[last] as string).length > 1
130
- ? isVisible(nodes[last], 'end', 0) ||
131
- isVisible(nodes[last], 'end', 1)
132
- : isVisible(nodes[last], 'end') || last === 0 ||
133
- isVisible(nodes[last - 1], 'end');
131
+ ? isVisible(nodes[last], -1) ||
132
+ isVisible(nodes[last], -2)
133
+ : isVisible(nodes[last], -1) || last === 0 ||
134
+ isVisible(nodes[last - 1], -1);
134
135
  }
135
- function isVisible(node: HTMLElement | string | undefined, dir: 'start' | 'end', offset = 0): boolean {
136
- assert(offset >= 0);
136
+ function isVisible(node: HTMLElement | string, position = 0): boolean {
137
137
  if (!node) return false;
138
138
  switch (typeof node) {
139
139
  case 'string':
140
- const char = node[dir === 'start' ? 0 + offset : node.length - 1 - offset];
140
+ assert(node.length + position >= 0);
141
+ const char = node[position >= 0 ? position : node.length + position];
141
142
  assert(char);
142
143
  switch (char) {
143
- case '':
144
144
  case ' ':
145
145
  case '\t':
146
146
  case '\n':
@@ -163,6 +163,31 @@ function isVisible(node: HTMLElement | string | undefined, dir: 'start' | 'end',
163
163
  }
164
164
  }
165
165
 
166
+ export function trimEnd(nodes: (HTMLElement | string)[]): (HTMLElement | string)[] {
167
+ assert(verifyStartTight(nodes));
168
+ const skip = nodes.length > 0 &&
169
+ typeof nodes[nodes.length - 1] === 'object' &&
170
+ nodes[nodes.length - 1]['className'] === 'indexer'
171
+ ? [nodes.pop()!]
172
+ : [];
173
+ for (
174
+ let last = nodes[0];
175
+ nodes.length > 0 &&
176
+ !isVisible(last = nodes[nodes.length - 1], -1) &&
177
+ !(typeof last === 'object' && last.className === 'comment');
178
+ ) {
179
+ assert(nodes.length > 0);
180
+ if (typeof last === 'string') {
181
+ const pos = last.trimEnd().length;
182
+ if (pos > 0) {
183
+ nodes[nodes.length - 1] = last.slice(0, pos);
184
+ break;
185
+ }
186
+ }
187
+ nodes.pop();
188
+ }
189
+ return push(nodes, skip);
190
+ }
166
191
  export function trimEndBR<T extends HTMLElement | string>(nodes: T[]): T[];
167
192
  export function trimEndBR(nodes: (HTMLElement | string)[]): (HTMLElement | string)[] {
168
193
  if (nodes.length === 0) return nodes;