securemark 0.294.4 → 0.294.6

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 (52) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/index.js +159 -114
  3. package/markdown.d.ts +3 -1
  4. package/package.json +3 -3
  5. package/src/combinator/control/manipulation/fence.ts +2 -0
  6. package/src/combinator/control/manipulation/indent.ts +1 -1
  7. package/src/combinator/control/manipulation/match.ts +11 -8
  8. package/src/combinator/data/parser.ts +3 -0
  9. package/src/parser/api/normalize.test.ts +9 -1
  10. package/src/parser/api/normalize.ts +17 -10
  11. package/src/parser/api/parse.test.ts +3 -3
  12. package/src/parser/block/blockquote.test.ts +3 -9
  13. package/src/parser/block/blockquote.ts +4 -4
  14. package/src/parser/block/dlist.ts +4 -4
  15. package/src/parser/block/extension/example.ts +1 -3
  16. package/src/parser/block/extension/fig.test.ts +0 -1
  17. package/src/parser/block/extension/fig.ts +6 -6
  18. package/src/parser/block/extension/figbase.ts +1 -1
  19. package/src/parser/block/extension/figure.test.ts +1 -1
  20. package/src/parser/block/extension/figure.ts +6 -6
  21. package/src/parser/block/extension/message.ts +1 -1
  22. package/src/parser/block/extension/table.ts +4 -4
  23. package/src/parser/block/heading.ts +4 -4
  24. package/src/parser/block/reply/cite.ts +1 -1
  25. package/src/parser/block/reply/quote.ts +2 -2
  26. package/src/parser/block/sidefence.test.ts +1 -3
  27. package/src/parser/block/sidefence.ts +4 -4
  28. package/src/parser/block/table.ts +2 -2
  29. package/src/parser/block.ts +1 -1
  30. package/src/parser/header.ts +3 -3
  31. package/src/parser/inline/autolink/account.ts +5 -7
  32. package/src/parser/inline/autolink/channel.ts +15 -15
  33. package/src/parser/inline/autolink/hashnum.ts +2 -2
  34. package/src/parser/inline/autolink/hashtag.test.ts +6 -2
  35. package/src/parser/inline/autolink/hashtag.ts +12 -10
  36. package/src/parser/inline/autolink.ts +1 -1
  37. package/src/parser/inline/code.ts +12 -18
  38. package/src/parser/inline/deletion.ts +3 -3
  39. package/src/parser/inline/emstrong.ts +3 -3
  40. package/src/parser/inline/extension/indexer.ts +1 -1
  41. package/src/parser/inline/html.ts +1 -1
  42. package/src/parser/inline/htmlentity.ts +13 -16
  43. package/src/parser/inline/insertion.ts +3 -3
  44. package/src/parser/inline/italic.ts +3 -3
  45. package/src/parser/inline/link.ts +3 -3
  46. package/src/parser/inline/mark.ts +3 -3
  47. package/src/parser/inline/remark.ts +3 -3
  48. package/src/parser/inline/ruby.ts +7 -2
  49. package/src/parser/inline.ts +2 -0
  50. package/src/parser/source/text.ts +11 -5
  51. package/src/parser/util.ts +1 -1
  52. package/src/parser/visibility.ts +1 -1
package/markdown.d.ts CHANGED
@@ -1057,7 +1057,9 @@ export namespace MarkdownParser {
1057
1057
  export interface UnsafeHTMLEntityParser extends
1058
1058
  // ©
1059
1059
  Inline<'unsafehtmlentity'>,
1060
- Parser<string, Context, []> {
1060
+ Parser<string, Context, [
1061
+ SourceParser.StrParser,
1062
+ ]> {
1061
1063
  }
1062
1064
  export interface ShortMediaParser extends
1063
1065
  // !https://host
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.294.4",
3
+ "version": "0.294.6",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -28,7 +28,7 @@
28
28
  "LICENSE"
29
29
  ],
30
30
  "dependencies": {
31
- "spica": "0.0.809"
31
+ "spica": "0.0.810"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/dompurify": "3.0.5",
@@ -42,7 +42,7 @@
42
42
  "babel-plugin-unassert": "^3.2.0",
43
43
  "concurrently": "^8.2.2",
44
44
  "eslint": "^9.8.0",
45
- "eslint-plugin-redos": "^4.4.5",
45
+ "eslint-plugin-redos": "^4.5.0",
46
46
  "eslint-webpack-plugin": "^4.2.0",
47
47
  "glob": "^11.0.0",
48
48
  "karma": "^6.4.4",
@@ -1,4 +1,5 @@
1
1
  import { Parser, List, Data, Ctx, failsafe } from '../../data/parser';
2
+ import { consume } from '../../../combinator';
2
3
  import { firstline, isBlank } from '../constraint/line';
3
4
  import { push } from 'spica/array';
4
5
 
@@ -12,6 +13,7 @@ export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: Reg
12
13
  const matches = opener.exec(source);
13
14
  if (!matches) return;
14
15
  assert(matches[0] === firstline(source, position));
16
+ consume(matches[0].length, context);
15
17
  const delim = matches[1];
16
18
  assert(delim && delim === delim.trim());
17
19
  if (matches[0].includes(delim, delim.length)) return;
@@ -26,7 +26,7 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
26
26
  context.position = source.length;
27
27
  return new List([new Data(source.slice(position))]);
28
28
  }))),
29
- ([indent]) => indent.length <= 16 ? indent.length * 2 + +(indent[0] === ' ') : -1, [])), separation),
29
+ ([indent]) => indent.length * 2 + -(indent[0] === ' '), [], 2 ** 4 - 1)), separation),
30
30
  (lines, context) => {
31
31
  assert(parser = parser as Parser<N>);
32
32
  return parser(subinput(trimBlockEnd(lines.foldl((acc, node) => acc + node.value, '')), context));
@@ -1,20 +1,23 @@
1
1
  import { Parser, failsafe } from '../../data/parser';
2
2
  import { consume } from '../../../combinator';
3
3
 
4
- export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P, cost?: boolean): P;
5
- export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<N>, cost = false): Parser<N> {
4
+ export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
5
+ export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<N>): Parser<N> {
6
6
  assert(!pattern.flags.match(/[gm]/) && pattern.sticky && !pattern.source.startsWith('^'));
7
+ const count = typeof pattern === 'object'
8
+ ? /[^^\\*+][*+]/.test(pattern.source)
9
+ : false;
7
10
  return failsafe(input => {
8
11
  const { context } = input;
9
12
  const { source, position } = context;
10
13
  if (position === source.length) return;
11
14
  pattern.lastIndex = position;
12
- const param = pattern.exec(source);
13
- if (!param) return;
14
- assert(source.startsWith(param[0], position));
15
- cost && consume(param[0].length, context);
16
- const result = f(param)(input);
17
- context.position += result && context.position === position ? param[0].length : 0;
15
+ const params = pattern.exec(source);
16
+ if (!params) return;
17
+ assert(source.startsWith(params[0], position));
18
+ count && consume(params[0].length, context);
19
+ const result = f(params)(input);
20
+ context.position += result && context.position === position ? params[0].length : 0;
18
21
  assert(context.position > position || !result);
19
22
  return context.position > position
20
23
  ? result
@@ -39,8 +39,11 @@ export interface CtxOptions {
39
39
  // 区間別テーブルは固定サイズであるためプールして再使用できる。
40
40
  // 従って分割時のデータ構造は区間ごとに探索木を動的に生成しデータ数に応じてテーブルに移行するのが最も効率的である。
41
41
  // これにより最悪時間計算量線形化に要する最悪空間計算量が+1nに局限される。
42
+ // またはテーブルの参照が高速なら変換せず併用してもよい。
42
43
  // 木とテーブルいずれにおいてもバックトラックデータとオーバーヘッドを合わせた追加データサイズの最大値は
43
44
  // セグメントサイズに制約されるため入力サイズに対する最大追加データサイズの平均比率はかなり小さくなる。
45
+ // 必要なテーブルの最大サイズは最大セグメントサイズであるため最大追加データサイズは入力サイズにかかわらず
46
+ // 10KB*並列数に留まり最大数百文字以下の短文ならば数百byte*並列数となる。
44
47
  //
45
48
  // 1. データ数が規定数を超えたら区間テーブルを生成しデータを振り分ける。
46
49
  // - 子ノードのポインタだけ保持するとしても1ノード複数データ保持で圧縮できるかは微妙。
@@ -67,12 +67,20 @@ describe('Unit: parser/normalize', () => {
67
67
  assert(normalize('\x01---\na: b\x01\n---\n\n!> \x01---\na: b\x01\n---') === '\uFFFD---\na: b\uFFFD\n---\n\n!> \uFFFD---\na: b\uFFFD\n---');
68
68
  });
69
69
 
70
+ it('emoji', () => {
71
+ assert(normalize('😀') === '😀');
72
+ assert(normalize('🤚🏽') === '🤚🏽');
73
+ assert(normalize('👨‍👩‍👧') === '👨‍👩‍👧');
74
+ assert(normalize('🇺🇳') === '🇺🇳');
75
+ assert(normalize('#️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣') === '#️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣');
76
+ });
77
+
70
78
  });
71
79
 
72
80
  describe('escape', () => {
73
81
  it('', () => {
74
82
  assert(escape('\u200B') === '&ZeroWidthSpace;');
75
- assert(escape('\u200D') === '&zwj;');
83
+ assert(escape('\u200F') === '&rlm;');
76
84
  });
77
85
 
78
86
  });
@@ -15,7 +15,7 @@ function format(source: string): string {
15
15
 
16
16
  function sanitize(source: string): string {
17
17
  return source
18
- .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]|(?<![\u1820\u1821])\u180E/g, UNICODE_REPLACEMENT_CHARACTER)
18
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|(?!\u200D)[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]|(?<![\u1820\u1821])\u180E/g, UNICODE_REPLACEMENT_CHARACTER)
19
19
  .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/g, char =>
20
20
  char.length === 1
21
21
  ? UNICODE_REPLACEMENT_CHARACTER
@@ -58,13 +58,20 @@ export const invisibleHTMLEntityNames = [
58
58
  'InvisibleComma',
59
59
  'ic',
60
60
  ] as const;
61
- const unreadableHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames.slice(2);
62
- const unreadableEscapableCharacters = unreadableHTMLEntityNames
61
+ const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name => ![
62
+ 'Tab',
63
+ 'NewLine',
64
+ 'NonBreakingSpace',
65
+ 'nbsp',
66
+ 'zwj',
67
+ 'zwnj',
68
+ ].includes(name));
69
+ const unreadableEscapeCharacters = unreadableEscapeHTMLEntityNames
63
70
  .map(name => unsafehtmlentity(input(`&${name};`, {}))!.head!.value);
64
- assert(unreadableEscapableCharacters.length === unreadableHTMLEntityNames.length);
65
- assert(unreadableEscapableCharacters.every(c => c.length === 1));
66
- const unreadableEscapableCharacter = new RegExp(`[${unreadableEscapableCharacters.join('')}]`, 'g');
67
- assert(!unreadableEscapableCharacter.source.includes('&'));
71
+ assert(unreadableEscapeCharacters.length === unreadableEscapeHTMLEntityNames.length);
72
+ assert(unreadableEscapeCharacters.every(c => c.length === 1));
73
+ const unreadableEscapeCharacter = new RegExp(`[${unreadableEscapeCharacters.join('')}]`, 'g');
74
+ assert(!unreadableEscapeCharacter.source.includes('&'));
68
75
 
69
76
  // https://www.pandanoir.info/entry/2018/03/11/193000
70
77
  // http://anti.rosx.net/etc/memo/002_space.html
@@ -77,7 +84,7 @@ const unreadableSpecialCharacters = [
77
84
  // ZERO WIDTH NON-JOINER
78
85
  '\u200C',
79
86
  // ZERO WIDTH JOINER
80
- '\u200D',
87
+ //'\u200D',
81
88
  // LEFT-TO-RIGHT MARK
82
89
  '\u200E',
83
90
  // RIGHT-TO-LEFT MARK
@@ -104,6 +111,6 @@ assert(unreadableSpecialCharacters.every(c => sanitize(c) === UNICODE_REPLACEMEN
104
111
  // 特殊不可視文字はエディタおよびソースビューアでは等幅および強調表示により可視化する
105
112
  export function escape(source: string): string {
106
113
  return source
107
- .replace(unreadableEscapableCharacter, char =>
108
- `&${unreadableHTMLEntityNames[unreadableEscapableCharacters.indexOf(char)]};`);
114
+ .replace(unreadableEscapeCharacter, char =>
115
+ `&${unreadableEscapeHTMLEntityNames[unreadableEscapeCharacters.indexOf(char)]};`);
109
116
  }
@@ -361,9 +361,9 @@ describe('Unit: parser/api/parse', () => {
361
361
 
362
362
  it('backtrack', function () {
363
363
  this.timeout(5000);
364
- // 最悪計算量での実行速度はCommonMarkの公式JS実装の32nに対して3倍遅い程度。
364
+ // 最悪計算量での実行速度はCommonMarkの公式JS実装の32nより速い。
365
365
  // 5n = annotation/reference + link + url/math + ruby + text
366
- const source = `${'.'.repeat(0 + 0)}((([[[[#$[${'&'.repeat(19998)}`;
366
+ const source = `((([[[[#$[${'.'.repeat(19997)}`;
367
367
  assert.deepStrictEqual(
368
368
  [...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
369
369
  .map(el => el.tagName),
@@ -372,7 +372,7 @@ describe('Unit: parser/api/parse', () => {
372
372
 
373
373
  it('backtrack error', function () {
374
374
  this.timeout(5000);
375
- const source = `${'.'.repeat(0 + 1)}((([[[[#$[${'&'.repeat(19998)}`;
375
+ const source = `((([[[[#$[${'.'.repeat(19997 + 1)}`;
376
376
  assert.deepStrictEqual(
377
377
  [...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
378
378
  .map(el => el.tagName),
@@ -14,6 +14,9 @@ describe('Unit: parser/block/blockquote', () => {
14
14
  assert.deepStrictEqual(inspect(parser('>'), ctx), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('>a'), ctx), undefined);
16
16
  assert.deepStrictEqual(inspect(parser('>\n'), ctx), undefined);
17
+ assert.deepStrictEqual(inspect(parser('>\na'), ctx), undefined);
18
+ assert.deepStrictEqual(inspect(parser('!>\n'), ctx), undefined);
19
+ assert.deepStrictEqual(inspect(parser('!>\na'), ctx), undefined);
17
20
  assert.deepStrictEqual(inspect(parser(' > '), ctx), undefined);
18
21
  assert.deepStrictEqual(inspect(parser('>>'), ctx), undefined);
19
22
  });
@@ -38,10 +41,6 @@ describe('Unit: parser/block/blockquote', () => {
38
41
  assert.deepStrictEqual(inspect(parser('> a\\\nb'), ctx), [['<blockquote><pre>a\\<br>b</pre></blockquote>'], '']);
39
42
  assert.deepStrictEqual(inspect(parser('> a '), ctx), [['<blockquote><pre> a </pre></blockquote>'], '']);
40
43
  assert.deepStrictEqual(inspect(parser('> \na'), ctx), [['<blockquote><pre><br>a</pre></blockquote>'], '']);
41
- assert.deepStrictEqual(inspect(parser('>\na'), ctx), [['<blockquote><pre><br>a</pre></blockquote>'], '']);
42
- assert.deepStrictEqual(inspect(parser('>\n a'), ctx), [['<blockquote><pre><br> a</pre></blockquote>'], '']);
43
- assert.deepStrictEqual(inspect(parser('>\n>'), ctx), [['<blockquote><pre><br></pre></blockquote>'], '']);
44
- assert.deepStrictEqual(inspect(parser('>\n> a'), ctx), [['<blockquote><pre><br>a</pre></blockquote>'], '']);
45
44
  assert.deepStrictEqual(inspect(parser('> http://host'), ctx), [['<blockquote><pre><a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
46
45
  assert.deepStrictEqual(inspect(parser('> http://host)'), ctx), [['<blockquote><pre><a class="url" href="http://host)" target="_blank">http://host)</a></pre></blockquote>'], '']);
47
46
  assert.deepStrictEqual(inspect(parser('> !http://host'), ctx), [['<blockquote><pre>!<a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
@@ -92,11 +91,6 @@ describe('Unit: parser/block/blockquote', () => {
92
91
  assert.deepStrictEqual(inspect(parser('!>> > a\n> b'), ctx), [['<blockquote><blockquote><section><blockquote><pre>a</pre></blockquote><h2>References</h2><ol class="references"></ol></section></blockquote><section><p>b</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
93
92
  assert.deepStrictEqual(inspect(parser('!> !> a'), ctx), [['<blockquote><section><blockquote><section><p>a</p><h2>References</h2><ol class="references"></ol></section></blockquote><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
94
93
  assert.deepStrictEqual(inspect(parser('!> \na'), ctx), [['<blockquote><section><p>a</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
95
- assert.deepStrictEqual(inspect(parser('!>\n'), ctx), undefined);
96
- assert.deepStrictEqual(inspect(parser('!>\na'), ctx), [['<blockquote><section><p>a</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
97
- assert.deepStrictEqual(inspect(parser('!>\n a'), ctx), [['<blockquote><section><p> a</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
98
- assert.deepStrictEqual(inspect(parser('!>\n>'), ctx), [['<blockquote><section><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
99
- assert.deepStrictEqual(inspect(parser('!>\n> a'), ctx), [['<blockquote><section><p>a</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
100
94
  assert.deepStrictEqual(inspect(parser('!>> ## a\n> ## a'), ctx), [['<blockquote><blockquote><section><h2>a</h2><h2>References</h2><ol class="references"></ol></section></blockquote><section><h2>a</h2><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
101
95
  assert.deepStrictEqual(inspect(parser('!>> ~ a\n> ~ a'), ctx), [['<blockquote><blockquote><section><dl><dt>a</dt><dd></dd></dl><h2>References</h2><ol class="references"></ol></section></blockquote><section><dl><dt>a</dt><dd></dd></dl><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
102
96
  assert.deepStrictEqual(inspect(parser('!>> ~~~figure $test-a\n>> > \n>>\n~~~\n> ~~~figure $test-a\n> > \n>\n[#a]\n~~~'), ctx), [['<blockquote><blockquote><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span><span class="figtext"></span></figcaption><div><blockquote></blockquote></div></figure><h2>References</h2><ol class="references"></ol></section></blockquote><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span><span class="figtext"><a class="index">a</a></span></figcaption><div><blockquote></blockquote></div></figure><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
@@ -9,7 +9,7 @@ import { parse } from '../api/parse';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
11
  export const segment: BlockquoteParser.SegmentParser = block(union([
12
- validate(/!?>+(?=[^\S\n]|\n[^\S\n]*\S)/y, some(contentline)),
12
+ validate(/!?>+ /y, some(contentline)),
13
13
  ]));
14
14
 
15
15
  export const blockquote: BlockquoteParser = lazy(() => block(rewrite(segment, union([
@@ -17,9 +17,9 @@ export const blockquote: BlockquoteParser = lazy(() => block(rewrite(segment, un
17
17
  open(/!(?=>)/y, markdown),
18
18
  ]))));
19
19
 
20
- const opener = /(?=>>+(?:$|\s))/y;
21
- const indent = block(open(opener, some(contentline, />(?:$|\s)/y)), false);
22
- const unindent = (source: string) => source.replace(/(?<=^|\n)>(?:[^\S\n]|(?=>*(?:$|\s)))|\n$/g, '');
20
+ const opener = /(?=>>+(?:$|[ \n]))/y;
21
+ const indent = block(open(opener, some(contentline, />(?:$|[ \n])/y)), false);
22
+ const unindent = (source: string) => source.replace(/(?<=^|\n)>(?: |(?=>*(?:$|[ \n])))|\n$/g, '');
23
23
 
24
24
  const source: BlockquoteParser.SourceParser = lazy(() => fmap(
25
25
  some(recursion(Recursion.blockquote, union([
@@ -9,7 +9,7 @@ import { unwrap } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
11
  export const dlist: DListParser = lazy(() => block(fmap(validate(
12
- /~[^\S\n]+(?=\S)/y,
12
+ /~ +(?=\S)/y,
13
13
  some(inits([
14
14
  state(State.annotation | State.reference | State.index | State.label | State.link,
15
15
  some(term)),
@@ -18,15 +18,15 @@ export const dlist: DListParser = lazy(() => block(fmap(validate(
18
18
  ns => new List([new Data(html('dl', unwrap(fillTrailingDescription(ns))))]))));
19
19
 
20
20
  const term: DListParser.TermParser = line(indexee(fmap(open(
21
- /~[^\S\n]+(?=\S)/y,
21
+ /~ +(?=\S)/y,
22
22
  visualize(trimBlank(some(union([indexer, inline])))),
23
23
  true),
24
24
  ns => new List([new Data(html('dt', { 'data-index': dataindex(ns) }, defrag(unwrap(ns))))]))));
25
25
 
26
26
  const desc: DListParser.DescriptionParser = block(fmap(open(
27
- /:[^\S\n]+(?=\S)|/y,
27
+ /: +(?=\S)|/y,
28
28
  rewrite(
29
- some(anyline, /[~:][^\S\n]+\S/y),
29
+ some(anyline, /[~:] +(?=\S)/y),
30
30
  visualize(trimBlankEnd(some(union([inline]))))),
31
31
  true),
32
32
  ns => new List([new Data(html('dd', defrag(unwrap(ns))))])),
@@ -7,10 +7,8 @@ import { unwrap, invalid } from '../../util';
7
7
  import { parse } from '../../api/parse';
8
8
  import { html } from 'typed-dom/dom';
9
9
 
10
- const opener = /(~{3,})(?:example\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/y;
11
-
12
10
  export const example: ExtensionParser.ExampleParser = recursion(Recursion.block, block(fmap(
13
- fence(opener, 300),
11
+ fence(/(~{3,})(?:example\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/y, 300),
14
12
  // Bug: Type mismatch between outer and inner.
15
13
  (nodes: List<Data<string>>, context) => {
16
14
  const [body, overflow, closer, opener, delim, type = 'markdown', param] = unwrap(nodes);
@@ -40,7 +40,6 @@ describe('Unit: parser/block/extension/fig', () => {
40
40
  assert.deepStrictEqual(inspect(parser('[$group-name]\n~~~table\n~~~\n'), ctx), [['<figure data-type="table" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><table></table></div></figure>'], '']);
41
41
  assert.deepStrictEqual(inspect(parser('[$group-name]\n> '), ctx), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote></blockquote></div></figure>'], '']);
42
42
  assert.deepStrictEqual(inspect(parser('[$group-name]\n> \n'), ctx), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote></blockquote></div></figure>'], '']);
43
- assert.deepStrictEqual(inspect(parser('[$group-name]\n>\n~~~'), ctx), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote><pre><br>~~~</pre></blockquote></div></figure>'], '']);
44
43
  assert.deepStrictEqual(inspect(parser('[$group-name]\n!> *a*'), ctx), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote><section><p><em>a</em></p><h2>References</h2><ol class="references"></ol></section></blockquote></div></figure>'], '']);
45
44
  assert.deepStrictEqual(inspect(parser('[$group-name]\n![]{https://host}'), ctx), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt="https://host"></a></div></figure>'], '']);
46
45
  assert.deepStrictEqual(inspect(parser('[$group-name]\n![]{https://host}\n'), ctx), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt="https://host"></a></div></figure>'], '']);
@@ -1,5 +1,5 @@
1
1
  import { ExtensionParser } from '../../block';
2
- import { union, sequence, some, block, line, validate, verify, rewrite, close, convert } from '../../../combinator';
2
+ import { union, sequence, some, block, line, verify, rewrite, close, convert } from '../../../combinator';
3
3
  import { contentline } from '../../source';
4
4
  import { figure } from './figure';
5
5
  import { segment as seg_label } from '../../inline/extension/label';
@@ -12,9 +12,9 @@ import { media, lineshortmedia } from '../../inline';
12
12
 
13
13
  import FigParser = ExtensionParser.FigParser;
14
14
 
15
- export const segment: FigParser.SegmentParser = block(validate(/\[?\$/y,
15
+ export const segment: FigParser.SegmentParser = block(
16
16
  sequence([
17
- line(close(seg_label, /(?=\s).*\n/y)),
17
+ line(close(seg_label, /(?!\S).*\n/y)),
18
18
  union([
19
19
  seg_code,
20
20
  seg_math,
@@ -23,12 +23,12 @@ export const segment: FigParser.SegmentParser = block(validate(/\[?\$/y,
23
23
  seg_placeholder,
24
24
  some(contentline),
25
25
  ]),
26
- ])));
26
+ ]));
27
27
 
28
28
  export const fig: FigParser = block(rewrite(segment, verify(convert(
29
29
  (source, context) => {
30
30
  // Bug: TypeScript
31
- const fence = (/^[^\n]*\n!?>+\s/.test(source) && source.match(/^~{3,}(?=[^\S\n]*$)/mg) as string[] || [])
31
+ const fence = (/^[^\n]*\n!?>+ /.test(source) && source.match(/^~{3,}(?=[^\S\n]*$)/mg) as string[] || [])
32
32
  .reduce((max, fence) => fence > max ? fence : max, '~~') + '~';
33
33
  return parser({ context })
34
34
  ? `${fence}figure ${source.replace(/^(.+\n.+\n)([\S\s]+?)\n?$/, '$1\n$2')}\n${fence}`
@@ -39,7 +39,7 @@ export const fig: FigParser = block(rewrite(segment, verify(convert(
39
39
  ([{ value: el }]) => el.tagName === 'FIGURE')));
40
40
 
41
41
  const parser = sequence([
42
- line(close(seg_label, /(?=\s).*\n/y)),
42
+ line(close(seg_label, /(?!\S).*\n/y)),
43
43
  line(union([
44
44
  media,
45
45
  lineshortmedia,
@@ -5,7 +5,7 @@ import { label } from '../../inline/extension/label';
5
5
  import { html } from 'typed-dom/dom';
6
6
 
7
7
  export const figbase: ExtensionParser.FigbaseParser = block(fmap(
8
- validate(/\[?\$-(?:[0-9]+\.)*0\]?[^\S\n]*(?!\S|\n[^\S\n]*\S)/y,
8
+ validate(/\[?\$-(?:[0-9]+\.)*0\]?(?:$|[ \n])/y,
9
9
  line(union([label]))),
10
10
  ([{ value: el }]) => {
11
11
  const label = el.getAttribute('data-label')!;
@@ -71,7 +71,7 @@ describe('Unit: parser/block/extension/figure', () => {
71
71
  assert.deepStrictEqual(inspect(parser('~~~figure [$-0.0]\n$$\n\n$$\n~~~'), ctx), [['<figure data-type="math" data-label="$-0.0" data-group="$" class="invalid"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><div class="math" translate="no">$$\n\n$$</div></div></figure>'], '']);
72
72
  assert.deepStrictEqual(inspect(parser('~~~figure [$-name]\n!https://host\n~~~'), ctx), [['<figure data-type="media" data-label="$-name" data-group="$" class="invalid"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt="https://host"></a></div></figure>'], '']);
73
73
  assert.deepStrictEqual(inspect(parser('~~~figure [$-name]\n$$\n\n$$\n\ncaption\n~~~'), ctx), [['<figure data-type="math" data-label="$-name" data-group="$" class="invalid"><figcaption><span class="figindex"></span><span class="figtext">caption</span></figcaption><div><div class="math" translate="no">$$\n\n$$</div></div></figure>'], '']);
74
- assert.deepStrictEqual(inspect(parser(`~~~figure [$group-name]\n${'>\n'.repeat(500)}\n~~~`), ctx, '>'), [['<figure data-type="quote" data-label="group-name" data-group="group">'], '']);
74
+ assert.deepStrictEqual(inspect(parser(`~~~figure [$group-name]\n${'> \n'.repeat(500)}\n~~~`), ctx, '>'), [['<figure data-type="quote" data-label="group-name" data-group="group">'], '']);
75
75
  assert.deepStrictEqual(inspect(parser(`~~~figure [$group-name]\n~~~\n0${'\n'.repeat(300)}~~~\n~~~`), ctx, '>'), [['<figure data-type="example" data-label="group-name" data-group="group">'], '']);
76
76
  });
77
77
 
@@ -21,7 +21,7 @@ import { html, defrag } from 'typed-dom/dom';
21
21
  import FigureParser = ExtensionParser.FigureParser;
22
22
 
23
23
  export const segment: FigureParser.SegmentParser = block(match(
24
- /(~{3,})(?:figure[^\S\n])?(?=\[?\$)/y,
24
+ /(~{3,})(?:figure )?(?=\[?\$)/y,
25
25
  memoize(
26
26
  ([, fence], closer = new RegExp(String.raw`${fence}[^\S\n]*(?:$|\n)`, 'y')) => close(
27
27
  sequence([
@@ -44,12 +44,12 @@ export const segment: FigureParser.SegmentParser = block(match(
44
44
  ]),
45
45
  ]),
46
46
  closer),
47
- ([, fence]) => fence.length <= 16 ? fence.length : -1, [])));
47
+ ([, fence]) => fence.length - 1, [], 2 ** 4 - 1)));
48
48
 
49
49
  export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
50
50
  convert(source => source.slice(source.match(/^~+(?:\w+\s+)?/)![0].length, source.trimEnd().lastIndexOf('\n')),
51
51
  sequence([
52
- line(sequence([label, str(/(?=\s).*\n/y)])),
52
+ line(sequence([label, str(/(?!\S).*\n/y)])),
53
53
  inits([
54
54
  block(union([
55
55
  ulist,
@@ -83,7 +83,7 @@ export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
83
83
  ]);
84
84
  })),
85
85
  fmap(
86
- fence(/(~{3,})(?:figure|\[?\$\S*)(?!\S)[^\n]*(?:$|\n)/y, 300),
86
+ fence(/(~{3,})(?:figure(?=$|[ \n])|\[?\$)[^\n]*(?:$|\n)/y, 300),
87
87
  (nodes, context) => {
88
88
  const [body, overflow, closer, opener, delim] = unwrap<string>(nodes);
89
89
  const violation =
@@ -95,11 +95,11 @@ export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
95
95
  'fence',
96
96
  `Invalid trailing line after the closing delimiter "${delim}"`,
97
97
  ] ||
98
- !seg_label(subinput(opener.match(/^~+(?:figure[^\S\n]+)?(\[?\$\S+)/)?.[1] ?? '', context)) && [
98
+ !seg_label(subinput(opener.match(/^~+(?:figure )?(\[?\$\S+)/)?.[1] ?? '', context)) && [
99
99
  'label',
100
100
  'Invalid label',
101
101
  ] ||
102
- /^~+(?:figure[^\S\n]+)?(\[?\$\S+)[^\S\n]+\S/.test(opener) && [
102
+ /^~+(?:figure )?(\[?\$\S+)[^\S\n]+\S/.test(opener) && [
103
103
  'argument',
104
104
  'Invalid argument',
105
105
  ] ||
@@ -20,7 +20,7 @@ import { html } from 'typed-dom/dom';
20
20
  import MessageParser = ExtensionParser.MessageParser;
21
21
 
22
22
  export const message: MessageParser = block(fmap(
23
- fence(/(~{3,})message\/(\S+)([^\n]*)(?:$|\n)/y, 300),
23
+ fence(/(~{3,})message\/(\S+)(?!\S)([^\n]*)(?:$|\n)/y, 300),
24
24
  // Bug: Type mismatch between outer and inner.
25
25
  (nodes: List<Data<string>>, context) => {
26
26
  const [body, overflow, closer, opener, delim, type, param] = unwrap(nodes);
@@ -83,10 +83,10 @@ const align: AlignParser = line(fmap(
83
83
  union([str(alignment)]),
84
84
  ([{ value }]) => new List([new Data(value.split('/').map(s => s.split('')) as [string[], string[]?])])));
85
85
 
86
- const delimiter = /[-=<>]+(?:\/[-=^v]*)?(?=[^\S\n]*\n)|[#:](?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/y;
86
+ const delimiter = /[-=<>]+(?:\/[-=^v]*)?(?=[^\S\n]*\n)|[#:](?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=[ \n])/y;
87
87
 
88
88
  const head: CellParser.HeadParser = block(fmap(open(
89
- str(/#(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/y),
89
+ str(/#(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=[ \n])/y),
90
90
  rewrite(
91
91
  inits([
92
92
  anyline,
@@ -108,7 +108,7 @@ const head: CellParser.HeadParser = block(fmap(open(
108
108
  false);
109
109
 
110
110
  const data: CellParser.DataParser = block(fmap(open(
111
- str(/:(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/y),
111
+ str(/:(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=[ \n])/y),
112
112
  rewrite(
113
113
  inits([
114
114
  anyline,
@@ -133,7 +133,7 @@ const dataline: CellParser.DatalineParser = line(
133
133
  rewrite(
134
134
  contentline,
135
135
  union([
136
- validate(/!+\s/y, convert(source => `:${source}`, data, false)),
136
+ validate(/!+ /y, convert(source => `:${source}`, data, false)),
137
137
  convert(source => `: ${source}`, data, false),
138
138
  ])));
139
139
 
@@ -1,16 +1,16 @@
1
1
  import { HeadingParser } from '../block';
2
2
  import { State } from '../context';
3
3
  import { List, Data } from '../../combinator/data/parser';
4
- import { union, some, state, block, line, validate, focus, rewrite, open, fmap } from '../../combinator';
4
+ import { union, some, state, block, line, focus, rewrite, open, fmap } from '../../combinator';
5
5
  import { inline, indexee, indexer, dataindex } from '../inline';
6
6
  import { str } from '../source';
7
7
  import { visualize, trimBlank } from '../visibility';
8
8
  import { unwrap, invalid } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
- export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
12
- /#+[^\S\n]+\S[^\n]*(?:\n#+(?!\S)[^\n]*)*(?:$|\n)/y,
13
- some(line(({ context: { source } }) => new List([new Data(source)]))))));
11
+ export const segment: HeadingParser.SegmentParser = block(focus(
12
+ /#+ +\S[^\n]*(?:\n#+(?=$|[ \n])[^\n]*)*(?:$|\n)/y,
13
+ some(line(({ context: { source } }) => new List([new Data(source)])))));
14
14
 
15
15
  export const heading: HeadingParser = block(rewrite(segment,
16
16
  // その他の表示制御は各所のCSSで行う。
@@ -17,7 +17,7 @@ export const cite: ReplyParser.CiteParser = line(fmap(
17
17
  // リンクの実装は後で検討
18
18
  focus(/>>#\S*(?=\s*$)/y, ({ context: { source } }) => new List([new Data(html('a', { class: 'anchor' }, source))])),
19
19
  focus(/>>https?:\/\/\S+(?=\s*$)/y, ({ context: { source } }) => new List([new Data(html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source))])),
20
- focus(/>>.+(?=\s*$)/y, ({ context: { source } }) => new List([new Data(source)])),
20
+ focus(/>>\S+(?=\s*$)/y, ({ context: { source } }) => new List([new Data(source)])),
21
21
  ])),
22
22
  nodes => {
23
23
  const quotes = nodes.head!.value as string;
@@ -7,14 +7,14 @@ import { linebreak, unescsource, anyline } from '../../source';
7
7
  import { unwrap } from '../../util';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
 
10
- export const syntax = />+[^\S\n]/y;
10
+ export const syntax = />+ /y;
11
11
 
12
12
  export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
13
13
  rewrite(
14
14
  some(validate(syntax, anyline)),
15
15
  convert(
16
16
  // TODO: インデント数を渡してインデント数前の行頭確認を行う実装に置き換える
17
- source => source.replace(/(?<=^>+[^\S\n])/mg, '\r'),
17
+ source => source.replace(/(?<=^>+ )/mg, '\r'),
18
18
  some(union([
19
19
  // quote補助関数が残した数式をパースする。
20
20
  math,
@@ -39,9 +39,7 @@ describe('Unit: parser/block/sidefence', () => {
39
39
  assert.deepStrictEqual(inspect(parser('| a '), ctx), [['<blockquote class="invalid"><pre> a </pre></blockquote>'], '']);
40
40
  assert.deepStrictEqual(inspect(parser('| \na'), ctx), undefined);
41
41
  assert.deepStrictEqual(inspect(parser('|\na'), ctx), undefined);
42
- assert.deepStrictEqual(inspect(parser('|\n a'), ctx), undefined);
43
- assert.deepStrictEqual(inspect(parser('|\n|'), ctx), [['<blockquote class="invalid"><pre><br></pre></blockquote>'], '']);
44
- assert.deepStrictEqual(inspect(parser('|\n| a'), ctx), [['<blockquote class="invalid"><pre><br>a</pre></blockquote>'], '']);
42
+ assert.deepStrictEqual(inspect(parser('|\n| a'), ctx), undefined);
45
43
  assert.deepStrictEqual(inspect(parser('| http://host'), ctx), [['<blockquote class="invalid"><pre><a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
46
44
  assert.deepStrictEqual(inspect(parser('| http://host)'), ctx), [['<blockquote class="invalid"><pre><a class="url" href="http://host)" target="_blank">http://host)</a></pre></blockquote>'], '']);
47
45
  assert.deepStrictEqual(inspect(parser('| !http://host'), ctx), [['<blockquote class="invalid"><pre>!<a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
@@ -8,7 +8,7 @@ import { unwrap, invalid } from '../util';
8
8
  import { html, define, defrag } from 'typed-dom/dom';
9
9
 
10
10
  export const sidefence: SidefenceParser = lazy(() => block(fmap(focus(
11
- /(?=\|+(?:[^\S\n]|\n\|))(?:\|+(?:[^\S\n][^\n]*)?(?:$|\n))+$/y,
11
+ /\|+ [^\n]*(?:\n\|+(?=$|[ \n])[^\n]*)*(?:$|\n)/y,
12
12
  union([source])),
13
13
  ([{ value }]) => new List([
14
14
  new Data(define(value, {
@@ -17,13 +17,13 @@ export const sidefence: SidefenceParser = lazy(() => block(fmap(focus(
17
17
  })),
18
18
  ]))));
19
19
 
20
- const opener = /(?=\|\|+(?:$|\s))/y;
21
- const unindent = (source: string) => source.replace(/(?<=^|\n)\|(?:[^\S\n]|(?=\|*(?:$|\s)))|\n$/g, '');
20
+ const opener = /(?=\|\|+(?:$|[ \n]))/y;
21
+ const unindent = (source: string) => source.replace(/(?<=^|\n)\|(?: |(?=\|*(?:$|[ \n])))|\n$/g, '');
22
22
 
23
23
  const source: SidefenceParser.SourceParser = lazy(() => fmap(
24
24
  some(recursion(Recursion.block, union([
25
25
  focus(
26
- /(?:\|\|+(?:[^\S\n][^\n]*)?(?:$|\n))+/y,
26
+ /(?:\|\|+(?=$|[ \n])[^\n]*(?:$|\n))+/y,
27
27
  convert(unindent, source, false, true)),
28
28
  rewrite(
29
29
  some(contentline, opener),
@@ -28,7 +28,7 @@ export const table: TableParser = lazy(() => block(fmap(validate(
28
28
  ]))));
29
29
 
30
30
  const row = <P extends CellParser | AlignParser>(parser: P, optional: boolean): RowParser<P> => fallback(fmap(
31
- line(surround(/(?=\|)/y, some(union([parser])), /[|\\]?\s*$/y, optional)),
31
+ line(surround(/(?=\|)/y, some(union([parser])), /\|?\s*$/y, optional)),
32
32
  ns => new List([new Data(html('tr', unwrap(ns)))])),
33
33
  rewrite(contentline, ({ context: { source } }) => new List([
34
34
  new Data(html('tr', {
@@ -53,7 +53,7 @@ const cell: CellParser = surround(
53
53
  close(medialink, /\s*(?=\||$)/y),
54
54
  close(media, /\s*(?=\||$)/y),
55
55
  close(shortmedia, /\s*(?=\||$)/y),
56
- trimBlank(some(inline, /\|/y, [[/[|\\]?\s*$/y, 9]])),
56
+ trimBlank(some(inline, /\|/y, [[/\|?\s*$/y, 9]])),
57
57
  ]),
58
58
  /[^|]*/y, true);
59
59
 
@@ -114,7 +114,7 @@ export const block: BlockParser = reset(
114
114
  ]) as any));
115
115
 
116
116
  function error(parser: BlockParser): BlockParser {
117
- const reg = new RegExp(String.raw`^${Command.Error}.*\n`)
117
+ const reg = new RegExp(String.raw`^${Command.Error}[^\n]*\n`)
118
118
  return recover<BlockParser>(fallback(
119
119
  open(Command.Error, ({ context: { source, position } }) => { throw new Error(source.slice(position).split('\n', 1)[0]); }),
120
120
  parser),