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.
- package/CHANGELOG.md +8 -0
- package/design.md +1 -1
- package/dist/index.js +84 -99
- package/package.json +1 -1
- package/src/combinator/control/constraint/block.ts +2 -2
- package/src/combinator/control/constraint/line.ts +5 -5
- package/src/combinator/control/manipulation/fence.ts +4 -4
- package/src/combinator/data/parser.ts +2 -0
- package/src/parser/api/normalize.ts +14 -7
- package/src/parser/block/reply/cite.ts +1 -1
- package/src/parser/block/reply/quote.ts +1 -1
- package/src/parser/block/reply.ts +1 -1
- package/src/parser/inline/annotation.test.ts +4 -5
- package/src/parser/inline/annotation.ts +2 -2
- package/src/parser/inline/html.ts +3 -3
- package/src/parser/inline/htmlentity.ts +2 -2
- package/src/parser/inline/link.test.ts +4 -4
- package/src/parser/inline/link.ts +2 -2
- package/src/parser/inline/media.test.ts +4 -2
- package/src/parser/inline/media.ts +9 -4
- package/src/parser/inline/reference.test.ts +5 -6
- package/src/parser/inline/reference.ts +2 -2
- package/src/parser/inline/ruby.test.ts +5 -0
- package/src/parser/inline/ruby.ts +2 -2
- package/src/parser/node.ts +12 -5
- package/src/parser/source/escapable.ts +2 -1
- package/src/parser/source/text.ts +3 -8
- package/src/parser/source/unescapable.ts +2 -1
- package/src/parser/visibility.ts +39 -63
|
@@ -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 {
|
|
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
|
-
/(
|
|
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
|
-
|
|
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 === '
') 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 =>
|
|
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
|
|
@@ -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.
|
|
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.
|
|
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('((
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
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 (!
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 (
|
|
56
|
-
|
|
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('[[
|
|
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())),
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
|
19
|
+
return isNonblankNodeStart(ns) ? ns : undefined;
|
|
20
20
|
})),
|
|
21
21
|
dup(surround(
|
|
22
22
|
'(', text, ')',
|
package/src/parser/node.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { invisibleBlankHTMLEntityNames } from './api/normalize';
|
|
2
2
|
|
|
3
3
|
export const enum Flag {
|
|
4
4
|
none,
|
|
5
|
-
|
|
5
|
+
blank,
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
|
|
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)
|
|
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.
|
|
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
|
-
|
|
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)]);
|