securemark 0.295.7 → 0.295.9

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 (34) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/index.js +243 -298
  3. package/package.json +1 -1
  4. package/src/combinator/control/constraint/contract.ts +5 -21
  5. package/src/combinator/data/parser/context.ts +3 -1
  6. package/src/combinator/data/parser/sequence.ts +3 -3
  7. package/src/combinator/data/parser.ts +5 -2
  8. package/src/parser/api/normalize.ts +1 -1
  9. package/src/parser/block/extension/figbase.test.ts +3 -0
  10. package/src/parser/block/extension/figbase.ts +1 -1
  11. package/src/parser/block/paragraph.test.ts +2 -2
  12. package/src/parser/block/reply/cite.ts +2 -1
  13. package/src/parser/block/reply/quote.ts +2 -1
  14. package/src/parser/block/reply.ts +2 -1
  15. package/src/parser/inline/emphasis.ts +3 -3
  16. package/src/parser/inline/emstrong.ts +4 -4
  17. package/src/parser/inline/extension/index.ts +2 -2
  18. package/src/parser/inline/extension/indexer.ts +3 -3
  19. package/src/parser/inline/extension/placeholder.ts +2 -2
  20. package/src/parser/inline/html.ts +2 -1
  21. package/src/parser/inline/htmlentity.test.ts +1 -1
  22. package/src/parser/inline/htmlentity.ts +10 -5
  23. package/src/parser/inline/italic.ts +2 -2
  24. package/src/parser/inline/link.ts +4 -2
  25. package/src/parser/inline/mark.ts +2 -2
  26. package/src/parser/inline/math.test.ts +10 -2
  27. package/src/parser/inline/math.ts +2 -2
  28. package/src/parser/inline/remark.ts +1 -1
  29. package/src/parser/inline/strong.ts +3 -3
  30. package/src/parser/node.ts +10 -0
  31. package/src/parser/source/str.ts +9 -7
  32. package/src/parser/source/text.ts +10 -42
  33. package/src/parser/visibility.ts +38 -69
  34. /package/src/combinator/data/{data.ts → node.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.295.7",
3
+ "version": "0.295.9",
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
- import { Parser, Input, List, Node, Context, failsafe } from '../../data/parser';
2
- import { matcher } from '../../../combinator';
1
+ import { Parser, Input, List, Node, Context } from '../../data/parser';
2
+ import { matcher, bind } from '../../../combinator';
3
3
 
4
4
  //export function contract<P extends Parser>(patterns: string | RegExp | (string | RegExp)[], parser: P, cond: (nodes: readonly Data<P>[], rest: string) => boolean): P;
5
5
  //export function contract<N>(patterns: string | RegExp | (string | RegExp)[], parser: Parser<N>, cond: (nodes: readonly N[], rest: string) => boolean): Parser<N> {
@@ -11,32 +11,16 @@ export function validate<P extends Parser>(cond: ((input: Input<Parser.Context<P
11
11
  export function validate<N>(pattern: string | RegExp | ((input: Input<Context>) => boolean), parser: Parser<N>): Parser<N> {
12
12
  if (typeof pattern === 'function') return guard(pattern, parser);
13
13
  const match = matcher(pattern, false);
14
- return input => {
15
- const { context } = input;
16
- const { source, position } = context;
17
- if (position === source.length) return;
18
- if (!match(input)) return;
19
- return parser(input);
20
- };
14
+ return input => match(input) ? parser(input) : undefined;
21
15
  }
22
16
 
23
17
  function guard<P extends Parser>(f: (input: Input<Parser.Context<P>>) => boolean, parser: P): P;
24
18
  function guard<N>(f: (input: Input<Context>) => boolean, parser: Parser<N>): Parser<N> {
25
- return input =>
26
- f(input)
27
- ? parser(input)
28
- : undefined;
19
+ return input => f(input) ? parser(input) : undefined;
29
20
  }
30
21
 
31
22
  export function verify<P extends Parser>(parser: P, cond: (nodes: List<Node<Parser.Node<P>>>, context: Parser.Context<P>) => boolean): P;
32
23
  export function verify<N>(parser: Parser<N>, cond: (nodes: List<Node<N>>, context: Context) => boolean): Parser<N> {
33
24
  assert(parser);
34
- return failsafe(input => {
35
- const { context } = input;
36
- const { source, position } = context;
37
- if (position === source.length) return;
38
- const result = parser(input);
39
- if (result && !cond(result, context)) return;
40
- return result;
41
- });
25
+ return bind(parser, (nodes, context) => cond(nodes, context) ? nodes : undefined)
42
26
  }
@@ -159,7 +159,7 @@ export function constraint<N>(state: number, positive: boolean | Parser<N>, pars
159
159
  };
160
160
  }
161
161
 
162
- export function matcher(pattern: string | RegExp, advance: boolean): Parser<string> {
162
+ export function matcher(pattern: string | RegExp, advance: boolean, verify?: (source: string, position: number, range: number) => boolean): Parser<string> {
163
163
  assert(pattern instanceof RegExp ? !pattern.flags.match(/[gm]/) && pattern.sticky && !pattern.source.startsWith('^') : true);
164
164
  const count = typeof pattern === 'object'
165
165
  ? /[^^\\*+][*+]/.test(pattern.source)
@@ -169,6 +169,7 @@ export function matcher(pattern: string | RegExp, advance: boolean): Parser<stri
169
169
  return ({ context }) => {
170
170
  const { source, position } = context;
171
171
  if (!source.startsWith(pattern, position)) return;
172
+ if (verify?.(source, position, pattern.length) === false) return;
172
173
  if (advance) {
173
174
  context.position += pattern.length;
174
175
  }
@@ -182,6 +183,7 @@ export function matcher(pattern: string | RegExp, advance: boolean): Parser<stri
182
183
  if (!pattern.test(source)) return;
183
184
  const src = source.slice(position, pattern.lastIndex);
184
185
  count && consume(src.length, context);
186
+ if (verify?.(source, position, src.length) === false) return;
185
187
  if (advance) {
186
188
  context.position += src.length;
187
189
  }
@@ -1,10 +1,10 @@
1
- import { Parser, List, Node, Context } from '../parser';
1
+ import { Parser, List, Node, Context, failsafe } from '../parser';
2
2
 
3
3
  export function sequence<P extends Parser>(parsers: Parser.SubParsers<P>): Parser.SubNode<P> extends Parser.Node<P> ? P : Parser<Parser.SubNode<P>, Parser.Context<P>, Parser.SubParsers<P>>;
4
4
  export function sequence<N, D extends Parser<N>[]>(parsers: D): Parser<N, Context, D> {
5
5
  assert(parsers.every(f => f));
6
6
  if (parsers.length === 1) return parsers[0];
7
- return input => {
7
+ return failsafe(input => {
8
8
  const { context } = input;
9
9
  const { source } = context;
10
10
  let nodes: List<Node<N>> | undefined;
@@ -16,5 +16,5 @@ export function sequence<N, D extends Parser<N>[]>(parsers: D): Parser<N, Contex
16
16
  nodes = nodes?.import(result) ?? result;
17
17
  }
18
18
  return nodes;
19
- };
19
+ });
20
20
  }
@@ -1,4 +1,4 @@
1
- import { List } from './data';
1
+ import { List } from './node';
2
2
  import { Delimiters } from './delimiter';
3
3
 
4
4
  export type Parser<N = unknown, C extends Context = Context, D extends Parser<unknown, C>[] = any>
@@ -20,7 +20,10 @@ export type Result<N, C extends Context = Context, D extends Parser<unknown, C>[
20
20
  | undefined;
21
21
  export { List };
22
22
  export class Node<N> implements List.Node {
23
- constructor(public value: N) {
23
+ constructor(
24
+ public value: N,
25
+ public flags: number = 0,
26
+ ) {
24
27
  }
25
28
  public next?: this = undefined;
26
29
  public prev?: this = undefined;
@@ -59,7 +59,7 @@ export const invisibleHTMLEntityNames = [
59
59
  'it',
60
60
  'InvisibleComma',
61
61
  'ic',
62
- ] as const;
62
+ ] as readonly string[];
63
63
  const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name => ![
64
64
  'Tab',
65
65
  'NewLine',
@@ -13,10 +13,13 @@ describe('Unit: parser/block/extension/figbase', () => {
13
13
  assert.deepStrictEqual(inspect(parser, input('$-0.', new Context())), undefined);
14
14
  assert.deepStrictEqual(inspect(parser, input('$-0.1', new Context())), undefined);
15
15
  assert.deepStrictEqual(inspect(parser, input('$-1', new Context())), undefined);
16
+ assert.deepStrictEqual(inspect(parser, input('$-0 0', new Context())), undefined);
16
17
  assert.deepStrictEqual(inspect(parser, input('$-0\n 0', new Context())), undefined);
17
18
  assert.deepStrictEqual(inspect(parser, input('$-name', new Context())), undefined);
18
19
  assert.deepStrictEqual(inspect(parser, input('$-name-0', new Context())), undefined);
19
20
  assert.deepStrictEqual(inspect(parser, input('$group-0', new Context())), undefined);
21
+ assert.deepStrictEqual(inspect(parser, input('$-0]', new Context())), undefined);
22
+ assert.deepStrictEqual(inspect(parser, input('[$-0', new Context())), undefined);
20
23
  assert.deepStrictEqual(inspect(parser, input(' [$-0]', new Context())), undefined);
21
24
  });
22
25
 
@@ -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\]?(?:$|[ \n])/y,
8
+ validate(/\[?\$-(?:[0-9]+\.)*0\]?[^\S\n]*(?:$|\n)/y,
9
9
  line(union([label]))),
10
10
  ([{ value: el }]) => {
11
11
  const label = el.getAttribute('data-label')!;
@@ -22,8 +22,8 @@ describe('Unit: parser/block/paragraph', () => {
22
22
  assert.deepStrictEqual(inspect(parser, input('a\\ \n', new Context())), [['<p>a</p>'], '']);
23
23
  assert.deepStrictEqual(inspect(parser, input('a\\\n', new Context())), [['<p>a</p>'], '']);
24
24
  assert.deepStrictEqual(inspect(parser, input('a\\\nb', new Context())), [['<p>a<br>b</p>'], '']);
25
- assert.deepStrictEqual(inspect(parser, input('a&NewLine;b', new Context())), [['<p>a b</p>'], '']);
26
- assert.deepStrictEqual(inspect(parser, input('&Tab;&NewLine;', new Context())), [['<p>&amp;Tab;</p>'], '']);
25
+ assert.deepStrictEqual(inspect(parser, input('a&NewLine;b', new Context())), [['<p>a<span class="invalid">&amp;NewLine;</span>b</p>'], '']);
26
+ assert.deepStrictEqual(inspect(parser, input('&Tab;&nbsp;', new Context())), [['<p>&amp;Tab;</p>'], '']);
27
27
  assert.deepStrictEqual(inspect(parser, input('<wbr>', new Context())), [['<p>&lt;wbr&gt;</p>'], '']);
28
28
  assert.deepStrictEqual(inspect(parser, input('<wbr>\n', new Context())), [['<p>&lt;wbr&gt;</p>'], '']);
29
29
  assert.deepStrictEqual(inspect(parser, input('<wbr>\na', new Context())), [['<p>&lt;wbr&gt;<br>a</p>'], '']);
@@ -1,5 +1,6 @@
1
1
  import { ReplyParser } from '../../block';
2
2
  import { List, Node } from '../../../combinator/data/parser';
3
+ import { Flag } from '../../node';
3
4
  import { union, line, focus, open, fmap } from '../../../combinator';
4
5
  import { anchor } from '../../inline/autolink/anchor';
5
6
  import { str } from '../../source';
@@ -33,6 +34,6 @@ export const cite: ReplyParser.CiteParser = line(fmap(
33
34
  ? define(node, { 'data-depth': `${quotes.length + 1}` }, node.innerText.slice(1))
34
35
  : node.slice(1),
35
36
  ]))),
36
- new Node(html('br')),
37
+ new Node(html('br'), Flag.invisible),
37
38
  ]);
38
39
  }));
@@ -1,5 +1,6 @@
1
1
  import { ReplyParser } from '../../block';
2
2
  import { List, Node } from '../../../combinator/data/parser';
3
+ import { Flag } from '../../node';
3
4
  import { union, some, block, validate, rewrite, convert, lazy, fmap } from '../../../combinator';
4
5
  import { math } from '../../inline/math';
5
6
  import { autolink } from '../../inline/autolink';
@@ -21,7 +22,7 @@ export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
21
22
  unescsource,
22
23
  ])))),
23
24
  (ns, { source, position }) => new List([
24
- new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br')),
25
+ new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br'), Flag.invisible),
25
26
  new Node(html('span', { class: 'quote' }, defrag(unwrap(ns)))),
26
27
  ].reverse())),
27
28
  false));
@@ -1,5 +1,6 @@
1
1
  import { ReplyParser } from '../block';
2
2
  import { List, Node } from '../../combinator/data/parser';
3
+ import { Flag } from '../node';
3
4
  import { union, some, block, validate, rewrite, fmap } from '../../combinator';
4
5
  import { cite, syntax as csyntax } from './reply/cite';
5
6
  import { quote, syntax as qsyntax } from './reply/quote';
@@ -20,6 +21,6 @@ export const reply: ReplyParser = block(validate(csyntax, fmap(
20
21
  visualize(fmap(some(inline), (ns, { source, position }) =>
21
22
  source[position - 1] === '\n'
22
23
  ? ns
23
- : ns.push(new Node(html('br'))) && ns)))
24
+ : ns.push(new Node(html('br'), Flag.invisible)) && ns)))
24
25
  ])),
25
26
  ns => new List([new Node(html('p', defrag(unwrap(trimBlankNodeEnd(ns)))))]))));
@@ -5,14 +5,14 @@ import { union, some, recursion, precedence, surround, lazy } from '../../combin
5
5
  import { inline } from '../inline';
6
6
  import { strong } from './strong';
7
7
  import { str } from '../source';
8
- import { tightStart, afterNonblank } from '../visibility';
8
+ import { beforeNonblank, afterNonblank } from '../visibility';
9
9
  import { unwrap } from '../util';
10
10
  import { html, defrag } from 'typed-dom/dom';
11
11
 
12
12
  export const emphasis: EmphasisParser = lazy(() => surround(
13
- str(/\*(?!\*)/y),
13
+ str('*', (source, position, range) => !source.startsWith('*', position + range)),
14
14
  precedence(0, recursion(Recursion.inline,
15
- tightStart(some(union([
15
+ beforeNonblank(some(union([
16
16
  some(inline, '*', afterNonblank),
17
17
  strong,
18
18
  ]))))),
@@ -5,8 +5,8 @@ import { union, some, recursion, precedence, surround, lazy, bind } from '../../
5
5
  import { inline } from '../inline';
6
6
  import { strong } from './strong';
7
7
  import { emphasis } from './emphasis';
8
- import { str } from '../source';
9
- import { tightStart, afterNonblank } from '../visibility';
8
+ import { strs } from '../source';
9
+ import { beforeNonblank, afterNonblank } from '../visibility';
10
10
  import { unwrap, repeat } from '../util';
11
11
  import { html, defrag } from 'typed-dom/dom';
12
12
 
@@ -25,8 +25,8 @@ const subemphasis: Parser.IntermediateParser<EmphasisParser> = lazy(() => some(u
25
25
  export const emstrong: EmStrongParser = lazy(() =>
26
26
  precedence(0, recursion(Recursion.inline, repeat('***', surround(
27
27
  '',
28
- tightStart(some(union([some(inline, '*', afterNonblank)]))),
29
- str(/\*{1,3}/y),
28
+ beforeNonblank(some(union([some(inline, '*', afterNonblank)]))),
29
+ strs('*', 3),
30
30
  false, [],
31
31
  ([, bs, cs], context): Result<Parser.Node<EmStrongParser>, Parser.Context<EmStrongParser>> => {
32
32
  assert(cs.length === 1);
@@ -6,7 +6,7 @@ import { inline } from '../../inline';
6
6
  import { indexee, identity } from './indexee';
7
7
  import { unsafehtmlentity } from '../htmlentity';
8
8
  import { txt, str } from '../../source';
9
- import { tightStart, trimBlankNodeEnd } from '../../visibility';
9
+ import { beforeNonblank, trimBlankNodeEnd } from '../../visibility';
10
10
  import { unwrap } from '../../util';
11
11
  import { html, define, defrag } from 'typed-dom/dom';
12
12
 
@@ -15,7 +15,7 @@ import IndexParser = ExtensionParser.IndexParser;
15
15
  export const index: IndexParser = lazy(() => constraint(State.index, fmap(indexee(surround(
16
16
  str('[#'),
17
17
  precedence(1, state(State.linkers,
18
- tightStart(
18
+ beforeNonblank(
19
19
  some(inits([
20
20
  inline,
21
21
  signature,
@@ -1,6 +1,6 @@
1
1
  import { ExtensionParser } from '../../inline';
2
2
  import { List, Node } from '../../../combinator/data/parser';
3
- import { union, focus, surround } from '../../../combinator';
3
+ import { union, validate, focus, surround } from '../../../combinator';
4
4
  import { signature } from './index';
5
5
  import { html } from 'typed-dom/dom';
6
6
 
@@ -10,10 +10,10 @@ import { html } from 'typed-dom/dom';
10
10
  // テキストまたはインデクスを付けて同期が必要な機会を減らすのが
11
11
  // 継続的編集において最も簡便となる。
12
12
 
13
- export const indexer: ExtensionParser.IndexerParser = surround(
13
+ export const indexer: ExtensionParser.IndexerParser = validate(' [|', surround(
14
14
  / \[(?=\|\S)/y,
15
15
  union([
16
16
  signature,
17
17
  focus(/\|(?=\])/y, () => new List([new Node(html('span', { class: 'indexer', 'data-index': '' }))])),
18
18
  ]),
19
- /\][^\S\n]*(?:$|\n)/y);
19
+ /\][^\S\n]*(?:$|\n)/y));
@@ -4,7 +4,7 @@ import { List, Node } from '../../../combinator/data/parser';
4
4
  import { union, some, recursion, precedence, surround, lazy } from '../../../combinator';
5
5
  import { inline } from '../../inline';
6
6
  import { str } from '../../source';
7
- import { tightStart } from '../../visibility';
7
+ import { beforeNonblank } from '../../visibility';
8
8
  import { invalid } from '../../util';
9
9
  import { html } from 'typed-dom/dom';
10
10
 
@@ -16,7 +16,7 @@ export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => surroun
16
16
  // ^はabbrで使用済みだが^:などのようにして分離使用可能
17
17
  str(/\[[:^|]/y),
18
18
  precedence(1, recursion(Recursion.inline,
19
- tightStart(some(union([inline]), ']', [[']', 1]])))),
19
+ beforeNonblank(some(union([inline]), ']', [[']', 1]])))),
20
20
  str(']'),
21
21
  false,
22
22
  [3 | Backtrack.common],
@@ -1,6 +1,7 @@
1
1
  import { HTMLParser } from '../inline';
2
2
  import { Recursion } from '../context';
3
3
  import { List, Node, Context } from '../../combinator/data/parser';
4
+ import { Flag } from '../node';
4
5
  import { union, some, recursion, precedence, validate, surround, open, match, lazy } from '../../combinator';
5
6
  import { inline } from '../inline';
6
7
  import { str } from '../source';
@@ -28,7 +29,7 @@ export const html: HTMLParser = lazy(() => validate(/<[a-z]+(?=[ >])/yi,
28
29
  open(str(/ ?/y), str('>'), true),
29
30
  true, [],
30
31
  ([as, bs = new List(), cs], context) =>
31
- new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context))]),
32
+ new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context), as.head!.value === '<wbr' ? Flag.invisible : Flag.none)]),
32
33
  ([as, bs = new List()], context) =>
33
34
  new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
34
35
  match(
@@ -33,11 +33,11 @@ describe('Unit: parser/inline/htmlentity', () => {
33
33
  assert.deepStrictEqual(inspect(parser, input('&#X22;', new Context())), [['&'], '#X22;']);
34
34
  assert.deepStrictEqual(inspect(parser, input('&#XD06;', new Context())), [['&'], '#XD06;']);
35
35
  assert.deepStrictEqual(inspect(parser, input('&#xcab;', new Context())), [['&'], '#xcab;']);
36
+ assert.deepStrictEqual(inspect(parser, input('&NewLine;', new Context())), [['<span class="invalid">&amp;NewLine;</span>'], '']);
36
37
  assert.deepStrictEqual(inspect(parser, input(' &amp;', new Context())), undefined);
37
38
  });
38
39
 
39
40
  it('entity', () => {
40
- assert.deepStrictEqual(inspect(parser, input('&NewLine;', new Context())), [[' '], '']);
41
41
  assert.deepStrictEqual(inspect(parser, input('&nbsp;', new Context())), [['\u00A0'], '']);
42
42
  assert.deepStrictEqual(inspect(parser, input('&amp;', new Context())), [['&'], '']);
43
43
  assert.deepStrictEqual(inspect(parser, input('&copy;', new Context())), [['©'], '']);
@@ -1,6 +1,7 @@
1
1
  import { HTMLEntityParser, UnsafeHTMLEntityParser } from '../inline';
2
2
  import { Backtrack } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
+ import { Flag, isinvisibleHTMLEntityName } from '../node';
4
5
  import { union, surround, fmap } from '../../combinator';
5
6
  import { str } from '../source';
6
7
  import { invalid } from '../util';
@@ -11,15 +12,19 @@ export const unsafehtmlentity: UnsafeHTMLEntityParser = surround(
11
12
  false,
12
13
  [3 | Backtrack.unescapable],
13
14
  ([as, bs, cs]) =>
14
- new List([new Node(parser(as.head!.value + bs.head!.value + cs.head!.value))]),
15
+ new List([
16
+ new Node(
17
+ parser(as.head!.value + bs.head!.value + cs.head!.value),
18
+ isinvisibleHTMLEntityName(bs.head!.value) ? Flag.invisible : Flag.none)
19
+ ]),
15
20
  ([as, bs]) =>
16
21
  new List([new Node(as.head!.value + (bs?.head?.value ?? ''))]));
17
22
 
18
23
  export const htmlentity: HTMLEntityParser = fmap(
19
24
  union([unsafehtmlentity]),
20
- ([{ value }]) => new List([
21
- length === 1 || value.at(-1) !== ';'
22
- ? new Node(value)
25
+ ([{ value, flags }]) => new List([
26
+ value.length === 1 || value.at(-1) !== ';'
27
+ ? new Node(value, flags)
23
28
  : new Node(html('span', {
24
29
  class: 'invalid',
25
30
  ...invalid('htmlentity', 'syntax', 'Invalid HTML entity'),
@@ -27,7 +32,7 @@ export const htmlentity: HTMLEntityParser = fmap(
27
32
  ]));
28
33
 
29
34
  const parser = (el => (entity: string): string => {
30
- if (entity === '&NewLine;') return ' ';
35
+ if (entity === '&NewLine;') return entity;
31
36
  el.innerHTML = entity;
32
37
  return el.textContent!;
33
38
  })(html('span'));
@@ -3,7 +3,7 @@ import { Recursion, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
4
  import { union, some, recursion, precedence, surround, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
- import { tightStart, afterNonblank } from '../visibility';
6
+ import { beforeNonblank, afterNonblank } from '../visibility';
7
7
  import { unwrap, repeat } from '../util';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
 
@@ -13,7 +13,7 @@ import { html, defrag } from 'typed-dom/dom';
13
13
  export const italic: ItalicParser = lazy(() =>
14
14
  precedence(0, recursion(Recursion.inline, repeat('///', surround(
15
15
  '',
16
- tightStart(some(union([inline]), '///', afterNonblank)),
16
+ beforeNonblank(some(union([inline]), '///', afterNonblank)),
17
17
  '///',
18
18
  false, [],
19
19
  ([, bs], { buffer }) => buffer.import(bs),
@@ -43,7 +43,9 @@ export const textlink: LinkParser.TextLinkParser = lazy(() => bind(
43
43
  bs && as.import(bs).push(new Node(Command.Cancel)) && as)),
44
44
  ]),
45
45
  ([{ value: content }, { value: params = undefined } = {}], context) => {
46
- if (context.state & State.link) return new List([new Node(context.source.slice(context.position - context.range, context.position))]);
46
+ if (context.state & State.link) return new List([
47
+ new Node(context.source.slice(context.position - context.range, context.position).replace(/\\($|.)/g, '$1'))
48
+ ]);
47
49
  if (content.last!.value === Command.Separator) {
48
50
  content.pop();
49
51
  if (params === undefined) {
@@ -88,7 +90,7 @@ export const medialink: LinkParser.MediaLinkParser = lazy(() => constraint(State
88
90
  new List([new Node(parse(content, params as List<Node<string>>, context))])))));
89
91
 
90
92
  export const uri: LinkParser.ParameterParser.UriParser = union([
91
- open(/ /y, str(/\S+/y)),
93
+ open(' ', str(/\S+/y)),
92
94
  str(/[^\s{}]+/y),
93
95
  ]);
94
96
 
@@ -4,14 +4,14 @@ import { List, Node } from '../../combinator/data/parser';
4
4
  import { union, some, recursion, precedence, state, surround, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
6
  import { identity, signature } from './extension/indexee';
7
- import { tightStart, afterNonblank } from '../visibility';
7
+ import { beforeNonblank, afterNonblank } from '../visibility';
8
8
  import { unwrap, repeat } from '../util';
9
9
  import { html, define, defrag } from 'typed-dom/dom';
10
10
 
11
11
  export const mark: MarkParser = lazy(() =>
12
12
  precedence(0, recursion(Recursion.inline, repeat('==', surround(
13
13
  '',
14
- tightStart(state(State.mark, some(union([inline]), '==', afterNonblank))),
14
+ beforeNonblank(state(State.mark, some(union([inline]), '==', afterNonblank))),
15
15
  '==',
16
16
  false, [],
17
17
  ([, bs], { buffer }) => buffer.import(bs),
@@ -14,6 +14,8 @@ describe('Unit: parser/inline/math', () => {
14
14
  assert.deepStrictEqual(inspect(parser, input('$$', new Context())), undefined);
15
15
  assert.deepStrictEqual(inspect(parser, input('$$$', new Context())), undefined);
16
16
  assert.deepStrictEqual(inspect(parser, input('$0 $', new Context())), undefined);
17
+ assert.deepStrictEqual(inspect(parser, input('$0\\ $', new Context())), undefined);
18
+ assert.deepStrictEqual(inspect(parser, input('$0\\\n$', new Context())), undefined);
17
19
  assert.deepStrictEqual(inspect(parser, input('$-0, $-1', new Context())), undefined);
18
20
  assert.deepStrictEqual(inspect(parser, input('$-0 and $-1', new Context())), undefined);
19
21
  assert.deepStrictEqual(inspect(parser, input('$-0と$-1', new Context())), undefined);
@@ -38,9 +40,13 @@ describe('Unit: parser/inline/math', () => {
38
40
  assert.deepStrictEqual(inspect(parser, input('$a$b', new Context())), undefined);
39
41
  assert.deepStrictEqual(inspect(parser, input('$a$b$', new Context())), undefined);
40
42
  assert.deepStrictEqual(inspect(parser, input('$ $', new Context())), undefined);
43
+ assert.deepStrictEqual(inspect(parser, input('$\\ $', new Context())), undefined);
41
44
  assert.deepStrictEqual(inspect(parser, input('$ a$', new Context())), undefined);
42
45
  assert.deepStrictEqual(inspect(parser, input('$ a $', new Context())), undefined);
43
46
  assert.deepStrictEqual(inspect(parser, input('$\n$', new Context())), undefined);
47
+ assert.deepStrictEqual(inspect(parser, input('$\\\n$', new Context())), undefined);
48
+ assert.deepStrictEqual(inspect(parser, input('$a\nb$', new Context())), undefined);
49
+ assert.deepStrictEqual(inspect(parser, input('$a\\\nb$', new Context())), undefined);
44
50
  assert.deepStrictEqual(inspect(parser, input('$a\\$\nb$', new Context())), undefined);
45
51
  assert.deepStrictEqual(inspect(parser, input('$a\\$\\\nb$', new Context())), undefined);
46
52
  assert.deepStrictEqual(inspect(parser, input('$`$', new Context())), undefined);
@@ -74,9 +80,12 @@ describe('Unit: parser/inline/math', () => {
74
80
  assert.deepStrictEqual(inspect(parser, input('${a}b$', new Context())), undefined);
75
81
  assert.deepStrictEqual(inspect(parser, input('${a}b{c}$', new Context())), undefined);
76
82
  assert.deepStrictEqual(inspect(parser, input('${a}{b}$', new Context())), undefined);
77
- assert.deepStrictEqual(inspect(parser, input('${$}$', new Context())), undefined);
78
83
  assert.deepStrictEqual(inspect(parser, input('${\\}$', new Context())), undefined);
79
84
  assert.deepStrictEqual(inspect(parser, input('${\n}$', new Context())), undefined);
85
+ assert.deepStrictEqual(inspect(parser, input('${\\\n}$', new Context())), undefined);
86
+ assert.deepStrictEqual(inspect(parser, input('${a\nb}$', new Context())), undefined);
87
+ assert.deepStrictEqual(inspect(parser, input('${a\\\nb}$', new Context())), undefined);
88
+ assert.deepStrictEqual(inspect(parser, input('${$}$', new Context())), undefined);
80
89
  assert.deepStrictEqual(inspect(parser, input('${a\\$\nb}$', new Context())), undefined);
81
90
  assert.deepStrictEqual(inspect(parser, input('${a\\$\\\nb}$', new Context())), undefined);
82
91
  assert.deepStrictEqual(inspect(parser, input('$\\begin$', new Context())), [['<span class="invalid" translate="no">$\\begin$</span>'], '']);
@@ -109,7 +118,6 @@ describe('Unit: parser/inline/math', () => {
109
118
  assert.deepStrictEqual(inspect(parser, input('$a$[A](a)', new Context())), [['<span class="math" translate="no" data-src="$a$">$a$</span>'], '[A](a)']);
110
119
  assert.deepStrictEqual(inspect(parser, input('$A$', new Context())), [['<span class="math" translate="no" data-src="$A$">$A$</span>'], '']);
111
120
  assert.deepStrictEqual(inspect(parser, input('$-a$', new Context())), [['<span class="math" translate="no" data-src="$-a$">$-a$</span>'], '']);
112
- assert.deepStrictEqual(inspect(parser, input('$\\ $', new Context())), [['<span class="math" translate="no" data-src="$\\ $">$\\ $</span>'], '']);
113
121
  assert.deepStrictEqual(inspect(parser, input('$\\$$', new Context())), [['<span class="math" translate="no" data-src="$\\$$">$\\$$</span>'], '']);
114
122
  assert.deepStrictEqual(inspect(parser, input('$\\Pi$', new Context())), [['<span class="math" translate="no" data-src="$\\Pi$">$\\Pi$</span>'], '']);
115
123
  assert.deepStrictEqual(inspect(parser, input('$\\ 0$', new Context())), [['<span class="math" translate="no" data-src="$\\ 0$">$\\ 0$</span>'], '']);
@@ -19,10 +19,10 @@ export const math: MathParser = lazy(() => rewrite(
19
19
  surround(
20
20
  /\$(?![\s{}])/y,
21
21
  precedence(2, some(union([
22
- some(escsource, /\s?\$|[`"{}\n]/y),
22
+ some(escsource, /\$|[`"{}\n]/y),
23
23
  precedence(4, bracket),
24
24
  ]))),
25
- /\$(?![-0-9A-Za-z])/y,
25
+ /(?<!\s)\$(?![-0-9A-Za-z])/y,
26
26
  false,
27
27
  [3 | Backtrack.escapable]),
28
28
  ]),
@@ -11,7 +11,7 @@ export const remark: RemarkParser = lazy(() => fallback(surround(
11
11
  str(/\[%(?=[ \n])/y),
12
12
  precedence(3, recursion(Recursion.inline,
13
13
  some(union([inline]), /[ \n]%\]/y, [[/[ \n]%\]/y, 3]]))),
14
- close(text, str(`%]`)),
14
+ close(text, str('%]')),
15
15
  true, [],
16
16
  ([as, bs = new List(), cs]) => new List([
17
17
  new Node(html('span', { class: 'remark' }, [
@@ -5,14 +5,14 @@ import { union, some, recursion, precedence, surround, lazy } from '../../combin
5
5
  import { inline } from '../inline';
6
6
  import { emphasis } from './emphasis';
7
7
  import { str } from '../source';
8
- import { tightStart, afterNonblank } from '../visibility';
8
+ import { beforeNonblank, afterNonblank } from '../visibility';
9
9
  import { unwrap } from '../util';
10
10
  import { html, defrag } from 'typed-dom/dom';
11
11
 
12
12
  export const strong: StrongParser = lazy(() => surround(
13
- str(/\*\*(?!\*)/y),
13
+ str('**', (source, position, range) => !source.startsWith('*', position + range)),
14
14
  precedence(0, recursion(Recursion.inline,
15
- tightStart(some(union([
15
+ beforeNonblank(some(union([
16
16
  some(inline, '*', afterNonblank),
17
17
  emphasis,
18
18
  ]))))),
@@ -0,0 +1,10 @@
1
+ import { invisibleHTMLEntityNames } from './api/normalize';
2
+
3
+ export const enum Flag {
4
+ none,
5
+ invisible,
6
+ }
7
+
8
+ export function isinvisibleHTMLEntityName(name: string): boolean {
9
+ return invisibleHTMLEntityNames.includes(name);
10
+ }
@@ -2,21 +2,23 @@ import { StrParser } from '../source';
2
2
  import { Parser, List, Node } from '../../combinator/data/parser';
3
3
  import { matcher } from '../../combinator';
4
4
 
5
- export function str(pattern: string | RegExp): StrParser;
6
- export function str(pattern: string | RegExp): Parser<string> {
7
- return matcher(pattern, true);
5
+ export function str(pattern: string | RegExp, verify?: (source: string, position: number, range: number) => boolean): StrParser;
6
+ export function str(pattern: string | RegExp, verify?: (source: string, position: number, range: number) => boolean): Parser<string> {
7
+ return matcher(pattern, true, verify);
8
8
  }
9
9
 
10
- export function strs(pattern: string): StrParser;
11
- export function strs(pattern: string): Parser<string> {
10
+ export function strs(pattern: string, limit?: number): StrParser;
11
+ export function strs(pattern: string, limit: number = -1): Parser<string> {
12
12
  assert(pattern);
13
13
  return ({ context }) => {
14
14
  const { source } = context;
15
15
  let acc = '';
16
- for (; context.position < source.length && source.startsWith(pattern, context.position);) {
16
+ for (let i = 0; i !== limit && context.position < source.length && source.startsWith(pattern, context.position); ++i) {
17
17
  acc += pattern;
18
18
  context.position += pattern.length;
19
19
  }
20
- return new List([new Node(acc)]);
20
+ return acc
21
+ ? new List([new Node(acc)])
22
+ : undefined;
21
23
  };
22
24
  }