securemark 0.296.0 → 0.296.2

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 (44) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/index.js +196 -187
  3. package/package.json +1 -1
  4. package/src/combinator/control/constraint/block.ts +2 -2
  5. package/src/combinator/control/constraint/contract.ts +4 -3
  6. package/src/combinator/control/constraint/line.ts +5 -5
  7. package/src/combinator/control/manipulation/fence.ts +4 -4
  8. package/src/combinator/control/manipulation/scope.ts +1 -1
  9. package/src/combinator/control/manipulation/surround.ts +31 -15
  10. package/src/combinator/data/delimiter.ts +82 -6
  11. package/src/combinator/data/parser/context.ts +1 -34
  12. package/src/parser/api/normalize.ts +5 -1
  13. package/src/parser/block/reply/cite.ts +1 -1
  14. package/src/parser/block/reply/quote.ts +1 -1
  15. package/src/parser/block/reply.ts +1 -1
  16. package/src/parser/inline/annotation.ts +3 -3
  17. package/src/parser/inline/deletion.ts +1 -1
  18. package/src/parser/inline/emphasis.ts +4 -4
  19. package/src/parser/inline/emstrong.test.ts +1 -0
  20. package/src/parser/inline/emstrong.ts +3 -3
  21. package/src/parser/inline/extension/index.ts +2 -3
  22. package/src/parser/inline/extension/placeholder.ts +2 -2
  23. package/src/parser/inline/html.ts +3 -3
  24. package/src/parser/inline/htmlentity.ts +2 -2
  25. package/src/parser/inline/insertion.ts +1 -1
  26. package/src/parser/inline/italic.test.ts +1 -0
  27. package/src/parser/inline/italic.ts +2 -2
  28. package/src/parser/inline/link.test.ts +0 -1
  29. package/src/parser/inline/link.ts +3 -3
  30. package/src/parser/inline/mark.test.ts +1 -0
  31. package/src/parser/inline/mark.ts +4 -4
  32. package/src/parser/inline/media.test.ts +0 -1
  33. package/src/parser/inline/media.ts +1 -1
  34. package/src/parser/inline/reference.ts +3 -3
  35. package/src/parser/inline/ruby.test.ts +5 -0
  36. package/src/parser/inline/ruby.ts +2 -2
  37. package/src/parser/inline/strong.ts +4 -4
  38. package/src/parser/node.ts +4 -4
  39. package/src/parser/source/escapable.ts +2 -1
  40. package/src/parser/source/str.ts +4 -4
  41. package/src/parser/source/text.ts +1 -1
  42. package/src/parser/source/unescapable.ts +2 -1
  43. package/src/parser/util.ts +7 -4
  44. package/src/parser/visibility.ts +41 -89
@@ -84,7 +84,6 @@ describe('Unit: parser/inline/link', () => {
84
84
  assert.deepStrictEqual(inspect(parser, input('[\\ ]{b}', new Context())), undefined);
85
85
  assert.deepStrictEqual(inspect(parser, input('[\\\n]{b}', new Context())), undefined);
86
86
  assert.deepStrictEqual(inspect(parser, input('[	]{b}', new Context())), undefined);
87
- assert.deepStrictEqual(inspect(parser, input('[‍]{b}', new Context())), undefined);
88
87
  assert.deepStrictEqual(inspect(parser, input('[[]{b}', new Context())), undefined);
89
88
  assert.deepStrictEqual(inspect(parser, input('[]]{b}', new Context())), undefined);
90
89
  assert.deepStrictEqual(inspect(parser, input('[a]{}', new Context())), undefined);
@@ -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, consume, precedence, state, constraint, surround, open, close, 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';
@@ -18,9 +18,9 @@ Object.setPrototypeOf(optspec, null);
18
18
  export const textlink: LinkParser.TextLinkParser = lazy(() => bind(
19
19
  subsequence([
20
20
  constraint(State.link, state(State.linkers, dup(surround(
21
- '[',
21
+ close('[', beforeNonblank),
22
22
  precedence(1,
23
- beforeNonblank(some(union([inline]), ']', [[']', 1]]))),
23
+ some(union([inline]), ']', [[']', 1]])),
24
24
  ']',
25
25
  true,
26
26
  [3 | Backtrack.common | Backtrack.link, 2 | Backtrack.ruby],
@@ -44,6 +44,7 @@ describe('Unit: parser/inline/mark', () => {
44
44
  assert.deepStrictEqual(inspect(parser, input('==a&Tab;==b====', new Context())), [['<mark id="mark::a_b=33Mw2l">a\t<mark>b</mark></mark>', '<a href="#mark::a_b=33Mw2l"></a>'], '']);
45
45
  assert.deepStrictEqual(inspect(parser, input('==a<wbr>==b====', new Context())), [['<mark id="mark::ab">a<wbr><mark>b</mark></mark>', '<a href="#mark::ab"></a>'], '']);
46
46
  assert.deepStrictEqual(inspect(parser, input('==*==a==*==', new Context())), [['<mark id="mark::a"><em><mark>a</mark></em></mark>', '<a href="#mark::a"></a>'], '']);
47
+ assert.deepStrictEqual(inspect(parser, input('====a== b==', new Context())), [['<mark id="mark::a_b"><mark>a</mark> b</mark>', '<a href="#mark::a_b"></a>'], '']);
47
48
  });
48
49
 
49
50
  });
@@ -9,16 +9,16 @@ 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
- precedence(0, recursion(Recursion.inline, repeat('==', surround(
12
+ precedence(0, recursion(Recursion.inline, repeat('==', beforeNonblank, surround(
13
13
  '',
14
- beforeNonblank(state(State.mark, some(union([inline]), '==', afterNonblank))),
14
+ state(State.mark, some(union([inline]), '==', afterNonblank)),
15
15
  '==',
16
16
  false, [],
17
17
  ([, bs], { buffer }) => buffer.import(bs),
18
18
  ([, bs], { buffer }) => bs && buffer.import(bs).push(new Node(Command.Cancel)) && buffer),
19
- (nodes, { id, state }) => {
19
+ (nodes, { id, state }, nest) => {
20
20
  const el = html('mark', defrag(unwrap(nodes)));
21
- if (state & State.linkers) return new List([new Node(el)]);
21
+ if (state & State.linkers || nest) return new List([new Node(el)]);
22
22
  define(el, { id: identity('mark', id, signature(el)) });
23
23
  return el.id
24
24
  ? new List([new Node(el), new Node(html('a', { href: `#${el.id}` }))])
@@ -49,7 +49,6 @@ describe('Unit: parser/inline/media', () => {
49
49
  assert.deepStrictEqual(inspect(parser, input('![\\ ]{b}', new Context())), undefined);
50
50
  assert.deepStrictEqual(inspect(parser, input('![\\\n]{b}', new Context())), undefined);
51
51
  assert.deepStrictEqual(inspect(parser, input('![&Tab;]{b}', new Context())), undefined);
52
- assert.deepStrictEqual(inspect(parser, input('![&zwj;]{b}', new Context())), undefined);
53
52
  assert.deepStrictEqual(inspect(parser, input('![&a;]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="&amp;a;"></a>'], '']);
54
53
  assert.deepStrictEqual(inspect(parser, input('![[]{b}', new Context())), undefined);
55
54
  assert.deepStrictEqual(inspect(parser, input('![]]{b}', new Context())), undefined);
@@ -53,7 +53,7 @@ export const media: MediaParser = lazy(() => constraint(State.media, open(
53
53
  ? new List<Node<List<Node<string>>>>([new Node(new List([new Node('')])), nodes.delete(nodes.head!)])
54
54
  : new List<Node<List<Node<string>>>>([new Node(new List([new Node(nodes.head!.value.foldl((acc, { value }) => acc + value, ''), nodes.head!.value.head?.flags)])), nodes.delete(nodes.last!)])),
55
55
  ([{ value: [{ value: text, flags }] }, { value: params }], context) => {
56
- if (flags & Flag.invisible) return;
56
+ if (flags & Flag.blank) return;
57
57
  if (text) {
58
58
  const tmp = text;
59
59
  text = text.trim();
@@ -1,7 +1,7 @@
1
1
  import { ReferenceParser } from '../inline';
2
2
  import { State, Backtrack, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
- import { union, subsequence, some, precedence, state, constraint, surround, isBacktrack, setBacktrack, lazy } from '../../combinator';
4
+ import { union, subsequence, some, precedence, state, constraint, surround, open, isBacktrack, setBacktrack, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
6
  import { textlink } from './link';
7
7
  import { str } from '../source';
@@ -10,11 +10,11 @@ import { unwrap, invalid } from '../util';
10
10
  import { html, defrag } from 'typed-dom/dom';
11
11
 
12
12
  export const reference: ReferenceParser = lazy(() => constraint(State.reference, surround(
13
- str('[['),
13
+ '[[',
14
14
  precedence(1, state(State.annotation | State.reference,
15
15
  subsequence([
16
16
  abbr,
17
- beforeNonblank(some(inline, ']', [[']', 1]])),
17
+ open(beforeNonblank, some(inline, ']', [[']', 1]])),
18
18
  ]))),
19
19
  ']]',
20
20
  false,
@@ -46,7 +46,12 @@ describe('Unit: parser/inline/ruby', () => {
46
46
  assert.deepStrictEqual(inspect(parser, input('[AB](a b c)', new Context())), [['<ruby>AB<rp>(</rp><rt>a b c</rt><rp>)</rp></ruby>'], '']);
47
47
  assert.deepStrictEqual(inspect(parser, input('[A B](ab)', new Context())), [['<ruby>A<rp>(</rp><rt>ab</rt><rp>)</rp>B<rt></rt></ruby>'], '']);
48
48
  assert.deepStrictEqual(inspect(parser, input('[A B](a b)', new Context())), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rp>(</rp><rt>b</rt><rp>)</rp></ruby>'], '']);
49
+ assert.deepStrictEqual(inspect(parser, input('[A B](a b )', new Context())), [['<ruby>A B<rp>(</rp><rt>a b</rt><rp>)</rp></ruby>'], '']);
50
+ assert.deepStrictEqual(inspect(parser, input('[ABC](a )', new Context())), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rt></rt>C<rt></rt></ruby>'], '']);
51
+ assert.deepStrictEqual(inspect(parser, input('[ABC](a )', new Context())), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rt></rt>C<rt></rt></ruby>'], '']);
52
+ assert.deepStrictEqual(inspect(parser, input('[ABC]( b)', new Context())), [['<ruby>A<rt></rt>B<rp>(</rp><rt>b</rt><rp>)</rp>C<rt></rt></ruby>'], '']);
49
53
  assert.deepStrictEqual(inspect(parser, input('[ABC]( b )', new Context())), [['<ruby>A<rt></rt>B<rp>(</rp><rt>b</rt><rp>)</rp>C<rt></rt></ruby>'], '']);
54
+ assert.deepStrictEqual(inspect(parser, input('[ABC]( c)', new Context())), [['<ruby>A<rt></rt>B<rt></rt>C<rp>(</rp><rt>c</rt><rp>)</rp></ruby>'], '']);
50
55
  assert.deepStrictEqual(inspect(parser, input('[ABC](a c)', new Context())), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rt></rt>C<rp>(</rp><rt>c</rt><rp>)</rp></ruby>'], '']);
51
56
  assert.deepStrictEqual(inspect(parser, input('[東方](とう ほう)', new Context())), [['<ruby>東<rp>(</rp><rt>とう</rt><rp>)</rp>方<rp>(</rp><rt>ほう</rt><rp>)</rp></ruby>'], '']);
52
57
  assert.deepStrictEqual(inspect(parser, input('[秦 \\  こころ](はた の こころ)', new Context())), [['<ruby>秦<rp>(</rp><rt>はた</rt><rp>)</rp> <rp>(</rp><rt>の</rt><rp>)</rp>こころ<rp>(</rp><rt>こころ</rt><rp>)</rp></ruby>'], '']);
@@ -4,7 +4,7 @@ import { List, Node } from '../../combinator/data/parser';
4
4
  import { inits, surround, setBacktrack, dup, lazy, bind } from '../../combinator';
5
5
  import { unsafehtmlentity } from './htmlentity';
6
6
  import { txt } from '../source';
7
- import { isTightNodeStart } from '../visibility';
7
+ import { isNonblankNodeStart } from '../visibility';
8
8
  import { unwrap } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
@@ -16,7 +16,7 @@ export const ruby: RubyParser = lazy(() => bind(
16
16
  [1 | Backtrack.common, 3 | Backtrack.ruby],
17
17
  ([, ns]) => {
18
18
  ns && ns.last?.value === '' && ns.pop();
19
- return isTightNodeStart(ns) ? ns : undefined;
19
+ return isNonblankNodeStart(ns) ? ns : undefined;
20
20
  })),
21
21
  dup(surround(
22
22
  '(', text, ')',
@@ -5,17 +5,17 @@ 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 { beforeNonblank, afterNonblank } from '../visibility';
8
+ import { beforeNonblankWith, 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('**', (source, position, range) => !source.startsWith('*', position + range)),
13
+ str('**', beforeNonblankWith(/(?!\*)/)),
14
14
  precedence(0, recursion(Recursion.inline,
15
- beforeNonblank(some(union([
15
+ some(union([
16
16
  some(inline, '*', afterNonblank),
17
17
  emphasis,
18
- ]))))),
18
+ ])))),
19
19
  str('**'),
20
20
  false, [],
21
21
  ([, bs]) => new List([new Node(html('strong', defrag(unwrap(bs))))]),
@@ -1,14 +1,14 @@
1
- import { invisibleHTMLEntityNames } from './api/normalize';
1
+ import { invisibleBlankHTMLEntityNames } from './api/normalize';
2
2
 
3
3
  export const enum Flag {
4
4
  none,
5
- invisible,
5
+ blank,
6
6
  }
7
7
 
8
- export const isInvisibleHTMLEntityName: (name: string) => boolean = eval([
8
+ export const isBlankHTMLEntityName: (name: string) => boolean = eval([
9
9
  'name => {',
10
10
  'switch(name){',
11
- invisibleHTMLEntityNames.map(name => `case '${name}':`).join(''),
11
+ invisibleBlankHTMLEntityNames.map(name => `case '${name}':`).join(''),
12
12
  'return true;',
13
13
  'default:',
14
14
  'return false;',
@@ -1,5 +1,6 @@
1
1
  import { EscapableSourceParser } from '../source';
2
2
  import { Command } from '../context';
3
+ import { Flag } from '../node';
3
4
  import { List, Node } from '../../combinator/data/parser';
4
5
  import { consume } from '../../combinator';
5
6
  import { next } from './text';
@@ -34,7 +35,7 @@ export const escsource: EscapableSourceParser = ({ context }) => {
34
35
  return new List();
35
36
  case '\n':
36
37
  context.linebreak ||= source.length - position;
37
- return new List([new Node(html('br'))]);
38
+ return new List([new Node(html('br'), Flag.blank)]);
38
39
  default:
39
40
  assert(char !== '\n');
40
41
  if (context.sequential) return new List([new Node(char)]);
@@ -1,10 +1,10 @@
1
1
  import { StrParser } from '../source';
2
2
  import { Parser, List, Node } from '../../combinator/data/parser';
3
- import { matcher } from '../../combinator';
3
+ import { matcher, tester } from '../../combinator/data/delimiter';
4
4
 
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);
5
+ export function str(pattern: string | RegExp, after?: string | RegExp): StrParser;
6
+ export function str(pattern: string | RegExp, after?: string | RegExp): Parser<string> {
7
+ return matcher(pattern, true, after ? tester(after, false) : undefined);
8
8
  }
9
9
 
10
10
  export function strs(pattern: string, limit?: number): StrParser;
@@ -33,7 +33,7 @@ export const text: TextParser = input => {
33
33
  return new List();
34
34
  case '\n':
35
35
  context.linebreak ||= source.length - position;
36
- return new List([new Node(html('br'), Flag.invisible)]);
36
+ return new List([new Node(html('br'), Flag.blank)]);
37
37
  default:
38
38
  assert(char !== '\n');
39
39
  if (context.sequential) return new List([new Node(char)]);
@@ -1,5 +1,6 @@
1
1
  import { UnescapableSourceParser } from '../source';
2
2
  import { Command } from '../context';
3
+ import { Flag } from '../node';
3
4
  import { List, Node } from '../../combinator/data/parser';
4
5
  import { consume } from '../../combinator';
5
6
  import { nonWhitespace, canSkip, next } from './text';
@@ -22,7 +23,7 @@ export const unescsource: UnescapableSourceParser = ({ context }) => {
22
23
  return new List();
23
24
  case '\n':
24
25
  context.linebreak ||= source.length - position;
25
- return new List([new Node(html('br'))]);
26
+ return new List([new Node(html('br'), Flag.blank)]);
26
27
  default:
27
28
  assert(char !== '\n');
28
29
  if (context.sequential) return new List([new Node(char)]);
@@ -1,4 +1,5 @@
1
1
  import { Parser, Result, List, Node, failsafe } from '../combinator/data/parser';
2
+ import { tester } from '../combinator/data/delimiter';
2
3
  import { Context, Command } from './context';
3
4
  import { min } from 'spica/alias';
4
5
  import { define } from 'typed-dom/dom';
@@ -10,8 +11,8 @@ export function* unwrap<N>(nodes: List<Node<N>> | undefined): Iterable<N> {
10
11
  }
11
12
  }
12
13
 
13
- export function repeat<P extends Parser<HTMLElement | string, Context>>(symbol: string, parser: P, cons: (nodes: List<Node<Parser.Node<P>>>, context: Parser.Context<P>) => List<Node<Parser.Node<P>>>, termination?: (acc: List<Node<Parser.Node<P>>>, context: Context, prefix: number, postfix: number, state: boolean) => Result<string | Parser.Node<P>>): P;
14
- export function repeat<N extends HTMLElement | string>(symbol: string, parser: Parser<N>, cons: (nodes: List<Node<N>>, context: Context) => List<Node<N>>, termination: (acc: List<Node<N>>, context: Context, prefix: number, postfix: number, state: boolean) => Result<string | N, Context> = (nodes, context, prefix, postfix) => {
14
+ export function repeat<P extends Parser<HTMLElement | string, Context>>(symbol: string, after: string | RegExp, parser: P, cons: (nodes: List<Node<Parser.Node<P>>>, context: Parser.Context<P>, nest: boolean) => List<Node<Parser.Node<P>>>, termination?: (acc: List<Node<Parser.Node<P>>>, context: Context, prefix: number, postfix: number, state: boolean) => Result<string | Parser.Node<P>>): P;
15
+ export function repeat<N extends HTMLElement | string>(symbol: string, after: string | RegExp, parser: Parser<N>, cons: (nodes: List<Node<N>>, context: Context, nest: boolean) => List<Node<N>>, termination: (acc: List<Node<N>>, context: Context, prefix: number, postfix: number, state: boolean) => Result<string | N, Context> = (nodes, context, prefix, postfix) => {
15
16
  const acc = new List<Node<string | N>>();
16
17
  if (prefix > 0) {
17
18
  acc.push(new Node(symbol[0].repeat(prefix)));
@@ -24,6 +25,7 @@ export function repeat<N extends HTMLElement | string>(symbol: string, parser: P
24
25
  }
25
26
  return acc;
26
27
  }): Parser<string | N, Context> {
28
+ const test = tester(after, false);
27
29
  return failsafe(input => {
28
30
  const { context } = input;
29
31
  const { source, position } = context;
@@ -32,10 +34,11 @@ export function repeat<N extends HTMLElement | string>(symbol: string, parser: P
32
34
  let i = symbol.length;
33
35
  for (; source[context.position + i] === source[context.position];) ++i;
34
36
  context.position += i;
37
+ if (test(input) === undefined) return;
35
38
  let state = false;
36
39
  for (; i >= symbol.length; i -= symbol.length) {
37
40
  if (nodes.length > 0 && source.startsWith(symbol, context.position)) {
38
- nodes = cons(nodes, context);
41
+ nodes = cons(nodes, context, i > symbol.length);
39
42
  context.position += symbol.length;
40
43
  continue;
41
44
  }
@@ -57,7 +60,7 @@ export function repeat<N extends HTMLElement | string>(symbol: string, parser: P
57
60
  state = true;
58
61
  continue;
59
62
  default:
60
- nodes = cons(nodes, context);
63
+ nodes = cons(nodes, context, i > symbol.length);
61
64
  state = true;
62
65
  continue;
63
66
  }
@@ -1,103 +1,73 @@
1
- import { Parser, Input, List, Node, failsafe } from '../combinator/data/parser';
2
- import { Context, Command } from './context';
1
+ import { Parser, List, Node, failsafe } from '../combinator/data/parser';
2
+ import { Command } from './context';
3
3
  import { Flag } from './node';
4
4
  import { convert, fmap } from '../combinator';
5
- import { invisibleHTMLEntityNames } from './api/normalize';
5
+ import { invisibleBlankHTMLEntityNames } from './api/normalize';
6
6
 
7
- namespace invisible {
7
+ namespace blank {
8
8
  export const line = new RegExp(
9
- /((?:^|\n)[^\S\n]*(?=\S))((?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)+(?=$|\n))/g.source
10
- .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
9
+ /((?:^|\n)[^\S\n]*(?=\S))((?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)+(?=$|\n))/g.source
10
+ .replace('IBHN', `(?:${invisibleBlankHTMLEntityNames.join('|')})`),
11
11
  'g');
12
12
  export const start = new RegExp(
13
- /(?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)+/y.source
14
- .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
13
+ /(?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)+/y.source
14
+ .replace('IBHN', `(?:${invisibleBlankHTMLEntityNames.join('|')})`),
15
15
  'y');
16
16
  export const unit = new RegExp(
17
- /(?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)/y.source
18
- .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
17
+ /(?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)/y.source
18
+ .replace('IBHN', `(?:${invisibleBlankHTMLEntityNames.join('|')})`),
19
19
  'y');
20
20
  }
21
21
 
22
22
  export function visualize<P extends Parser>(parser: P): P {
23
23
  return convert(
24
- source => source.replace(invisible.line, `$1${Command.Escape}$2`),
24
+ source => source.replace(blank.line, `$1${Command.Escape}$2`),
25
25
  parser);
26
26
  }
27
27
 
28
- export const afterNonblank = nonblankWith('');
28
+ export const beforeNonblank = beforeNonblankWith('');
29
+ export const afterNonblank = afterNonblankWith('');
29
30
  export function blankWith(starts: '\n', delimiter: string | RegExp): RegExp {
30
31
  return new RegExp([
31
32
  // 空行除去
32
33
  // 完全な空行はエスケープ済みなので再帰的バックトラックにはならない。
33
- String.raw`(?:${starts}(?:\\?\s|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr ?>)*)?`,
34
+ String.raw`(?:${starts}(?:\\?\s|&(?:${invisibleBlankHTMLEntityNames.join('|')});|<wbr ?>)*)?`,
34
35
  typeof delimiter === 'string'
35
36
  ? delimiter.replace(/[*+()\[\]]/g, '\\$&')
36
37
  : delimiter.source,
37
38
  ].join(''), 'y');
38
39
  }
39
- function nonblankWith(delimiter: string | RegExp): RegExp {
40
+ export function beforeNonblankWith(delimiter: string | RegExp): RegExp {
40
41
  return new RegExp([
41
- String.raw`(?<!\s|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr ?>)`,
42
42
  typeof delimiter === 'string'
43
43
  ? delimiter.replace(/[*+()\[\]]/g, '\\$&')
44
44
  : delimiter.source,
45
+ String.raw`(?!\\?\s|&(?:${invisibleBlankHTMLEntityNames.join('|')});|<wbr ?>)`,
45
46
  ].join(''), 'y');
46
47
  }
47
-
48
- //export function looseStart<P extends Parser<HTMLElement | string>>(parser: P): P;
49
- //export function looseStart<N extends HTMLElement | string>(parser: Parser<N>): Parser<N> {
50
- // return input =>
51
- // isLooseStart(input)
52
- // ? parser(input)
53
- // : undefined;
54
- //}
55
- //const isLooseStart = reduce(({ source, context }: Input<Context>): boolean => {
56
- // return isTightStart({ source: source.replace(invisible.start, ''), context });
57
- //}, ({ source }) => `${source}${Command.Separator}`);
58
-
59
- export function beforeNonblank<P extends Parser>(parser: P): P;
60
- export function beforeNonblank<N>(parser: Parser<N>): Parser<N, Context> {
61
- return input =>
62
- isTightStart(input)
63
- ? parser(input)
64
- : undefined;
65
- }
66
- function isTightStart(input: Input<Context>): boolean {
67
- const { context } = input;
68
- const { source, position } = context;
69
- if (position === source.length) return true;
70
- switch (source[position]) {
71
- case ' ':
72
- case ' ':
73
- case '\t':
74
- case '\n':
75
- return false;
76
- default:
77
- const reg = invisible.unit;
78
- reg.lastIndex = position;
79
- return !reg.test(source);
80
- }
48
+ function afterNonblankWith(delimiter: string | RegExp): RegExp {
49
+ return new RegExp([
50
+ String.raw`(?<!\s|&(?:${invisibleBlankHTMLEntityNames.join('|')});|<wbr ?>)`,
51
+ typeof delimiter === 'string'
52
+ ? delimiter.replace(/[*+()\[\]]/g, '\\$&')
53
+ : delimiter.source,
54
+ ].join(''), 'y');
81
55
  }
82
56
 
83
- export function isLooseNodeStart(nodes: List<Node<HTMLElement | string>>): boolean {
57
+ export function isNonblankFirstLine(nodes: List<Node<HTMLElement | string>>): boolean {
84
58
  if (nodes.length === 0) return true;
85
59
  for (const node of nodes) {
86
- if (isVisible(node)) return true;
87
- if (typeof node.value === 'object' && node.value.tagName === 'BR') break;
60
+ if (isNonblank(node)) return true;
61
+ if (node.flags & Flag.blank && typeof node.value === 'object' && node.value.tagName === 'BR') break;
88
62
  }
89
63
  return false;
90
64
  }
91
- export function isTightNodeStart(nodes: List<Node<HTMLElement | string>>): boolean {
65
+ export function isNonblankNodeStart(nodes: List<Node<HTMLElement | string>>): boolean {
92
66
  if (nodes.length === 0) return true;
93
- return isVisible(nodes.head!, 0);
67
+ return isNonblank(nodes.head!, 0);
94
68
  }
95
- //export function isTightNodeEnd(nodes: readonly (HTMLElement | string)[]): boolean {
96
- // if (nodes.length === 0) return true;
97
- // return isVisible(nodes.at(-1)!, -1);
98
- //}
99
- function isVisible({ value: node, flags }: Node<HTMLElement | string>, strpos?: number): boolean {
100
- if (flags & Flag.invisible) return false;
69
+ function isNonblank({ value: node, flags }: Node<HTMLElement | string>, strpos?: number): boolean {
70
+ if (flags & Flag.blank) return false;
101
71
  if (typeof node !== 'string') return true;
102
72
  const str = node && strpos !== undefined
103
73
  ? node[strpos >= 0 ? strpos : node.length + strpos]
@@ -124,7 +94,7 @@ function trimBlankStart<N>(parser: Parser<N>): Parser<N> {
124
94
  const { context } = input;
125
95
  const { source, position } = context;
126
96
  if (position === source.length) return;
127
- const reg = invisible.start;
97
+ const reg = blank.start;
128
98
  reg.lastIndex = position;
129
99
  reg.test(source);
130
100
  context.position = reg.lastIndex || position;
@@ -137,41 +107,23 @@ export function trimBlankEnd<P extends Parser<HTMLElement | string>>(parser: P):
137
107
  export function trimBlankEnd<N extends HTMLElement>(parser: Parser<N>): Parser<string | N> {
138
108
  return fmap(parser, trimBlankNodeEnd);
139
109
  }
140
- //export function trimBlankNode<N extends HTMLElement | string>(nodes: N[]): N[] {
141
- // return trimBlankNodeStart(trimBlankNodeEnd(nodes));
142
- //}
143
- //function trimBlankNodeStart<N extends HTMLElement | string>(nodes: N[]): N[] {
144
- // for (let node = nodes[0]; nodes.length > 0 && !isVisible(node = nodes[0], 0);) {
145
- // if (typeof node === 'string') {
146
- // const pos = node.trimStart().length;
147
- // if (pos > 0) {
148
- // nodes[0] = node.slice(-pos) as N;
149
- // break;
150
- // }
151
- // }
152
- // else if (nodes.length === 1 && node.className === 'indexer') {
153
- // break;
154
- // }
155
- // nodes.shift();
156
- // }
157
- // return nodes;
158
- //}
159
110
  export function trimBlankNodeEnd<N extends HTMLElement>(nodes: List<Node<string | N>>): List<Node<string | N>> {
160
- const skip = nodes.last && ~nodes.last.flags & Flag.invisible && typeof nodes.last.value === 'object'
111
+ const skip = nodes.last && ~nodes.last.flags & Flag.blank && typeof nodes.last.value === 'object'
161
112
  ? nodes.last.value.className === 'indexer'
162
113
  : false;
163
114
  for (let node = skip ? nodes.last?.prev : nodes.last; node;) {
164
- const visible = ~node.flags & Flag.invisible;
165
- if (visible && typeof node.value === 'string') {
166
- const str = node.value.trimEnd();
167
- if (str.length > 0) {
168
- node.value = str;
115
+ if (~node.flags & Flag.blank) {
116
+ if (typeof node.value === 'string') {
117
+ const str = node.value.trimEnd();
118
+ if (str.length > 0) {
119
+ node.value = str;
120
+ break;
121
+ }
122
+ }
123
+ else {
169
124
  break;
170
125
  }
171
126
  }
172
- else if (visible) {
173
- break;
174
- }
175
127
  const target = node;
176
128
  node = node.prev;
177
129
  nodes.delete(target);