securemark 0.299.1 → 0.299.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 (35) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/index.js +192 -138
  3. package/markdown.d.ts +2 -2
  4. package/package.json +1 -1
  5. package/src/combinator/control/manipulation/fence.ts +2 -2
  6. package/src/combinator/control/manipulation/match.ts +2 -2
  7. package/src/combinator/data/delimiter.ts +5 -3
  8. package/src/combinator/data/parser/context.ts +12 -38
  9. package/src/combinator/data/parser/some.ts +13 -6
  10. package/src/parser/api/header.ts +5 -1
  11. package/src/parser/api/parse.test.ts +9 -9
  12. package/src/parser/block/blockquote.ts +2 -2
  13. package/src/parser/block.ts +1 -1
  14. package/src/parser/context.ts +5 -6
  15. package/src/parser/header.test.ts +5 -5
  16. package/src/parser/header.ts +2 -3
  17. package/src/parser/inline/annotation.ts +9 -4
  18. package/src/parser/inline/autolink/url.ts +3 -4
  19. package/src/parser/inline/deletion.ts +1 -1
  20. package/src/parser/inline/emstrong.ts +1 -1
  21. package/src/parser/inline/insertion.ts +1 -1
  22. package/src/parser/inline/italic.ts +1 -1
  23. package/src/parser/inline/link.ts +2 -2
  24. package/src/parser/inline/mark.ts +1 -1
  25. package/src/parser/inline/math.test.ts +2 -2
  26. package/src/parser/inline/math.ts +3 -3
  27. package/src/parser/inline/media.ts +2 -2
  28. package/src/parser/inline/ruby.ts +2 -3
  29. package/src/parser/inline.test.ts +1 -1
  30. package/src/parser/inline.ts +1 -1
  31. package/src/parser/repeat.ts +11 -23
  32. package/src/parser/source/escapable.ts +33 -10
  33. package/src/parser/source/text.ts +15 -24
  34. package/src/parser/source/unescapable.test.ts +1 -1
  35. package/src/parser/source/unescapable.ts +90 -9
package/markdown.d.ts CHANGED
@@ -1082,7 +1082,7 @@ export namespace MarkdownParser {
1082
1082
  Inline<'url'>,
1083
1083
  Parser<string | HTMLElement, Context, [
1084
1084
  Parser<HTMLAnchorElement, Context, []>,
1085
- InlineParser,
1085
+ Parser<string, Context, []>,
1086
1086
  ]> {
1087
1087
  }
1088
1088
  export namespace UrlParser {
@@ -1092,7 +1092,7 @@ export namespace MarkdownParser {
1092
1092
  SourceParser.StrParser,
1093
1093
  Parser<string | HTMLElement, Context, [
1094
1094
  Parser<HTMLAnchorElement, Context, []>,
1095
- SourceParser.StrParser,
1095
+ Parser<string, Context, []>,
1096
1096
  ]>,
1097
1097
  ]> {
1098
1098
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.299.1",
3
+ "version": "0.299.3",
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",
@@ -1,5 +1,5 @@
1
1
  import { Parser, List, Node, Context, failsafe } from '../../data/parser';
2
- import { consume } from '../../../combinator';
2
+ import { spend } from '../../../combinator';
3
3
  import { firstline, isEmptyline } from '../constraint/line';
4
4
  import { push } from 'spica/array';
5
5
 
@@ -13,7 +13,7 @@ export function fence<C extends Context, D extends Parser<unknown, C>[]>(opener:
13
13
  const matches = opener.exec(source);
14
14
  if (!matches) return;
15
15
  assert(matches[0] === firstline(source, position));
16
- consume(matches[0].length, context);
16
+ spend(context, matches[0].length);
17
17
  const delim = matches[1];
18
18
  assert(delim && delim === delim.trim());
19
19
  if (matches[0].includes(delim, delim.length)) return;
@@ -1,5 +1,5 @@
1
1
  import { Parser } from '../../data/parser';
2
- import { consume } from '../../../combinator';
2
+ import { spend } from '../../../combinator';
3
3
 
4
4
  export function match<P extends Parser>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
5
5
  export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<N>): Parser<N> {
@@ -15,7 +15,7 @@ export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Pars
15
15
  const params = pattern.exec(source);
16
16
  if (!params) return;
17
17
  assert(source.startsWith(params[0], position));
18
- count && consume(params[0].length, context);
18
+ count && spend(context, params[0].length);
19
19
  const result = f(params)(input);
20
20
  context.position += result
21
21
  ? context.position === position
@@ -1,5 +1,5 @@
1
1
  import { Parser, Input, List, Node, Context } from './parser';
2
- import { consume } from './parser/context';
2
+ import { spend } from './parser/context';
3
3
 
4
4
  interface Delimiter {
5
5
  readonly memory: Delimiter[];
@@ -37,6 +37,8 @@ export class Delimiters {
37
37
  assert(pattern !== '');
38
38
  return index(`'${pattern}`);
39
39
  case 'object':
40
+ assert(pattern.flags.includes('y'));
41
+ assert(/^yu?$/.test(pattern.flags));
40
42
  return index(`/${pattern.source}`);
41
43
  }
42
44
  }
@@ -197,7 +199,7 @@ export function matcher(pattern: string | RegExp, advance: boolean, after?: Pars
197
199
  pos = position;
198
200
  if (index === -1) return;
199
201
  const src = source.slice(position, index);
200
- count && !hit && consume(src.length, context);
202
+ count && !hit && spend(context, src.length);
201
203
  if (advance) {
202
204
  context.position = index;
203
205
  }
@@ -248,7 +250,7 @@ export function tester(pattern: string | RegExp, advance: boolean, after?: Parse
248
250
  pos = position;
249
251
  if (index === -1) return;
250
252
  const len = index - position;
251
- count && !hit && consume(len, context);
253
+ count && !hit && spend(context, len);
252
254
  if (advance) {
253
255
  context.position = index;
254
256
  }
@@ -6,61 +6,35 @@ export function creation(cost: number, parser: Parser): Parser {
6
6
  assert(cost >= 0);
7
7
  return input => {
8
8
  const context = input;
9
- const resources = context.resources ?? { clock: cost || 1, recursions: [1] };
10
- const { recursions } = resources;
11
- assert(recursions.length > 0);
12
9
  const result = parser(input);
13
10
  if (result === undefined) return;
14
- consume(cost, context);
11
+ spend(context, cost);
15
12
  return result;
16
13
  };
17
14
  }
18
- export function consume(cost: number, context: Context): void {
19
- const { resources } = context;
20
- if (!resources) return;
15
+ export function spend(context: Context, cost: number): void {
16
+ const resources = context.resources ?? { clock: cost || 1, recursions: [1] };
21
17
  if (resources.clock < cost) throw new Error('Too many creations');
22
18
  resources.clock -= cost;
23
19
  }
24
20
 
25
- export function recursion<P extends Parser>(recursion: number, parser: P): P;
26
- export function recursion(recursion: number, parser: Parser): Parser {
27
- assert(recursion >= 0);
21
+ export function recursion<P extends Parser>(index: number, parser: P): P;
22
+ export function recursion(index: number, parser: Parser): Parser {
23
+ assert(index >= 0);
28
24
  return input => {
29
25
  const context = input;
30
26
  const resources = context.resources ?? { clock: 1, recursions: [1] };
31
27
  const { recursions } = resources;
32
- assert(recursions.length > 0);
33
- const rec = min(recursion, recursions.length - 1);
34
- if (rec >= 0 && recursions[rec] < 1) throw new Error('Too much recursion');
35
- rec >= 0 && --recursions[rec];
28
+ recur(recursions, index, 1);
36
29
  const result = parser(input);
37
- rec >= 0 && ++recursions[rec];
30
+ recur(recursions, index, -1);
38
31
  return result;
39
32
  };
40
33
  }
41
-
42
- export function recursions<P extends Parser>(recursions: readonly number[], parser: P): P;
43
- export function recursions(rs: readonly number[], parser: Parser): Parser {
44
- assert(rs.every(r => r >= 0));
45
- return input => {
46
- const context = input;
47
- const resources = context.resources ?? { clock: 1, recursions: [4] };
48
- const { recursions } = resources;
49
- assert(recursions.length > 0);
50
- for (const recursion of rs) {
51
- const rec = min(recursion, recursions.length - 1);
52
- if (rec === -1) continue;
53
- if (recursions[rec] < 1) throw new Error('Too much recursion');
54
- --recursions[rec];
55
- }
56
- const result = parser(input);
57
- for (const recursion of rs) {
58
- const rec = min(recursion, recursions.length - 1);
59
- if (rec === -1) continue;
60
- ++recursions[rec];
61
- }
62
- return result;
63
- };
34
+ export function recur(recursions: number[], index: number, size: number, force: boolean = false): void {
35
+ index = min(index, recursions.length && recursions.length - 1);
36
+ if (recursions[index] < size - +force) throw new Error('Too much recursion');
37
+ recursions[index] -= size;
64
38
  }
65
39
 
66
40
  export function precedence<P extends Parser>(precedence: number, parser: P): P;
@@ -4,24 +4,31 @@ import { Delimiters } from '../delimiter';
4
4
  type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number];
5
5
 
6
6
  export function some<P extends Parser>(parser: P, limit?: number): P;
7
- export function some<P extends Parser>(parser: P, delimiters?: readonly DelimiterOption[]): P;
8
- export function some<P extends Parser>(parser: P, delimiter: string | RegExp, delimiters?: readonly DelimiterOption[]): P;
9
- export function some<P extends Parser>(parser: P, delimiter: string | RegExp, after: string | RegExp, delimiters?: readonly DelimiterOption[]): P;
10
- export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp | readonly DelimiterOption[], after?: string | RegExp | readonly DelimiterOption[], delimiters?: readonly DelimiterOption[], limit = -1): Parser<N> {
7
+ export function some<P extends Parser>(parser: P, delimiters?: readonly DelimiterOption[], limit?: number): P;
8
+ export function some<P extends Parser>(parser: P, delimiter: string | RegExp, delimiters?: readonly DelimiterOption[], limit?: number): P;
9
+ export function some<P extends Parser>(parser: P, delimiter: string | RegExp, after: string | RegExp, delimiters?: readonly DelimiterOption[], limit?: number): P;
10
+ export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp | readonly DelimiterOption[], after?: number | string | RegExp | readonly DelimiterOption[], delimiters?: number | readonly DelimiterOption[], limit = 0): Parser<N> {
11
11
  if (typeof delimiter === 'number') {
12
12
  limit = delimiter;
13
+ delimiters = undefined;
13
14
  delimiter = undefined;
14
15
  }
15
16
  else if (Array.isArray(delimiter)) {
17
+ limit = after as number;
16
18
  delimiters = delimiter;
17
19
  delimiter = undefined;
18
20
  }
19
21
  else if (after === undefined || Array.isArray(after)) {
22
+ limit = delimiters as number;
20
23
  delimiters = after;
21
24
  after = undefined;
22
25
  }
26
+ else {
27
+ delimiters = delimiters as readonly DelimiterOption[];
28
+ }
23
29
  assert(parser);
24
30
  assert(delimiter !== '');
31
+ assert(delimiters === undefined || Array.isArray(delimiters));
25
32
  const match = Delimiters.tester(delimiter as string, after as string);
26
33
  const delims = delimiters?.map(([delimiter, precedence]) => ({
27
34
  signature: Delimiters.signature(delimiter),
@@ -31,7 +38,6 @@ export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp
31
38
  return input => {
32
39
  const context = input;
33
40
  const { source, position } = context;
34
- //assert(context.backtracks ??= {});
35
41
  let nodes: List<Node<N>> | undefined;
36
42
  delims && context.delimiters.push(delims);
37
43
  // whileは数倍遅い
@@ -43,7 +49,8 @@ export function some<N>(parser: Parser<N>, delimiter?: number | string | RegExp
43
49
  if (result === undefined) break;
44
50
  if (context.position === pos) break;
45
51
  nodes = nodes?.import(result) ?? result;
46
- if (limit >= 0 && context.position - position > limit) break;
52
+ // 次にパースに成功すれば確実に制限値を超えるので制限値ちょうどでも中止する
53
+ if (limit > 0 && context.position - position >= limit) break;
47
54
  }
48
55
  delims && context.delimiters.pop(delims.length);
49
56
  assert(context.position >= position);
@@ -8,7 +8,11 @@ export function header(source: string): string {
8
8
 
9
9
  export function headers(source: string): string[] {
10
10
  const [el] = parse(source);
11
- return el?.textContent!.trimEnd().slice(el.firstChild!.firstChild!.textContent!.length).split(/\r?\n/) ?? [];
11
+ const acc = [];
12
+ for (let field = el?.firstChild?.firstChild; field = field?.nextSibling;) {
13
+ acc.push(field.textContent!);
14
+ }
15
+ return acc;
12
16
  }
13
17
 
14
18
  function parse(source: string): [HTMLElement, number] | [] {
@@ -125,7 +125,7 @@ describe('Unit: parser/api/parse', () => {
125
125
  '!{../../a}',
126
126
  ].join('\n\n'), { host: new URL(`${location.origin}/z`) }).children].map(el => el.outerHTML),
127
127
  [
128
- '<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://source/x/y"><span class="field-name">URL</span>: <span class="field-value">https://source/x/y</span>\n</span></details></aside>',
128
+ '<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="https://source/x/y"><span class="field-name">URL</span>: <span class="field-value">https://source/x/y</span></div></details></aside>',
129
129
  '<p><a class="account" href="https://source/@a" target="_blank">@a</a></p>',
130
130
  '<p><a class="account" href="https://domain/@a" target="_blank">@domain/a</a></p>',
131
131
  '<p><a class="channel" href="https://source/@a?ch=b" target="_blank">@a#b</a></p>',
@@ -158,7 +158,7 @@ describe('Unit: parser/api/parse', () => {
158
158
  '{./a}',
159
159
  ].join('\n\n'), { host: new URL(`${location.origin}/index.md`) }).children].map(el => el.outerHTML),
160
160
  [
161
- '<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://source/x/y"><span class="field-name">URL</span>: <span class="field-value">https://source/x/y</span>\n</span></details></aside>',
161
+ '<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="https://source/x/y"><span class="field-name">URL</span>: <span class="field-value">https://source/x/y</span></div></details></aside>',
162
162
  '<p><a class="url" href="/a">^/a</a></p>',
163
163
  '<p><a class="url" href="https://source/x/a" target="_blank">./a</a></p>',
164
164
  ]);
@@ -173,7 +173,7 @@ describe('Unit: parser/api/parse', () => {
173
173
  '{./a}',
174
174
  ].join('\n\n'), { host: new URL(`${location.origin}/z`) }).children].map(el => el.outerHTML),
175
175
  [
176
- `<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="${location.origin}/x/y"><span class="field-name">URL</span>: <span class="field-value">${location.origin}/x/y</span>\n</span></details></aside>`,
176
+ `<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="${location.origin}/x/y"><span class="field-name">URL</span>: <span class="field-value">${location.origin}/x/y</span></div></details></aside>`,
177
177
  '<p><a class="url" href="/z/a">^/a</a></p>',
178
178
  '<p><a class="url" href="/x/a">./a</a></p>',
179
179
  ]);
@@ -204,9 +204,9 @@ describe('Unit: parser/api/parse', () => {
204
204
  '{#}',
205
205
  ].join('\n\n'), { host: new URL(`${location.origin}/z`) }).children].map(el => normalize(el.outerHTML)),
206
206
  [
207
- `<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://example/x"><span class="field-name">URL</span>: <span class="field-value">https://example/x</span>\n</span></details></aside>`,
207
+ `<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="https://example/x"><span class="field-name">URL</span>: <span class="field-value">https://example/x</span></div></details></aside>`,
208
208
  '<pre class="invalid" translate="no">---\nURL: https://example/y\n---\n</pre>',
209
- '<aside class="example" data-type="markdown"><pre translate="no">---\nURL: https://example/y\n---\n\n{#}</pre><hr><section><aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="url" data-value="https://example/y"><span class="field-name">URL</span>: <span class="field-value">https://example/y</span>\n</span></details></aside><p><a class="url" href="https://example/y#" target="_blank">#</a></p><h2>References</h2><ol class="references"></ol></section></aside>',
209
+ '<aside class="example" data-type="markdown"><pre translate="no">---\nURL: https://example/y\n---\n\n{#}</pre><hr><section><aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="url" data-value="https://example/y"><span class="field-name">URL</span>: <span class="field-value">https://example/y</span></div></details></aside><p><a class="url" href="https://example/y#" target="_blank">#</a></p><h2>References</h2><ol class="references"></ol></section></aside>',
210
210
  '<p><a class="url" href="https://example/x#" target="_blank">#</a></p>',
211
211
  ]);
212
212
  });
@@ -380,28 +380,28 @@ describe('Unit: parser/api/parse', () => {
380
380
  // 最悪計算量での実行速度はCommonMarkの公式JS実装の32nに対して1-4倍程度。
381
381
  // 5n = reference + link + url/math + ruby + text
382
382
  assert.deepStrictEqual(
383
- [...parse(`((([[[[#$[${'.'.repeat(19998)}`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
383
+ [...parse(`((([[[[#$http://[${'.'.repeat(19992)}`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
384
384
  .map(el => el.tagName),
385
385
  ['P']);
386
386
  });
387
387
 
388
388
  it('backtrack 1 error', () => {
389
389
  assert.deepStrictEqual(
390
- [...parse(`((([[[[#$[${'.'.repeat(19998 + 1)}`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
390
+ [...parse(`((([[[[#$http://[${'.'.repeat(19992 + 1)}`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
391
391
  .map(el => el.tagName),
392
392
  ['H1', 'PRE']);
393
393
  });
394
394
 
395
395
  it('backtrack 2', () => {
396
396
  assert.deepStrictEqual(
397
- [...parse(`((([[[[#$[${'.'.repeat(19998)}]]]`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
397
+ [...parse(`((([[[[#$http://[${'.'.repeat(33324)}]]]`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
398
398
  .map(el => el.tagName),
399
399
  ['P', 'OL']);
400
400
  });
401
401
 
402
402
  it('backtrack 2 error', () => {
403
403
  assert.deepStrictEqual(
404
- [...parse(`((([[[[#$[${'.'.repeat(19998 + 1)}]]]`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
404
+ [...parse(`((([[[[#$http://[${'.'.repeat(33324 + 1)}]]]`, {}, new Context({ resources: { clock: 100000, recursions: [100] } })).children]
405
405
  .map(el => el.tagName),
406
406
  ['H1', 'PRE']);
407
407
  });
@@ -1,7 +1,7 @@
1
1
  import { BlockquoteParser } from '../block';
2
2
  import { Recursion } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
- import { union, some, consume, recursion, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
4
+ import { union, some, spend, recursion, block, validate, rewrite, open, convert, lazy, fmap } from '../../combinator';
5
5
  import { autolink } from '../autolink';
6
6
  import { contentline } from '../source';
7
7
  import { unwrap, randomID } from '../util';
@@ -40,7 +40,7 @@ const markdown: BlockquoteParser.MarkdownParser = lazy(() => fmap(
40
40
  rewrite(
41
41
  some(contentline, opener),
42
42
  convert(unindent, context => {
43
- consume(10, context);
43
+ spend(context, 10);
44
44
  const { source } = context;
45
45
  const references = html('ol', { class: 'references' });
46
46
  const document = parse(source, {
@@ -99,7 +99,7 @@ export const block: BlockParser = error(union([
99
99
  case '(':
100
100
  return olist(input);
101
101
  default:
102
- if ('0' <= char && char <= '9') return olist(input);
102
+ if (char <= '9' && '0' <= char) return olist(input);
103
103
  }
104
104
  },
105
105
  paragraph
@@ -20,9 +20,9 @@ export class Context extends Ctx {
20
20
  id,
21
21
  caches,
22
22
  } = options;
23
- this.resources ??= {
23
+ this.resources = options.resources ?? {
24
24
  // バックトラックのせいで文字数制限を受けないようにする。
25
- clock: MAX_SEGMENT_SIZE * (6 + 1),
25
+ clock: MAX_SEGMENT_SIZE * (5 + 1),
26
26
  recursions: [
27
27
  5 || Recursion.block,
28
28
  20 || Recursion.blockquote,
@@ -42,7 +42,7 @@ export class Context extends Ctx {
42
42
  this.id = id;
43
43
  this.caches = caches;
44
44
  }
45
- public override readonly resources?: {
45
+ public override readonly resources: {
46
46
  clock: number;
47
47
  recursions: number[];
48
48
  };
@@ -50,7 +50,7 @@ export class Context extends Ctx {
50
50
  public local: boolean;
51
51
  public sequential: boolean;
52
52
  public buffer: List<Node<(string | HTMLElement)>>;
53
- public recursion = new RecursionCounter('annotation', 2);
53
+ public recursion = new RecursionCounter(2);
54
54
  public readonly header: boolean;
55
55
  public readonly host?: URL;
56
56
  public readonly url?: URL;
@@ -65,7 +65,6 @@ export type Options = Partial<Context>;
65
65
 
66
66
  class RecursionCounter {
67
67
  constructor(
68
- private readonly syntax: string,
69
68
  private readonly limit: number,
70
69
  ) {
71
70
  }
@@ -75,7 +74,7 @@ class RecursionCounter {
75
74
  const { stack } = this
76
75
  for (; this.index > 0 && stack[this.index - 1] <= depth; --this.index);
77
76
  // 内側から数えるので無効化処理できずエラーを投げるしかない。
78
- if (this.index === this.limit) throw new Error(`Too much ${this.syntax} recursion`);
77
+ if (this.index === this.limit) throw new Error(`Too much recursion`);
79
78
  stack[this.index] = depth;
80
79
  ++this.index;
81
80
  }
@@ -24,11 +24,11 @@ describe('Unit: parser/header', () => {
24
24
  });
25
25
 
26
26
  it('basic', () => {
27
- assert.deepStrictEqual(inspect(parser, input('---\na: b\n---', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span></details></aside>'], '']);
28
- assert.deepStrictEqual(inspect(parser, input('---\na: b\n---\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span></details></aside>'], '']);
29
- assert.deepStrictEqual(inspect(parser, input('---\na: b\nC: D e\n---\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span><span class="field" data-name="c" data-value="D e"><span class="field-name">C</span>: <span class="field-value">D e</span>\n</span></details></aside>'], '']);
30
- assert.deepStrictEqual(inspect(parser, input('---\r\na: b\r\nC: D e\r\n---\r\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span><span class="field" data-name="c" data-value="D e"><span class="field-name">C</span>: <span class="field-value">D e</span>\n</span></details></aside>'], '']);
31
- assert.deepStrictEqual(inspect(parser, input('----\na: b\n----', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><span class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span>\n</span></details></aside>'], '']);
27
+ assert.deepStrictEqual(inspect(parser, input('---\na: b\n---', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div></details></aside>'], '']);
28
+ assert.deepStrictEqual(inspect(parser, input('---\na: b\n---\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div></details></aside>'], '']);
29
+ assert.deepStrictEqual(inspect(parser, input('---\na: b\nC: D e\n---\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div><div class="field" data-name="c" data-value="D e"><span class="field-name">C</span>: <span class="field-value">D e</span></div></details></aside>'], '']);
30
+ assert.deepStrictEqual(inspect(parser, input('---\r\na: b\r\nC: D e\r\n---\r\n', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div><div class="field" data-name="c" data-value="D e"><span class="field-name">C</span>: <span class="field-value">D e</span></div></details></aside>'], '']);
31
+ assert.deepStrictEqual(inspect(parser, input('----\na: b\n----', new Context())), [['<aside class="header"><details open=""><summary>Header</summary><div class="field" data-name="a" data-value="b"><span class="field-name">a</span>: <span class="field-value">b</span></div></details></aside>'], '']);
32
32
  });
33
33
 
34
34
  });
@@ -11,7 +11,7 @@ export const header: MarkdownParser.HeaderParser = lazy(() => validate(
11
11
  block(
12
12
  union([
13
13
  validate(context => context.header,
14
- focus(/(---+)[^\S\r\n]*\r?\n(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*:[ \t]+\S[^\r\n]*\r?\n){1,100}\1[^\S\r\n]*(?:$|\r?\n)/yi,
14
+ focus(/(---+)[^\S\r\n]*\r?\n(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*:[ \t]+\S[^\r\n]*\r?\n){1,32}\1[^\S\r\n]*(?:$|\r?\n)/yi,
15
15
  convert(source =>
16
16
  source.slice(source.indexOf('\n') + 1, source.trimEnd().lastIndexOf('\n')),
17
17
  fmap(
@@ -42,11 +42,10 @@ const field: MarkdownParser.HeaderParser.FieldParser = line(({ source, position
42
42
  const name = source.slice(position, source.indexOf(':', position));
43
43
  const value = source.slice(position + name.length + 1).trim();
44
44
  return new List([
45
- new Node(html('span', { class: 'field', 'data-name': name.toLowerCase(), 'data-value': value }, [
45
+ new Node(html('div', { class: 'field', 'data-name': name.toLowerCase(), 'data-value': value }, [
46
46
  html('span', { class: 'field-name' }, name),
47
47
  ': ',
48
48
  html('span', { class: 'field-value' }, value),
49
- '\n',
50
49
  ])),
51
50
  ]);
52
51
  });
@@ -25,7 +25,7 @@ import { html, defrag } from 'typed-dom/dom';
25
25
  // 常に非常に非効率な処理を行い常時低速化するより三重以上の注釈を禁止して効率性を維持するのが妥当である。
26
26
  const MAX_DEPTH = 20;
27
27
  export const annotation: AnnotationParser = lazy(() => constraint(State.annotation,
28
- repeat('(', beforeNonblank, ')', [Recursion.bracket], precedence(1, surround(
28
+ repeat('(', beforeNonblank, ')', Recursion.bracket, precedence(1, surround(
29
29
  '',
30
30
  some(union([inline]), ')', [[')', 1]]),
31
31
  ')',
@@ -41,17 +41,22 @@ export const annotation: AnnotationParser = lazy(() => constraint(State.annotati
41
41
  new Node(html('span', { class: bracketname(context, 1, 1) }, defrag(unwrap(nodes))))
42
42
  ]);
43
43
  }
44
- recursion.add(MAX_DEPTH - (resources?.recursions[Recursion.bracket] ?? resources?.recursions.at(-1) ?? MAX_DEPTH));
44
+ recursion.add(
45
+ MAX_DEPTH - (resources?.recursions[Recursion.bracket] ?? resources?.recursions.at(-1) ?? MAX_DEPTH));
45
46
  context.position += 1;
46
47
  return new List([
47
- new Node(html('sup', { class: 'annotation' }, [html('span', defrag(unwrap(trimBlankNodeEnd(nodes))))]))
48
+ new Node(html('sup', { class: 'annotation' }, [
49
+ html('span', defrag(unwrap(trimBlankNodeEnd(nodes))))
50
+ ]))
48
51
  ]);
49
52
  },
50
53
  (nodes, context, prefix, postfix) => {
51
54
  assert(postfix === 0);
52
55
  for (let i = 0; i < prefix; ++i) {
53
56
  nodes.unshift(new Node('('));
54
- nodes = new List([new Node(html('span', { class: bracketname(context, 0, 0) }, defrag(unwrap(nodes))))]);
57
+ nodes = new List([
58
+ new Node(html('span', { class: bracketname(context, 0, 0) }, defrag(unwrap(nodes))))
59
+ ]);
55
60
  context.range += 1;
56
61
  }
57
62
  return nodes;
@@ -2,13 +2,12 @@ import { AutolinkParser } from '../../inline';
2
2
  import { State, Recursion, Backtrack } from '../../context';
3
3
  import { List, Node } from '../../../combinator/data/parser';
4
4
  import { union, tails, some, recursion, precedence, state, constraint, focus, rewrite, surround, open, lazy } from '../../../combinator';
5
- import { inline } from '../../inline';
6
5
  import { parse } from '../link';
7
6
  import { unescsource, str } from '../../source';
8
7
 
9
8
  export const url: AutolinkParser.UrlParser = lazy(() => rewrite(
10
9
  open(
11
- /(?<![0-9A-Za-z][.+-]?|[@#])https?:\/\/(?=[\x21-\x7E])/y,
10
+ /(?<![0-9A-Za-z][.+-]?|[@#])https?:\/\/(?=[[0-9A-Za-z])/y,
12
11
  precedence(0, some(union([
13
12
  some(unescsource, /(?<![-+*=~^_,.;:!?]|\/{3})(?:[-+*=~^_,.;:!?]|\/{3,}(?!\/))*(?=[\\$"`\[\](){}<>()[]{}|]|[^\x21-\x7E]|$)/y),
14
13
  precedence(1, bracket),
@@ -18,7 +17,7 @@ export const url: AutolinkParser.UrlParser = lazy(() => rewrite(
18
17
  union([
19
18
  constraint(State.autolink, state(State.autolink, context =>
20
19
  new List([new Node(parse(new List(), new List([new Node(context.source)]), context))]))),
21
- open(str(/[^:]+/y), some(inline)),
20
+ context => new List([new Node(context.source)]),
22
21
  ])));
23
22
 
24
23
  export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => focus(
@@ -36,7 +35,7 @@ export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => focus(
36
35
  context))
37
36
  ]);
38
37
  })),
39
- str(/[^:]+/y),
38
+ context => new List([new Node(context.source)]),
40
39
  ]),
41
40
  ])));
42
41
 
@@ -9,7 +9,7 @@ import { unwrap } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
11
  export const deletion: DeletionParser = lazy(() =>
12
- repeat('~~', '', '~~', [Recursion.inline], precedence(0, surround(
12
+ repeat('~~', '', '~~', Recursion.inline, precedence(0, surround(
13
13
  '',
14
14
  some(union([
15
15
  some(inline, blankWith('\n', '~~')),
@@ -24,7 +24,7 @@ const subemphasis: Parser.IntermediateParser<EmphasisParser> = lazy(() => some(u
24
24
  // 可能な限り早く閉じるよう解析しなければならない。
25
25
  // このため終端記号の後ろを見て終端を中止し同じ構文を再帰的に適用してはならない。
26
26
  export const emstrong: EmStrongParser = lazy(() =>
27
- repeat('***', beforeNonblank, '***', [Recursion.inline], precedence(0, surround(
27
+ repeat('***', beforeNonblank, '***', Recursion.inline, precedence(0, surround(
28
28
  '',
29
29
  some(union([some(inline, '*', afterNonblank)])),
30
30
  strs('*', 1, 3),
@@ -9,7 +9,7 @@ import { unwrap } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
11
  export const insertion: InsertionParser = lazy(() =>
12
- repeat('++', '', '++', [Recursion.inline], precedence(0, surround(
12
+ repeat('++', '', '++', Recursion.inline, precedence(0, surround(
13
13
  '',
14
14
  some(union([
15
15
  some(inline, blankWith('\n', '++')),
@@ -12,7 +12,7 @@ import { html, defrag } from 'typed-dom/dom';
12
12
  // 斜体は単語に使うとかえって見づらく読み飛ばしやすくなるため使わないべきであり
13
13
  // ある程度の長さのある文に使うのが望ましい。
14
14
  export const italic: ItalicParser = lazy(() =>
15
- repeat('///', beforeNonblank, '///', [Recursion.inline], precedence(0, surround(
15
+ repeat('///', beforeNonblank, '///', Recursion.inline, precedence(0, surround(
16
16
  '',
17
17
  some(union([inline]), '///', afterNonblank),
18
18
  '///',
@@ -1,7 +1,7 @@
1
1
  import { LinkParser } from '../inline';
2
2
  import { Context, State, Backtrack, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
- import { union, inits, sequence, subsequence, some, consume, precedence, state, constraint, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
4
+ import { union, inits, sequence, subsequence, some, spend, precedence, state, constraint, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
5
5
  import { inline, media, shortmedia } from '../inline';
6
6
  import { attributes } from './html';
7
7
  import { str } from '../source';
@@ -109,7 +109,7 @@ export function parse(
109
109
  const INSECURE_URI = params.shift()!.value;
110
110
  assert(INSECURE_URI === INSECURE_URI.trim());
111
111
  assert(!INSECURE_URI.match(/\s/));
112
- consume(10, context);
112
+ spend(context, 10);
113
113
  let uri: ReadonlyURL | undefined;
114
114
  try{
115
115
  uri = new ReadonlyURL(
@@ -10,7 +10,7 @@ import { unwrap } from '../util';
10
10
  import { html, define, defrag } from 'typed-dom/dom';
11
11
 
12
12
  export const mark: MarkParser = lazy(() =>
13
- repeat('==', beforeNonblank, '==', [Recursion.inline], precedence(0, surround(
13
+ repeat('==', beforeNonblank, '==', Recursion.inline, precedence(0, surround(
14
14
  '',
15
15
  state(State.mark, some(union([inline]), '==', afterNonblank)),
16
16
  '==',
@@ -93,8 +93,8 @@ describe('Unit: parser/inline/math', () => {
93
93
  assert.deepStrictEqual(inspect(parser, input('$\\Begin$', new Context())), [['<span class="invalid" translate="no">$\\Begin$</span>'], '']);
94
94
  assert.deepStrictEqual(inspect(parser, input('$\\begin{}$', new Context())), [['<span class="invalid" translate="no">$\\begin{}$</span>'], '']);
95
95
  assert.deepStrictEqual(inspect(parser, input('${\\begin}$', new Context())), [['<span class="invalid" translate="no">${\\begin}$</span>'], '']);
96
- assert.deepStrictEqual(inspect(parser, input('$http://host$', new Context())), [['<span class="invalid" translate="no">$http://host$</span>'], '']);
97
- assert.deepStrictEqual(inspect(parser, input('${http://host}$', new Context())), [['<span class="invalid" translate="no">${http://host}$</span>'], '']);
96
+ assert.deepStrictEqual(inspect(parser, input('$http://host$', new Context())), undefined);
97
+ assert.deepStrictEqual(inspect(parser, input('${http://host}$', new Context())), undefined);
98
98
  assert.deepStrictEqual(inspect(parser, input(' ${a}$', new Context())), undefined);
99
99
  });
100
100
 
@@ -6,7 +6,7 @@ import { escsource, str } from '../source';
6
6
  import { invalid } from '../util';
7
7
  import { html } from 'typed-dom/dom';
8
8
 
9
- const forbiddenCommand = /\\(?:begin|tiny|huge|large)(?![a-z])|:\/\//i;
9
+ const forbiddenCommand = /\\(?:begin|tiny|huge|large)(?![a-z])/i;
10
10
 
11
11
  export const math: MathParser = lazy(() => rewrite(
12
12
  union([
@@ -19,7 +19,7 @@ export const math: MathParser = lazy(() => rewrite(
19
19
  surround(
20
20
  /\$(?![\s{}])/y,
21
21
  precedence(2, some(union([
22
- some(escsource, /\$|[`"{}\r\n]/y),
22
+ some(escsource, /[`"{}$\r\n]|(?<=[0-9A-Za-z]):\/\/[[0-9A-Za-z]/y),
23
23
  precedence(4, bracket),
24
24
  ]))),
25
25
  /(?<!\s)\$(?![-0-9A-Za-z])/y,
@@ -45,7 +45,7 @@ const bracket: MathParser.BracketParser = lazy(() => surround(
45
45
  recursion(Recursion.terminal,
46
46
  some(union([
47
47
  bracket,
48
- some(escsource, /[{}$\r\n]/y),
48
+ some(escsource, /[{}$\r\n]|(?<=[0-9A-Za-z]):\/\/[[0-9A-Za-z]/y),
49
49
  ]))),
50
50
  str('}'),
51
51
  true));