securemark 0.295.9 → 0.296.1

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.
@@ -69,6 +69,8 @@ export class Context {
69
69
  // Objectの内部実装を利用する。
70
70
  // 探索木を直接使用する場合は探索速度が重要で挿入は相対的に少なく削除は不要かつ不確実であるため
71
71
  // AVL木が適当と思われる。
72
+ // メモリの局所性を得るために木ごとに最初の数十から数百byte分のノードをプールしノードが不足した場合は
73
+ // 使い捨てノードを追加またはテーブルに移行するとよいだろう。
72
74
  // 最大セグメントサイズ10KB内で探索コストが平均実行性能を圧迫するほど大きくなるとは考えにくいが
73
75
  // 探索コストを減らすにはバックトラック位置数が規定数を超えた場合一定区間ごとに探索木を分割する方法が考えられる。
74
76
  // 10KBの入力すべてを保持する探索木を1024文字ごとに分割するために必要なテーブルサイズは64bit*98=784byteとなる。
@@ -1,6 +1,4 @@
1
- import { input } from '../../combinator/data/parser';
2
- import { Context } from '../context';
3
- import { unsafehtmlentity } from '../inline/htmlentity';
1
+ import { html } from 'typed-dom/dom';
4
2
 
5
3
  const UNICODE_REPLACEMENT_CHARACTER = '\uFFFD';
6
4
  assert(UNICODE_REPLACEMENT_CHARACTER.trim());
@@ -15,7 +13,7 @@ function format(source: string): string {
15
13
 
16
14
  const invalid = new RegExp([
17
15
  /(?![\t\r\n])[\x00-\x1F\x7F]/g.source,
18
- /(?!\u200D)[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]/g.source,
16
+ /(?![\u200C\u200D])[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]/g.source,
19
17
  // 後読みが重い
20
18
  ///(?<![\u1820\u1821])\u180E/g.source,
21
19
  ///[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g.source,
@@ -26,7 +24,7 @@ function sanitize(source: string): string {
26
24
 
27
25
  // https://dev.w3.org/html5/html-author/charref
28
26
  // https://en.wikipedia.org/wiki/Whitespace_character
29
- export const invisibleHTMLEntityNames = [
27
+ const invisibleHTMLEntityNames = [
30
28
  'Tab',
31
29
  'NewLine',
32
30
  'NonBreakingSpace',
@@ -60,6 +58,15 @@ export const invisibleHTMLEntityNames = [
60
58
  'InvisibleComma',
61
59
  'ic',
62
60
  ] as readonly string[];
61
+ const parser = (el => (entity: string): string => {
62
+ if (entity === '&NewLine;') return entity;
63
+ el.innerHTML = entity;
64
+ return el.textContent!;
65
+ })(html('span'));
66
+ export const invisibleBlankHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames
67
+ .filter(name => parser(`&${name};`).trimStart() === '');
68
+ export const invisibleGraphHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames
69
+ .filter(name => parser(`&${name};`).trimStart() !== '');
63
70
  const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name => ![
64
71
  'Tab',
65
72
  'NewLine',
@@ -69,7 +76,7 @@ const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name =>
69
76
  'zwnj',
70
77
  ].includes(name));
71
78
  const unreadableEscapeCharacters = unreadableEscapeHTMLEntityNames
72
- .map(name => unsafehtmlentity(input(`&${name};`, new Context()))!.head!.value);
79
+ .map(name => parser(`&${name};`));
73
80
  assert(unreadableEscapeCharacters.length === unreadableEscapeHTMLEntityNames.length);
74
81
  assert(unreadableEscapeCharacters.every(c => c.length === 1));
75
82
  const unreadableEscapeCharacter = new RegExp(`[${unreadableEscapeCharacters.join('')}]`, 'g');
@@ -84,7 +91,7 @@ const unreadableSpecialCharacters = [
84
91
  // ZERO WIDTH SPACE
85
92
  '\u200B',
86
93
  // ZERO WIDTH NON-JOINER
87
- '\u200C',
94
+ //'\u200C',
88
95
  // ZERO WIDTH JOINER
89
96
  //'\u200D',
90
97
  // LEFT-TO-RIGHT MARK
@@ -34,6 +34,6 @@ export const cite: ReplyParser.CiteParser = line(fmap(
34
34
  ? define(node, { 'data-depth': `${quotes.length + 1}` }, node.innerText.slice(1))
35
35
  : node.slice(1),
36
36
  ]))),
37
- new Node(html('br'), Flag.invisible),
37
+ new Node(html('br'), Flag.blank),
38
38
  ]);
39
39
  }));
@@ -22,7 +22,7 @@ export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
22
22
  unescsource,
23
23
  ])))),
24
24
  (ns, { source, position }) => new List([
25
- new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br'), Flag.invisible),
25
+ new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br'), Flag.blank),
26
26
  new Node(html('span', { class: 'quote' }, defrag(unwrap(ns)))),
27
27
  ].reverse())),
28
28
  false));
@@ -21,6 +21,6 @@ export const reply: ReplyParser = block(validate(csyntax, fmap(
21
21
  visualize(fmap(some(inline), (ns, { source, position }) =>
22
22
  source[position - 1] === '\n'
23
23
  ? ns
24
- : ns.push(new Node(html('br'), Flag.invisible)) && ns)))
24
+ : ns.push(new Node(html('br'), Flag.blank)) && ns)))
25
25
  ])),
26
26
  ns => new List([new Node(html('p', defrag(unwrap(trimBlankNodeEnd(ns)))))]))));
@@ -19,7 +19,10 @@ describe('Unit: parser/inline/annotation', () => {
19
19
  assert.deepStrictEqual(inspect(parser, input('(([))', new Context())), undefined);
20
20
  assert.deepStrictEqual(inspect(parser, input('(([%))', new Context())), undefined);
21
21
  assert.deepStrictEqual(inspect(parser, input('(( ))', new Context())), undefined);
22
- assert.deepStrictEqual(inspect(parser, input('(( (a', new Context())), undefined);
22
+ assert.deepStrictEqual(inspect(parser, input('(( a))', new Context())), undefined);
23
+ assert.deepStrictEqual(inspect(parser, input('(( a ))', new Context())), undefined);
24
+ assert.deepStrictEqual(inspect(parser, input('((\\ a))', new Context())), undefined);
25
+ assert.deepStrictEqual(inspect(parser, input('((<wbr>a))', new Context())), undefined);
23
26
  assert.deepStrictEqual(inspect(parser, input('((\n))', new Context())), undefined);
24
27
  assert.deepStrictEqual(inspect(parser, input('((\na))', new Context())), undefined);
25
28
  assert.deepStrictEqual(inspect(parser, input('((\\\na))', new Context())), undefined);
@@ -35,10 +38,6 @@ describe('Unit: parser/inline/annotation', () => {
35
38
  });
36
39
 
37
40
  it('basic', () => {
38
- assert.deepStrictEqual(inspect(parser, input('(( a))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
39
- assert.deepStrictEqual(inspect(parser, input('(( a ))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
40
- assert.deepStrictEqual(inspect(parser, input('((\\ a))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
41
- assert.deepStrictEqual(inspect(parser, input('((<wbr>a))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
42
41
  assert.deepStrictEqual(inspect(parser, input('((a))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
43
42
  assert.deepStrictEqual(inspect(parser, input('((a ))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
44
43
  assert.deepStrictEqual(inspect(parser, input('((a ))', new Context())), [['<sup class="annotation"><span>a</span></sup>'], '']);
@@ -3,14 +3,14 @@ import { State, Backtrack } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
4
  import { union, some, precedence, state, constraint, surround, setBacktrack, lazy } from '../../combinator';
5
5
  import { inline } from '../inline';
6
- import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
6
+ import { beforeNonblank, trimBlankNodeEnd } from '../visibility';
7
7
  import { unwrap } from '../util';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
 
10
10
  export const annotation: AnnotationParser = lazy(() => constraint(State.annotation, surround(
11
11
  '((',
12
12
  precedence(1, state(State.annotation,
13
- trimBlankStart(some(union([inline]), ')', [[')', 1]])))),
13
+ beforeNonblank(some(union([inline]), ')', [[')', 1]])))),
14
14
  '))',
15
15
  false,
16
16
  [2, 1 | Backtrack.common, 3 | Backtrack.doublebracket],
@@ -5,7 +5,7 @@ import { Flag } from '../node';
5
5
  import { union, some, recursion, precedence, validate, surround, open, match, lazy } from '../../combinator';
6
6
  import { inline } from '../inline';
7
7
  import { str } from '../source';
8
- import { isLooseNodeStart, blankWith } from '../visibility';
8
+ import { isNonblankFirstLine, blankWith } from '../visibility';
9
9
  import { invalid, unwrap } from '../util';
10
10
  import { memoize } from 'spica/memoize';
11
11
  import { html as h, defrag } from 'typed-dom/dom';
@@ -29,7 +29,7 @@ export const html: HTMLParser = lazy(() => validate(/<[a-z]+(?=[ >])/yi,
29
29
  open(str(/ ?/y), str('>'), true),
30
30
  true, [],
31
31
  ([as, bs = new List(), cs], 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
+ 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.blank : Flag.none)]),
33
33
  ([as, bs = new List()], context) =>
34
34
  new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
35
35
  match(
@@ -85,7 +85,7 @@ function elem(tag: string, content: boolean, as: readonly string[], bs: List<Nod
85
85
  if (content) {
86
86
  if (cs.length === 0) return ielem('tag', `Missing the closing HTML tag "</${tag}>"`, context);
87
87
  if (bs.length === 0) return ielem('content', `Missing the content`, context);
88
- if (!isLooseNodeStart(bs)) return ielem('content', `Missing the visible content in the same line`, context);
88
+ if (!isNonblankFirstLine(bs)) return ielem('content', `Missing the visible content in the same line`, context);
89
89
  }
90
90
  const [attrs] = attributes('html', attrspecs[tag], as.slice(1, as.at(-1) === '>' ? -1 : as.length));
91
91
  if (/(?<!\S)invalid(?!\S)/.test(attrs['class'] ?? '')) return ielem('attribute', 'Invalid HTML attribute', context)
@@ -1,7 +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
+ import { Flag, isBlankHTMLEntityName } from '../node';
5
5
  import { union, surround, fmap } from '../../combinator';
6
6
  import { str } from '../source';
7
7
  import { invalid } from '../util';
@@ -15,7 +15,7 @@ export const unsafehtmlentity: UnsafeHTMLEntityParser = surround(
15
15
  new List([
16
16
  new Node(
17
17
  parser(as.head!.value + bs.head!.value + cs.head!.value),
18
- isinvisibleHTMLEntityName(bs.head!.value) ? Flag.invisible : Flag.none)
18
+ isBlankHTMLEntityName(bs.head!.value) ? Flag.blank : Flag.none)
19
19
  ]),
20
20
  ([as, bs]) =>
21
21
  new List([new Node(as.head!.value + (bs?.head?.value ?? ''))]));
@@ -76,6 +76,10 @@ describe('Unit: parser/inline/link', () => {
76
76
  assert.deepStrictEqual(inspect(parser, input('[ ]{ }', new Context())), undefined);
77
77
  assert.deepStrictEqual(inspect(parser, input('[ ]{b}', new Context())), undefined);
78
78
  assert.deepStrictEqual(inspect(parser, input('[ ]{b}', new Context())), undefined);
79
+ assert.deepStrictEqual(inspect(parser, input('[ a]{b}', new Context())), undefined);
80
+ assert.deepStrictEqual(inspect(parser, input('[ a ]{b}', new Context())), undefined);
81
+ assert.deepStrictEqual(inspect(parser, input('[\\ a]{b}', new Context())), undefined);
82
+ assert.deepStrictEqual(inspect(parser, input('[ \\ a]{b}', new Context())), undefined);
79
83
  assert.deepStrictEqual(inspect(parser, input('[\n]{b}', new Context())), undefined);
80
84
  assert.deepStrictEqual(inspect(parser, input('[\\ ]{b}', new Context())), undefined);
81
85
  assert.deepStrictEqual(inspect(parser, input('[\\\n]{b}', new Context())), undefined);
@@ -131,10 +135,6 @@ describe('Unit: parser/inline/link', () => {
131
135
  assert.deepStrictEqual(inspect(parser, input('[]{^/b}', new Context({ host: new URL('/0.a0', location.origin) }))), [[`<a class="url" href="/b">^/b</a>`], '']);
132
136
  assert.deepStrictEqual(inspect(parser, input('[]{^/b}', new Context({ host: new URL('/0.0', location.origin) }))), [[`<a class="url" href="/0.0/b">^/b</a>`], '']);
133
137
  assert.deepStrictEqual(inspect(parser, input('[]{^/b}', new Context({ host: new URL('/0.0,0.0,0z', location.origin) }))), [[`<a class="url" href="/0.0,0.0,0z/b">^/b</a>`], '']);
134
- assert.deepStrictEqual(inspect(parser, input('[ a]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
135
- assert.deepStrictEqual(inspect(parser, input('[ a ]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
136
- assert.deepStrictEqual(inspect(parser, input('[\\ a]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
137
- assert.deepStrictEqual(inspect(parser, input('[ \\ a]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
138
138
  assert.deepStrictEqual(inspect(parser, input('[a ]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
139
139
  assert.deepStrictEqual(inspect(parser, input('[a ]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
140
140
  assert.deepStrictEqual(inspect(parser, input('[a]{b}', new Context())), [['<a class="link" href="b">a</a>'], '']);
@@ -5,7 +5,7 @@ import { union, inits, sequence, subsequence, some, consume, precedence, state,
5
5
  import { inline, media, shortmedia } from '../inline';
6
6
  import { attributes } from './html';
7
7
  import { str } from '../source';
8
- import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
8
+ import { beforeNonblank, trimBlankNodeEnd } from '../visibility';
9
9
  import { unwrap, invalid, stringify } from '../util';
10
10
  import { ReadonlyURL } from 'spica/url';
11
11
  import { html, define, defrag } from 'typed-dom/dom';
@@ -20,7 +20,7 @@ export const textlink: LinkParser.TextLinkParser = lazy(() => bind(
20
20
  constraint(State.link, state(State.linkers, dup(surround(
21
21
  '[',
22
22
  precedence(1,
23
- trimBlankStart(some(union([inline]), ']', [[']', 1]]))),
23
+ beforeNonblank(some(union([inline]), ']', [[']', 1]]))),
24
24
  ']',
25
25
  true,
26
26
  [3 | Backtrack.common | Backtrack.link, 2 | Backtrack.ruby],
@@ -41,6 +41,10 @@ describe('Unit: parser/inline/media', () => {
41
41
  assert.deepStrictEqual(inspect(parser, input('![ ]{}', new Context())), undefined);
42
42
  assert.deepStrictEqual(inspect(parser, input('![ ]{b}', new Context())), undefined);
43
43
  assert.deepStrictEqual(inspect(parser, input('![ ]{b}', new Context())), undefined);
44
+ assert.deepStrictEqual(inspect(parser, input('![ a]{b}', new Context())), undefined);
45
+ assert.deepStrictEqual(inspect(parser, input('![ a ]{b}', new Context())), undefined);
46
+ assert.deepStrictEqual(inspect(parser, input('![\\ a]{b}', new Context())), undefined);
47
+ assert.deepStrictEqual(inspect(parser, input('![ \\ a]{b}', new Context())), undefined);
44
48
  assert.deepStrictEqual(inspect(parser, input('![\n]{b}', new Context())), undefined);
45
49
  assert.deepStrictEqual(inspect(parser, input('![\\ ]{b}', new Context())), undefined);
46
50
  assert.deepStrictEqual(inspect(parser, input('![\\\n]{b}', new Context())), undefined);
@@ -79,8 +83,6 @@ describe('Unit: parser/inline/media', () => {
79
83
  assert.deepStrictEqual(inspect(parser, input('![]{?/../}', new Context())), [[`<a href="?/../" target="_blank"><img class="media" data-src="?/../" alt="?/../"></a>`], '']);
80
84
  assert.deepStrictEqual(inspect(parser, input('![]{#/../}', new Context())), [[`<a href="#/../" target="_blank"><img class="media" data-src="#/../" alt="#/../"></a>`], '']);
81
85
  assert.deepStrictEqual(inspect(parser, input('![]{^/b}', new Context())), [[`<a href="/b" target="_blank"><img class="media" data-src="/b" alt="^/b"></a>`], '']);
82
- assert.deepStrictEqual(inspect(parser, input('![ a]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
83
- assert.deepStrictEqual(inspect(parser, input('![ a ]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
84
86
  assert.deepStrictEqual(inspect(parser, input('![a ]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
85
87
  assert.deepStrictEqual(inspect(parser, input('![a ]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
86
88
  assert.deepStrictEqual(inspect(parser, input('![a b]{c}', new Context())), [['<a href="c" target="_blank"><img class="media" data-src="c" alt="a b"></a>'], '']);
@@ -1,6 +1,7 @@
1
1
  import { MediaParser } from '../inline';
2
2
  import { State, Recursion, Backtrack, Command } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
+ import { Flag } from '../node';
4
5
  import { union, inits, tails, some, consume, recursion, precedence, constraint, surround, open, setBacktrack, dup, lazy, fmap, bind } from '../../combinator';
5
6
  import { uri, option as linkoption, resolve, decode, parse } from './link';
6
7
  import { attributes } from './html';
@@ -50,10 +51,14 @@ export const media: MediaParser = lazy(() => constraint(State.media, open(
50
51
  nodes =>
51
52
  nodes.length === 1
52
53
  ? new List<Node<List<Node<string>>>>([new Node(new List([new Node('')])), nodes.delete(nodes.head!)])
53
- : new List<Node<List<Node<string>>>>([new Node(new List([new Node(nodes.head!.value.foldl((acc, { value }) => acc + value, ''))])), nodes.delete(nodes.last!)])),
54
- ([{ value: [{ value: text }] }, { value: params }], context) => {
55
- if (text && text.trimStart() === '') return;
56
- text = text.trim();
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
+ ([{ value: [{ value: text, flags }] }, { value: params }], context) => {
56
+ if (flags & Flag.blank) return;
57
+ if (text) {
58
+ const tmp = text;
59
+ text = text.trim();
60
+ if (text === '' || text[0] !== tmp[0]) return;
61
+ }
57
62
  consume(100, context);
58
63
  if (params.last!.value === Command.Cancel) {
59
64
  params.pop();
@@ -19,7 +19,10 @@ describe('Unit: parser/inline/reference', () => {
19
19
  assert.deepStrictEqual(inspect(parser, input('[[(]]', new Context())), undefined);
20
20
  assert.deepStrictEqual(inspect(parser, input('[[[%]]', new Context())), undefined);
21
21
  assert.deepStrictEqual(inspect(parser, input('[[ ]]', new Context())), undefined);
22
- assert.deepStrictEqual(inspect(parser, input('[[ [a', new Context())), undefined);
22
+ assert.deepStrictEqual(inspect(parser, input('[[ a]]', new Context())), undefined);
23
+ assert.deepStrictEqual(inspect(parser, input('[[ a ]]', new Context())), undefined);
24
+ assert.deepStrictEqual(inspect(parser, input('[[\\ a]]', new Context())), undefined);
25
+ assert.deepStrictEqual(inspect(parser, input('[[<wbr>a]]', new Context())), undefined);
23
26
  assert.deepStrictEqual(inspect(parser, input('[[\n]]', new Context())), undefined);
24
27
  assert.deepStrictEqual(inspect(parser, input('[[\na]]', new Context())), undefined);
25
28
  assert.deepStrictEqual(inspect(parser, input('[[\\\na]]', new Context())), undefined);
@@ -35,10 +38,6 @@ describe('Unit: parser/inline/reference', () => {
35
38
  });
36
39
 
37
40
  it('basic', () => {
38
- assert.deepStrictEqual(inspect(parser, input('[[ a]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
39
- assert.deepStrictEqual(inspect(parser, input('[[ a ]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
40
- assert.deepStrictEqual(inspect(parser, input('[[\\ a]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
41
- assert.deepStrictEqual(inspect(parser, input('[[<wbr>a]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
42
41
  assert.deepStrictEqual(inspect(parser, input('[[a]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
43
42
  assert.deepStrictEqual(inspect(parser, input('[[a ]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
44
43
  assert.deepStrictEqual(inspect(parser, input('[[a ]]', new Context())), [['<sup class="reference"><span>a</span></sup>'], '']);
@@ -81,7 +80,7 @@ describe('Unit: parser/inline/reference', () => {
81
80
  assert.deepStrictEqual(inspect(parser, input('[[^A| b]]', new Context())), [['<sup class="reference" data-abbr="A"><span>b</span></sup>'], '']);
82
81
  assert.deepStrictEqual(inspect(parser, input('[[^A| ]]', new Context())), undefined);
83
82
  assert.deepStrictEqual(inspect(parser, input('[[^A|<wbr>]]', new Context())), undefined);
84
- assert.deepStrictEqual(inspect(parser, input('[[^A|<wbr>b]]', new Context())), [['<sup class="reference" data-abbr="A"><span>b</span></sup>'], '']);
83
+ assert.deepStrictEqual(inspect(parser, input('[[^A|<wbr>b]]', new Context())), undefined);
85
84
  assert.deepStrictEqual(inspect(parser, input('[[^A|^]]', new Context())), [['<sup class="reference" data-abbr="A"><span>^</span></sup>'], '']);
86
85
  assert.deepStrictEqual(inspect(parser, input('[[^A|^B]]', new Context())), [['<sup class="reference" data-abbr="A"><span>^B</span></sup>'], '']);
87
86
  assert.deepStrictEqual(inspect(parser, input('[[^1]]', new Context())), [['<sup class="invalid"><span>^1</span></sup>'], '']);
@@ -5,7 +5,7 @@ import { union, subsequence, some, precedence, state, constraint, surround, isBa
5
5
  import { inline } from '../inline';
6
6
  import { textlink } from './link';
7
7
  import { str } from '../source';
8
- import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
8
+ import { beforeNonblank, trimBlankNodeEnd } from '../visibility';
9
9
  import { unwrap, invalid } from '../util';
10
10
  import { html, defrag } from 'typed-dom/dom';
11
11
 
@@ -14,7 +14,7 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
14
14
  precedence(1, state(State.annotation | State.reference,
15
15
  subsequence([
16
16
  abbr,
17
- trimBlankStart(some(inline, ']', [[']', 1]])),
17
+ 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, ')',
@@ -1,10 +1,17 @@
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 function isinvisibleHTMLEntityName(name: string): boolean {
9
- return invisibleHTMLEntityNames.includes(name);
10
- }
8
+ export const isBlankHTMLEntityName: (name: string) => boolean = eval([
9
+ 'name => {',
10
+ 'switch(name){',
11
+ invisibleBlankHTMLEntityNames.map(name => `case '${name}':`).join(''),
12
+ 'return true;',
13
+ 'default:',
14
+ 'return false;',
15
+ '}',
16
+ '}',
17
+ ].join(''));
@@ -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)]);
@@ -24,19 +24,16 @@ export const text: TextParser = input => {
24
24
  assert(char !== Command.Escape);
25
25
  return new List();
26
26
  default:
27
- const flags = source[position + 1].trimStart()
28
- ? Flag.none
29
- : Flag.invisible;
30
27
  consume(1, context);
31
28
  context.position += 1;
32
- return new List([new Node(source.slice(position + 1, context.position), flags)]);
29
+ return new List([new Node(source.slice(position + 1, context.position))]);
33
30
  }
34
31
  case '\r':
35
32
  consume(-1, context);
36
33
  return new List();
37
34
  case '\n':
38
35
  context.linebreak ||= source.length - position;
39
- return new List([new Node(html('br'), Flag.invisible)]);
36
+ return new List([new Node(html('br'), Flag.blank)]);
40
37
  default:
41
38
  assert(char !== '\n');
42
39
  if (context.sequential) return new List([new Node(char)]);
@@ -57,9 +54,7 @@ export const text: TextParser = input => {
57
54
  context.position += i - 1;
58
55
  const linestart = position === 0 || source[position - 1] === '\n';
59
56
  if (position === context.position || s && !linestart || lineend) return new List();
60
- const str = source.slice(position, context.position);
61
- const flags = str.length === 1 && str.trimStart() === '' ? Flag.invisible : Flag.none;
62
- return new List([new Node(str, flags)]);
57
+ return new List([new Node(source.slice(position, context.position))]);
63
58
  }
64
59
  };
65
60
 
@@ -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)]);