securemark 0.293.2 → 0.293.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.293.2",
3
+ "version": "0.293.3",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -12,12 +12,17 @@ describe('Unit: combinator/indent', () => {
12
12
  assert.deepStrictEqual(inspect(parser(input(' ', ctx)), ctx), undefined);
13
13
  assert.deepStrictEqual(inspect(parser(input(' ', ctx)), ctx), undefined);
14
14
  assert.deepStrictEqual(inspect(parser(input('a ', ctx)), ctx), undefined);
15
- assert.deepStrictEqual(inspect(parser(input(' a\n', ctx)), ctx), [['a'], '']);
15
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
16
16
  assert.deepStrictEqual(inspect(parser(input(' a ', ctx)), ctx), [['a '], '']);
17
+ assert.deepStrictEqual(inspect(parser(input(' a\n', ctx)), ctx), [['a'], '']);
17
18
  assert.deepStrictEqual(inspect(parser(input(' a \n', ctx)), ctx), [['a '], '']);
18
19
  assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
20
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
21
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [['a'], '']);
22
+ assert.deepStrictEqual(inspect(parser(input(' a', ctx)), ctx), [[' a'], '']);
19
23
  assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a\na'], '']);
20
24
  assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a\n a'], '']);
25
+ assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a\n a'], '']);
21
26
  assert.deepStrictEqual(inspect(parser(input(' a\n a', ctx)), ctx), [['a'], ' a']);
22
27
  assert.deepStrictEqual(inspect(parser(input(' \ta', ctx)), ctx), [['\ta'], '']);
23
28
  assert.deepStrictEqual(inspect(parser(input('\ta', ctx)), ctx), [['a'], '']);
@@ -13,7 +13,7 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
13
13
  if (typeof opener === 'function') {
14
14
  separation = parser as boolean;
15
15
  parser = opener;
16
- opener = /([ \t])\1*/y;
16
+ opener = / {1,4}|\t{1,2}/y;
17
17
  }
18
18
  assert(!opener.flags.match(/[gm]/) && opener.sticky && !opener.source.startsWith('^'));
19
19
  assert(parser);
@@ -157,7 +157,8 @@ export function setBacktrack(
157
157
  position: number,
158
158
  length: number = 1,
159
159
  ): void {
160
- const { source } = context;
160
+ const { source, state = 0 } = context;
161
+ if (state === 0) return;
161
162
  if (position === source.length) return;
162
163
  if (length === 0) return;
163
164
  for (const backtrack of backtracks) {
@@ -7,26 +7,24 @@ interface Delimiter {
7
7
  readonly signature: number | string;
8
8
  readonly matcher: (input: Input) => boolean | undefined;
9
9
  readonly precedence: number;
10
- readonly linebreakable: boolean;
11
10
  state: boolean;
12
11
  }
13
12
 
14
13
  export class Delimiters {
15
14
  // 手間を惜しまなければ規定のパターンはすべて配列のインデクスに変換可能。
16
- public static signature(pattern: string | RegExp | undefined, linebreakable: boolean): number | string {
15
+ public static signature(pattern: string | RegExp | undefined): number | string {
17
16
  switch (typeof pattern) {
18
17
  case 'undefined':
19
- return +linebreakable;
18
+ return 1 << 7;
20
19
  case 'string':
21
20
  assert(pattern !== '\x00');
22
21
  if (pattern.length === 1) {
23
22
  const code = pattern.charCodeAt(0);
24
- // 使用中のパターンの8ビット目が空いてるのでひとまずこうしとく
25
- if ((code & 1 << 7) === 0) return code | +linebreakable << 7;
23
+ return code;
26
24
  }
27
- return `s:${pattern}:${+linebreakable}`;
25
+ return `s:${pattern}`;
28
26
  case 'object':
29
- return `r/${pattern.source}/${+linebreakable}`;
27
+ return `r/${pattern.source}`;
30
28
  }
31
29
  }
32
30
  public static matcher(pattern: string | RegExp | undefined): (input: Input<Ctx>) => true | undefined {
@@ -61,14 +59,13 @@ export class Delimiters {
61
59
  readonly signature: number | string;
62
60
  readonly matcher: (input: Input) => boolean | undefined;
63
61
  readonly precedence: number;
64
- readonly linebreakable: boolean;
65
62
  }[]
66
63
  ): void {
67
64
  const { delimiters, stack } = this;
68
65
  // シグネチャ数以下
69
66
  assert(delimiters.length < 100);
70
67
  for (let i = 0; i < delims.length; ++i) {
71
- const { signature, matcher, precedence, linebreakable } = delims[i];
68
+ const { signature, matcher, precedence } = delims[i];
72
69
  const memory = this.registry(signature);
73
70
  const index = memory[0]?.index ?? delimiters.length;
74
71
  assert(memory.length === 0 || precedence === delimiters[index].precedence);
@@ -79,7 +76,6 @@ export class Delimiters {
79
76
  signature,
80
77
  matcher,
81
78
  precedence,
82
- linebreakable,
83
79
  state: true,
84
80
  };
85
81
  delimiters[index] = delimiter;
@@ -134,14 +130,13 @@ export class Delimiters {
134
130
  }
135
131
  }
136
132
  public match(input: Input): boolean {
137
- const { precedence = 0, linebreak = 0 } = input.context;
133
+ const { precedence = 0 } = input.context;
138
134
  const { delimiters } = this;
139
135
  for (let i = delimiters.length; i--;) {
140
136
  const delimiter = delimiters[i];
141
137
  if (delimiter.precedence <= precedence || !delimiter.state) continue;
142
138
  switch (delimiter.matcher(input)) {
143
139
  case true:
144
- if (!delimiter.linebreakable && linebreak > 0) return false;
145
140
  return true;
146
141
  case false:
147
142
  return false;
@@ -1,8 +1,7 @@
1
1
  import { Parser, eval } from '../parser';
2
2
  import { Delimiters } from './context/delimiter';
3
- import { unshift, push } from 'spica/array';
4
3
 
5
- type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number, linebreak?: boolean];
4
+ type DelimiterOption = readonly [delimiter: string | RegExp, precedence: number];
6
5
 
7
6
  export function some<P extends Parser<unknown>>(parser: P, limit?: number): P;
8
7
  export function some<P extends Parser<unknown>>(parser: P, end?: string | RegExp, delimiters?: readonly DelimiterOption[], limit?: number): P;
@@ -10,11 +9,10 @@ export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delim
10
9
  if (typeof end === 'number') return some(parser, undefined, delimiters, end);
11
10
  assert(parser);
12
11
  const match = Delimiters.matcher(end);
13
- const delims = delimiters.map(([delimiter, precedence, linebreakable = true]) => ({
14
- signature: Delimiters.signature(delimiter, linebreakable),
12
+ const delims = delimiters.map(([delimiter, precedence]) => ({
13
+ signature: Delimiters.signature(delimiter),
15
14
  matcher: Delimiters.matcher(delimiter),
16
15
  precedence,
17
- linebreakable,
18
16
  }));
19
17
  return input => {
20
18
  const { context } = input;
@@ -32,9 +30,7 @@ export function some<N>(parser: Parser<N>, end?: string | RegExp | number, delim
32
30
  const result = parser(input);
33
31
  if (result === undefined) break;
34
32
  nodes = nodes
35
- ? nodes.length < eval(result).length / 8
36
- ? unshift(nodes, eval(result))
37
- : push(nodes, eval(result))
33
+ ? (nodes.push(...eval(result)), nodes)
38
34
  : eval(result);
39
35
  if (limit >= 0 && context.position - position > limit) break;
40
36
  }
@@ -32,6 +32,8 @@ describe('Unit: parser/block/olist', () => {
32
32
  assert.deepStrictEqual(inspect(parser('(1)'), ctx), undefined);
33
33
  assert.deepStrictEqual(inspect(parser('(1)\n'), ctx), undefined);
34
34
  assert.deepStrictEqual(inspect(parser('(1)\n(1) a'), ctx), undefined);
35
+ assert.deepStrictEqual(inspect(parser('I. '), ctx), undefined);
36
+ assert.deepStrictEqual(inspect(parser('A. '), ctx), undefined);
35
37
  assert.deepStrictEqual(inspect(parser('(I) '), ctx), undefined);
36
38
  assert.deepStrictEqual(inspect(parser('(A) '), ctx), undefined);
37
39
  assert.deepStrictEqual(inspect(parser(' 1. '), ctx), undefined);
@@ -117,12 +119,12 @@ describe('Unit: parser/block/olist', () => {
117
119
  });
118
120
 
119
121
  it('type', () => {
120
- assert.deepStrictEqual(inspect(parser('i. '), ctx), [['<ol type="i" data-type="lower-roman"><li></li></ol>'], '']);
121
- assert.deepStrictEqual(inspect(parser('a. '), ctx), [['<ol type="a" data-type="lower-alpha"><li></li></ol>'], '']);
122
- assert.deepStrictEqual(inspect(parser('I. '), ctx), [['<ol type="I" data-type="upper-roman"><li></li></ol>'], '']);
123
- assert.deepStrictEqual(inspect(parser('A. '), ctx), [['<ol type="A" data-type="upper-alpha"><li></li></ol>'], '']);
124
- assert.deepStrictEqual(inspect(parser('a. \n1.\nc'), ctx), [['<ol type="a" data-type="lower-alpha"><li></li><li data-marker="1."></li><li data-marker="c."></li></ol>'], '']);
125
- assert.deepStrictEqual(inspect(parser('i. 1'), ctx), [['<ol type="i" data-type="lower-roman"><li id="index::1">1</li></ol>'], '']);
122
+ assert.deepStrictEqual(inspect(parser('1. \n i. '), ctx), [['<ol><li><br><ol type="i" data-type="lower-roman"><li></li></ol></li></ol>'], '']);
123
+ assert.deepStrictEqual(inspect(parser('1. \n a. '), ctx), [['<ol><li><br><ol type="a" data-type="lower-alpha"><li></li></ol></li></ol>'], '']);
124
+ assert.deepStrictEqual(inspect(parser('1. \n I. '), ctx), [['<ol><li><br><ol type="I" data-type="upper-roman"><li></li></ol></li></ol>'], '']);
125
+ assert.deepStrictEqual(inspect(parser('1. \n A. '), ctx), [['<ol><li><br><ol type="A" data-type="upper-alpha"><li></li></ol></li></ol>'], '']);
126
+ assert.deepStrictEqual(inspect(parser('1. \n a. \n 1.\n c'), ctx), [['<ol><li><br><ol type="a" data-type="lower-alpha"><li></li><li data-marker="1."></li><li data-marker="c."></li></ol></li></ol>'], '']);
127
+ assert.deepStrictEqual(inspect(parser('1. \n i. 1'), ctx), [['<ol><li><br><ol type="i" data-type="lower-roman"><li id="index::1">1</li></ol></li></ol>'], '']);
126
128
  });
127
129
 
128
130
  it('checkbox', () => {
@@ -16,8 +16,8 @@ const openers = {
16
16
 
17
17
  export const olist: OListParser = lazy(() => block(validate(
18
18
  new RegExp([
19
- /([0-9]+|[a-z]+|[A-Z]+)(?:-[0-9]+)*\. /y.source,
20
- /\(([0-9]+|[a-z]+)\)(?:-[0-9]+)* /y.source,
19
+ /(?:[0-9]+)(?:-[0-9]+)*\. /y.source,
20
+ /\((?:[0-9]+)\)(?:-[0-9]+)* /y.source,
21
21
  ].join('|'), 'y'),
22
22
  olist_)));
23
23
 
@@ -58,51 +58,53 @@ export const block: BlockParser = reset(
58
58
  input => {
59
59
  const { context: { source, position } } = input;
60
60
  if (position === source.length) return;
61
- switch (source.slice(position, position + 3)) {
62
- case '===':
63
- return pagebreak(input);
64
- case '~~~':
65
- return extension(input);
66
- case '```':
67
- return codeblock(input);
68
- }
69
- switch (source.slice(position, position + 2)) {
70
- case '$$':
71
- return mathblock(input);
72
- case '[$':
73
- return extension(input);
74
- case '[!':
61
+ const fst = source[position];
62
+ switch (fst) {
63
+ case '=':
64
+ if (source.startsWith('===', position)) return pagebreak(input);
65
+ break;
66
+ case '`':
67
+ if (source.startsWith('```', position)) return codeblock(input);
68
+ break;
69
+ case '~':
70
+ if (source.startsWith('~~~', position)) return extension(input);
71
+ if (source[position + 1] === ' ') return dlist(input);
72
+ break;
73
+ case '-':
74
+ if (source[position + 1] === ' ') return ulist(input) || ilist(input);
75
+ break;
76
+ case '+':
77
+ case '*':
78
+ if (source[position + 1] === ' ') return ilist(input);
79
+ break;
80
+ case '[':
81
+ switch (source[position + 1]) {
82
+ case '$':
83
+ return extension(input);
84
+ case '!':
85
+ return mediablock(input);
86
+ }
87
+ break;
88
+ case '!':
89
+ if (source[position + 1] === '>') return blockquote(input);
75
90
  return mediablock(input);
76
- case '!>':
91
+ case '>':
92
+ if (source[position + 1] === '>') return blockquote(input) || reply(input);
77
93
  return blockquote(input);
78
- case '>>':
79
- return blockquote(input)
80
- || reply(input);
81
- case '- ':
82
- return ulist(input)
83
- || ilist(input);
84
- case '+ ':
85
- case '* ':
86
- return ilist(input);
87
- case '~ ':
88
- return dlist(input);
89
- }
90
- switch (source[position]) {
91
94
  case '#':
92
95
  return heading(input);
93
- case '|':
94
- return table(input)
95
- || sidefence(input);
96
96
  case '$':
97
+ if (source[position + 1] === '$') return mathblock(input);
97
98
  return extension(input);
98
- case '>':
99
- return blockquote(input);
100
- case '!':
101
- return mediablock(input);
99
+ case '|':
100
+ return table(input) || sidefence(input);
101
+ case '(':
102
+ return olist(input);
103
+ default:
104
+ if ('0' <= fst && fst <= '9') return olist(input);
102
105
  }
103
106
  },
104
107
  emptyline,
105
- olist,
106
108
  paragraph
107
109
  ]) as any));
108
110
 
@@ -17,7 +17,7 @@ describe('Unit: parser/inline/annotation', () => {
17
17
  assert.deepStrictEqual(inspect(parser('(()))'), ctx), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('(("))'), ctx), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('(([))'), ctx), undefined);
20
- assert.deepStrictEqual(inspect(parser('((<bdi>))'), ctx), undefined);
20
+ assert.deepStrictEqual(inspect(parser('(([%))'), ctx), undefined);
21
21
  assert.deepStrictEqual(inspect(parser('(( ))'), ctx), undefined);
22
22
  assert.deepStrictEqual(inspect(parser('(( (a'), ctx), undefined);
23
23
  assert.deepStrictEqual(inspect(parser('((\n))'), ctx), undefined);
@@ -41,6 +41,7 @@ describe('Unit: parser/inline/bracket', () => {
41
41
  assert.deepStrictEqual(inspect(parser('(ABBR, ABBR)'), ctx), [['(', 'ABBR, ABBR', ')'], '']);
42
42
  assert.deepStrictEqual(inspect(parser('(\\a)'), ctx), [['<span class="paren">(a)</span>'], '']);
43
43
  assert.deepStrictEqual(inspect(parser('(==)'), ctx), [['<span class="paren">(==)</span>'], '']);
44
+ assert.deepStrictEqual(inspect(parser('("(\n))"(")'), ctx), [['<span class="paren">("(<br>)</span>'], ')"(")']);
44
45
  assert.deepStrictEqual(inspect(parser('($)$'), ctx), [['(', '<span class="math" translate="no" data-src="$)$">$)$</span>'], '']);
45
46
  assert.deepStrictEqual(inspect(parser(')'), ctx), undefined);
46
47
  assert.deepStrictEqual(inspect(parser('(1,2)'), ctx), [['(', '1,2', ')'], '']);
@@ -82,8 +83,9 @@ describe('Unit: parser/inline/bracket', () => {
82
83
  assert.deepStrictEqual(inspect(parser('"(")"'), ctx), [['"', '(', '"'], ')"']);
83
84
  assert.deepStrictEqual(inspect(parser('"(("'), ctx), [['"', '(', '(', '"'], '']);
84
85
  assert.deepStrictEqual(inspect(parser('"(\\")"'), ctx), [['"', '<span class="paren">(")</span>', '"'], '']);
85
- assert.deepStrictEqual(inspect(parser('"\n"'), ctx), [['"', '<br>', '"'], '']);
86
- assert.deepStrictEqual(inspect(parser('"\n"(")'), ctx), [['"', '<br>', '"', '(', '"'], ')']);
86
+ assert.deepStrictEqual(inspect(parser('"(\n)"(")'), ctx), [['"', '('], '\n)"(")']);
87
+ assert.deepStrictEqual(inspect(parser('"\n"'), ctx), [['"'], '\n"']);
88
+ assert.deepStrictEqual(inspect(parser('"\n"(")'), ctx), [['"'], '\n"(")']);
87
89
  });
88
90
 
89
91
  });
@@ -1,6 +1,6 @@
1
1
  import { BracketParser } from '../inline';
2
2
  import { State, Recursion, Backtrack } from '../context';
3
- import { union, some, recursion, precedence, validate, surround, isBacktrack, setBacktrack, lazy } from '../../combinator';
3
+ import { union, some, recursion, precedence, surround, isBacktrack, setBacktrack, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
5
  import { textlink } from './link';
6
6
  import { str } from '../source';
@@ -11,93 +11,119 @@ const indexA = /^[0-9A-Za-z]+(?:(?:[.-]|, )[0-9A-Za-z]+)*$/;
11
11
  const indexF = new RegExp(indexA.source.replace(', ', '[,、]')
12
12
  .replace(/[09AZaz.]|\-(?!\w)/g, c => String.fromCodePoint(c.codePointAt(0)! + 0xFEE0)));
13
13
 
14
- export const bracket: BracketParser = lazy(() => validate(/[([{([{"]/y, union([
15
- surround(
16
- str('('),
17
- precedence(1, recursion(Recursion.bracket, some(inline, ')', [[')', 1]]))),
18
- str(')'),
19
- true,
20
- ([as, bs = [], cs], { source, position, range = 0 }) => {
21
- const str = source.slice(position - range + 1, position - 1);
22
- return indexA.test(str)
23
- ? [[as[0], str, cs[0]]]
24
- : [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))]];
25
- },
26
- ([as, bs = []]) => [unshift(as, bs)],
27
- [2 | Backtrack.bracket]),
28
- surround(
29
- str('('),
30
- precedence(1, recursion(Recursion.bracket, some(inline, '', [[')', 1]]))),
31
- str(')'),
32
- true,
33
- ([as, bs = [], cs], { source, position, range = 0 }) => {
34
- const str = source.slice(position - range + 1, position - 1);
35
- return indexF.test(str)
36
- ? [[as[0], str, cs[0]]]
37
- : [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))]];
38
- },
39
- ([as, bs = []]) => [unshift(as, bs)]),
40
- surround(
41
- str('['),
42
- precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
43
- str(']'),
44
- true,
45
- ([as, bs = [], cs], context) => {
46
- if (context.state! & State.link) {
47
- const { source, position, range = 0 } = context;
48
- const head = position - range;
49
- if (context.linebreak !== 0 || source[position] !== '{') {
14
+ export const bracket: BracketParser = lazy(() => union([
15
+ input => {
16
+ const { context: { source, position } } = input;
17
+ switch (source[position]) {
18
+ case '(':
19
+ return p1(input);
20
+ case '(':
21
+ return p2(input);
22
+ case '[':
23
+ return s1(input);
24
+ case '':
25
+ return s2(input);
26
+ case '{':
27
+ return c1(input);
28
+ case '{':
29
+ return c2(input);
30
+ case '"':
31
+ return d1(input);
32
+ }
33
+ }
34
+ ])) as any;
35
+
36
+ const p1 = lazy(() => surround(
37
+ str('('),
38
+ precedence(1, recursion(Recursion.bracket, some(inline, ')', [[')', 1]]))),
39
+ str(')'),
40
+ true,
41
+ ([as, bs = [], cs], { source, position, range = 0 }) => {
42
+ const str = source.slice(position - range + 1, position - 1);
43
+ return indexA.test(str)
44
+ ? [[as[0], str, cs[0]]]
45
+ : [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))]];
46
+ },
47
+ ([as, bs = []]) => [unshift(as, bs)],
48
+ [2 | Backtrack.bracket]));
49
+
50
+ const p2 = lazy(() => surround(
51
+ str('('),
52
+ precedence(1, recursion(Recursion.bracket, some(inline, ')', [[')', 1]]))),
53
+ str(')'),
54
+ true,
55
+ ([as, bs = [], cs], { source, position, range = 0 }) => {
56
+ const str = source.slice(position - range + 1, position - 1);
57
+ return indexF.test(str)
58
+ ? [[as[0], str, cs[0]]]
59
+ : [[html('span', { class: 'paren' }, defrag(push(unshift(as, bs), cs)))]];
60
+ },
61
+ ([as, bs = []]) => [unshift(as, bs)],
62
+ [2 | Backtrack.bracket]));
63
+
64
+ const s1 = lazy(() => surround(
65
+ str('['),
66
+ precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
67
+ str(']'),
68
+ true,
69
+ ([as, bs = [], cs], context) => {
70
+ if (context.state! & State.link) {
71
+ const { source, position, range = 0 } = context;
72
+ const head = position - range;
73
+ if (context.linebreak !== 0 || source[position] !== '{') {
74
+ setBacktrack(context, [2 | Backtrack.link], head);
75
+ }
76
+ else {
77
+ context.state! ^= State.link;
78
+ const result = !isBacktrack(context, [1 | Backtrack.link])
79
+ ? textlink({ context })
80
+ : undefined;
81
+ context.position = position;
82
+ if (!result) {
50
83
  setBacktrack(context, [2 | Backtrack.link], head);
51
84
  }
52
- else {
53
- context.state! ^= State.link;
54
- const result = !isBacktrack(context, [1 | Backtrack.link])
55
- ? textlink({ context })
56
- : undefined;
57
- context.position = position;
58
- if (!result) {
59
- setBacktrack(context, [2 | Backtrack.link], head);
60
- }
61
- context.state! ^= State.link;
62
- context.range = range;
63
- }
85
+ context.state! ^= State.link;
86
+ context.range = range;
64
87
  }
65
- return [push(unshift(as, bs), cs)];
66
- },
67
- ([as, bs = []]) => [unshift(as, bs)],
68
- [2 | Backtrack.bracket]),
69
- surround(
70
- str('['),
71
- precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
72
- str(''),
73
- true,
74
- undefined,
75
- ([as, bs = []]) => [unshift(as, bs)]),
76
- surround(
77
- str('{'),
78
- precedence(1, recursion(Recursion.bracket, some(inline, '}', [['}', 1]]))),
79
- str('}'),
80
- true,
81
- undefined,
82
- ([as, bs = []]) => [unshift(as, bs)],
83
- [2 | Backtrack.bracket]),
84
- surround(
85
- str('{'),
86
- precedence(1, recursion(Recursion.bracket, some(inline, '}', [['}', 1]]))),
87
- str('}'),
88
- true,
89
- undefined,
90
- ([as, bs = []]) => [unshift(as, bs)]),
91
- // 同一行内でしか閉じない以外括弧と同じ挙動
92
- surround(
93
- str('"'),
94
- precedence(2, recursion(Recursion.bracket, some(inline, '"', [['"', 2, false]]))),
95
- str('"'),
96
- true,
97
- ([as, bs = [], cs], context) =>
98
- context.linebreak === 0
99
- ? [push(unshift(as, bs), cs)]
100
- : (context.position -= 1, [unshift(as, bs)]),
101
- ([as, bs = []]) => [unshift(as, bs)],
102
- [2 | Backtrack.bracket]),
103
- ])));
88
+ }
89
+ return [push(unshift(as, bs), cs)];
90
+ },
91
+ ([as, bs = []]) => [unshift(as, bs)],
92
+ [2 | Backtrack.bracket]));
93
+
94
+ const s2 = lazy(() => surround(
95
+ str(''),
96
+ precedence(1, recursion(Recursion.bracket, some(inline, ']', [[']', 1]]))),
97
+ str(']'),
98
+ true,
99
+ undefined,
100
+ ([as, bs = []]) => [unshift(as, bs)],
101
+ [2 | Backtrack.bracket]));
102
+
103
+ const c1 = lazy(() => surround(
104
+ str('{'),
105
+ precedence(1, recursion(Recursion.bracket, some(inline, '}', [['}', 1]]))),
106
+ str('}'),
107
+ true,
108
+ undefined,
109
+ ([as, bs = []]) => [unshift(as, bs)],
110
+ [2 | Backtrack.bracket]));
111
+
112
+ const c2 = lazy(() => surround(
113
+ str('{'),
114
+ precedence(1, recursion(Recursion.bracket, some(inline, '}', [['}', 1]]))),
115
+ str('}'),
116
+ true,
117
+ undefined,
118
+ ([as, bs = []]) => [unshift(as, bs)],
119
+ [2 | Backtrack.bracket]));
120
+
121
+ const d1 = lazy(() => surround(
122
+ str('"'),
123
+ // 改行の優先度を構文ごとに変える場合シグネチャの優先度対応が必要
124
+ precedence(2, recursion(Recursion.bracket, some(inline, /["\n]/y, [['"', 2], ['\n', 3]]))),
125
+ str('"'),
126
+ true,
127
+ undefined,
128
+ ([as, bs = []]) => [unshift(as, bs)],
129
+ [2 | Backtrack.bracket]));
@@ -7,7 +7,7 @@ import { str } from '../source';
7
7
  import { isLooseNodeStart, blankWith } from '../visibility';
8
8
  import { invalid } from '../util';
9
9
  import { memoize } from 'spica/memoize';
10
- import { unshift, push, splice } from 'spica/array';
10
+ import { unshift, push } from 'spica/array';
11
11
  import { html as h, defrag } from 'typed-dom/dom';
12
12
 
13
13
  const tags: readonly string[] = ['wbr', 'bdo', 'bdi'];
@@ -42,10 +42,14 @@ export const html: HTMLParser = lazy(() => validate(/<[a-z]+(?=[^\S\n]|>)/yi,
42
42
  true,
43
43
  ([as, bs = [], cs]) => [push(unshift(as, bs), cs)],
44
44
  ([as, bs = []]) => [unshift(as, bs)]),
45
- precedence(3, recursion(Recursion.inline,
45
+ // 不可視のHTML構造が可視構造を変化させるべきでない。
46
+ // 可視のHTMLは優先度変更を検討する。
47
+ // このため<>は将来的に共通構造を変化させる可能性があり
48
+ // 共通構造を変更させない非構造文字列としては依然としてエスケープを要する。
49
+ precedence(0, recursion(Recursion.inline,
46
50
  some(union([
47
- some(inline, blankWith('\n', `</${tag}>`), [[blankWith('\n', `</${tag}>`), 3]]),
48
- open('\n', some(inline, `</${tag}>`, [[`</${tag}>`, 3]]), true),
51
+ some(inline, blankWith('\n', `</${tag}>`)),
52
+ open('\n', some(inline, `</${tag}>`), true),
49
53
  ])))),
50
54
  str(`</${tag}>`),
51
55
  true,
@@ -81,7 +85,7 @@ function elem(tag: string, content: boolean, as: string[], bs: (HTMLElement | st
81
85
  if (bs.length === 0) return ielem('content', `Missing the content`, context);
82
86
  if (!isLooseNodeStart(bs)) return ielem('content', `Missing the visible content in the same line`, context);
83
87
  }
84
- const attrs = attributes('html', attrspecs[tag], as.slice(1, as.at(-1) === '>' ? -1 : as.length));
88
+ const [attrs] = attributes('html', attrspecs[tag], as.slice(1, as.at(-1) === '>' ? -1 : as.length));
85
89
  if (/(?<!\S)invalid(?!\S)/.test(attrs['class'] ?? '')) return ielem('attribute', 'Invalid HTML attribute', context)
86
90
  if (as.at(-1) !== '>') return ielem('tag', `Missing the closing symbol ">"`, context);
87
91
  return h(tag as 'span', attrs, defrag(bs));
@@ -101,11 +105,12 @@ const requiredAttributes = memoize(
101
105
  export function attributes(
102
106
  syntax: string,
103
107
  spec: Readonly<Record<string, readonly (string | undefined)[] | undefined>> | undefined,
104
- params: string[],
105
- ): Record<string, string | undefined> {
108
+ params: readonly string[],
109
+ ): [Record<string, string | undefined>, string[]] {
106
110
  assert(spec instanceof Object === false);
107
111
  assert(!spec?.['__proto__']);
108
112
  assert(!spec?.toString);
113
+ const remains = [];
109
114
  let invalidation = false;
110
115
  const attrs: Record<string, string | undefined> = {};
111
116
  for (let i = 0; i < params.length; ++i) {
@@ -116,19 +121,25 @@ export function attributes(
116
121
  ? param.slice(name.length + 2, -1).replace(/\\(.?)/g, '$1')
117
122
  : undefined;
118
123
  invalidation ||= name === '' || !spec || name in attrs;
119
- if (name === '' || spec && name in spec && !spec[name]) continue;
120
- spec?.[name]?.includes(value) || spec?.[name]?.length === 0 && value !== undefined
121
- ? attrs[name] = value ?? ''
122
- : invalidation ||= !!spec;
124
+ if (name === '')continue;
125
+ if (spec && name in spec && !spec[name]) {
126
+ remains.push(params[i]);
127
+ continue;
128
+ }
129
+ if (spec?.[name]?.includes(value) || spec?.[name]?.length === 0 && value !== undefined) {
130
+ attrs[name] = value ?? ''
131
+ }
132
+ else {
133
+ invalidation ||= !!spec;
134
+ }
123
135
  assert(!(name in {} && attrs.hasOwnProperty(name)));
124
- splice(params, i--, 1);
125
136
  }
126
137
  invalidation ||= !!spec && !requiredAttributes(spec).every(name => name in attrs);
127
138
  if (invalidation) {
128
139
  attrs['class'] = 'invalid';
129
140
  Object.assign(attrs, invalid(syntax, 'argument', 'Invalid argument'));
130
141
  }
131
- return attrs;
142
+ return [attrs, remains];
132
143
  }
133
144
 
134
145
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
@@ -135,7 +135,7 @@ function parse(
135
135
  context.host?.origin || location.origin);
136
136
  return el.classList.contains('invalid')
137
137
  ? el
138
- : define(el, attributes('link', optspec, params));
138
+ : define(el, attributes('link', optspec, params)[0]);
139
139
  }
140
140
 
141
141
  function elem(