securemark 0.297.5 → 0.297.7

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.297.7
4
+
5
+ - Fix annotation parser.
6
+
7
+ ## 0.297.6
8
+
9
+ - Refactoring.
10
+
3
11
  ## 0.297.5
4
12
 
5
13
  - Fix annotation parser.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.297.5 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.297.7 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
2
2
  (function webpackUniversalModuleDefinition(root, factory) {
3
3
  if(typeof exports === 'object' && typeof module === 'object')
4
4
  module.exports = factory(require("Prism"), require("DOMPurify"));
@@ -6111,7 +6111,7 @@ const parser_1 = __webpack_require__(605);
6111
6111
  class Context extends parser_1.Context {
6112
6112
  constructor(options = {}) {
6113
6113
  super(options);
6114
- this.recursion = new RecursionCounter(5);
6114
+ this.recursion = new RecursionCounter('annotation', 2);
6115
6115
  const {
6116
6116
  segment,
6117
6117
  buffer,
@@ -6134,7 +6134,8 @@ class Context extends parser_1.Context {
6134
6134
  }
6135
6135
  exports.Context = Context;
6136
6136
  class RecursionCounter {
6137
- constructor(limit) {
6137
+ constructor(syntax, limit) {
6138
+ this.syntax = syntax;
6138
6139
  this.limit = limit;
6139
6140
  this.stack = [];
6140
6141
  this.index = 0;
@@ -6144,7 +6145,8 @@ class RecursionCounter {
6144
6145
  stack
6145
6146
  } = this;
6146
6147
  for (; this.index > 0 && stack[this.index - 1] <= depth; --this.index);
6147
- if (this.index === this.limit) throw new Error('Too much recursion');
6148
+ // 内側から数えるので無効化処理できずエラーを投げるしかない。
6149
+ if (this.index === this.limit) throw new Error(`Too much ${this.syntax} recursion`);
6148
6150
  stack[this.index] = depth;
6149
6151
  ++this.index;
6150
6152
  }
@@ -6377,36 +6379,48 @@ const dom_1 = __webpack_require__(394);
6377
6379
  // 動的計画法を適用するか再帰数を制限する必要がある。
6378
6380
  // 動的計画法においては再帰的記録により指数空間計算量にならないよう下位の記録を消しながら記録しなければならない。
6379
6381
  // トリムも再帰的に行わないよう前後のトリムサイズの記録を要する。
6380
- // しかし理論的には再帰数の制限はないがポップアップテキストの記録やハッシュの計算を行う言語仕様から指数計算量を
6382
+ // しかし理論的には無制限の再帰が可能だがホバーテキストの記録やハッシュの計算を行う言語仕様から指数計算量を
6381
6383
  // 避けられないためAnnotation構文に限り再帰数の制限が必要となる。
6382
- // シグネチャやハッシュは分割計算可能にすれば解決するがポップアップテキストは記録せず動的に再計算して
6384
+ // シグネチャやハッシュは分割計算可能にすれば解決するがホバーテキストは記録せず動的に再計算して
6383
6385
  // 表示しなければ指数空間計算量を避けられない。
6384
- // 注釈は本来再帰的に行うものではないため再帰数を制限して機能を優先するのが合理的となる。
6386
+ // 注釈を除外すると重複排除により参照元が消滅し欠番が生じるため少なくとも直接注釈は残す必要があるが間接注釈は
6387
+ // 除外できる。しかしこれを効率的に行うことは難しいため最大再帰数を1回に制限することで間接注釈を行えない
6388
+ // ようにするのが合理的だろう。
6389
+ // 原理的には逆順処理により圧縮後正順で再附番すればすべて解決するはずだがテキストとシグネチャとハッシュも
6390
+ // 修正する必要があるためほぼ完全な二重処理が必要になり三重以上の注釈という不適切な使用のために
6391
+ // 常に非常に非効率な処理を行い常時低速化するより三重以上の注釈を禁止して効率性を維持するのが妥当である。
6392
+ const MAX_DEPTH = 20;
6385
6393
  exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(128 /* State.annotation */, (0, combinator_1.surround)((0, combinator_1.open)('((', visibility_1.beforeNonblank), (0, combinator_1.precedence)(1, (0, combinator_1.recursions)([4 /* Recursion.annotation */, 3 /* Recursion.inline */, 5 /* Recursion.bracket */, 5 /* Recursion.bracket */], (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ')', [[')', 1]]))), '))', false, [], ([, ns], context) => {
6386
6394
  const {
6387
- linebreak
6395
+ linebreak,
6396
+ recursion,
6397
+ resources
6388
6398
  } = context;
6389
- if (linebreak === 0) {
6390
- context.recursion.add(20 - (context.resources?.recursions[4 /* Recursion.annotation */] ?? context.resources?.recursions.at(-1) ?? 20));
6391
- return new parser_1.List([new parser_1.Node((0, dom_1.html)('sup', {
6392
- class: 'annotation'
6393
- }, [(0, dom_1.html)('span', (0, dom_1.defrag)((0, util_1.unwrap)((0, visibility_1.trimBlankNodeEnd)(ns))))]))]);
6394
- }
6395
- ns.unshift(new parser_1.Node('('));
6396
- ns.push(new parser_1.Node(')'));
6397
- return new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6398
- class: 'bracket'
6399
- }, ['(', (0, dom_1.html)('span', {
6400
- class: 'bracket'
6401
- }, (0, dom_1.defrag)((0, util_1.unwrap)(ns))), ')']))]);
6402
- }, ([, bs = new parser_1.List()], context) => {
6399
+ if (linebreak !== 0) {
6400
+ ns.unshift(new parser_1.Node('('));
6401
+ ns.push(new parser_1.Node(')'));
6402
+ return new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6403
+ class: 'bracket'
6404
+ }, ['(', (0, dom_1.html)('span', {
6405
+ class: 'bracket'
6406
+ }, (0, dom_1.defrag)((0, util_1.unwrap)(ns))), ')']))]);
6407
+ }
6408
+ const depth = MAX_DEPTH - (resources?.recursions[4 /* Recursion.annotation */] ?? resources?.recursions.at(-1) ?? MAX_DEPTH);
6409
+ recursion.add(depth);
6410
+ return new parser_1.List([new parser_1.Node((0, dom_1.html)('sup', {
6411
+ class: 'annotation'
6412
+ }, [(0, dom_1.html)('span', (0, dom_1.defrag)((0, util_1.unwrap)((0, visibility_1.trimBlankNodeEnd)(ns))))]))]);
6413
+ }, ([, bs], context) => {
6403
6414
  const {
6404
6415
  source,
6405
6416
  position,
6406
6417
  range,
6407
- linebreak
6418
+ linebreak,
6419
+ recursion,
6420
+ resources
6408
6421
  } = context;
6409
- if (linebreak === 0 && bs.length === 1 && source[position] === ')' && typeof bs.head?.value === 'object') {
6422
+ const depth = MAX_DEPTH - (resources?.recursions[4 /* Recursion.annotation */] ?? resources?.recursions.at(-1) ?? MAX_DEPTH);
6423
+ if (linebreak === 0 && bs && bs.length === 1 && source[position] === ')' && typeof bs.head?.value === 'object') {
6410
6424
  const {
6411
6425
  className
6412
6426
  } = bs.head.value;
@@ -6426,7 +6440,7 @@ exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(1
6426
6440
  lastChild.nodeValue = lastChild.nodeValue.slice(0, -1);
6427
6441
  }
6428
6442
  context.position += 1;
6429
- context.recursion.add(20 - (context.resources?.recursions[4 /* Recursion.annotation */] ?? context.resources?.recursions.at(-1) ?? 20));
6443
+ recursion.add(depth);
6430
6444
  return new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6431
6445
  class: 'bracket'
6432
6446
  }, ['(', (0, dom_1.html)('sup', {
@@ -6435,7 +6449,7 @@ exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(1
6435
6449
  }
6436
6450
  if (className === 'annotation' && deepunwrap(bs)) {
6437
6451
  context.position += 1;
6438
- context.recursion.add(20 - (context.resources?.recursions[4 /* Recursion.annotation */] ?? context.resources?.recursions.at(-1) ?? 20));
6452
+ recursion.add(depth);
6439
6453
  return new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6440
6454
  class: 'bracket'
6441
6455
  }, ['(', (0, dom_1.html)('sup', {
@@ -6443,19 +6457,32 @@ exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(1
6443
6457
  }, [(0, dom_1.html)('span', [bs.head.value])])]))]);
6444
6458
  }
6445
6459
  }
6446
- const str = linebreak === 0 ? source.slice(position - range + 2, position) : '';
6447
- if (linebreak === 0 && bracket_1.indexA.test(str)) {
6448
- return new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6449
- class: 'bracket'
6450
- }, ['((' + str]))]);
6460
+ bs ??= new parser_1.List();
6461
+ bs.unshift(new parser_1.Node('('));
6462
+ if (source[context.position] === ')') {
6463
+ bs.push(new parser_1.Node(')'));
6464
+ context.position += 1;
6451
6465
  }
6466
+ const str = linebreak === 0 ? source.slice(position - range + 2, position) : '';
6467
+ bs = linebreak === 0 && (str === '' || bracket_1.indexA.test(str)) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6468
+ class: 'paren'
6469
+ }, (0, dom_1.defrag)((0, util_1.unwrap)(bs))))]) : new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6470
+ class: 'bracket'
6471
+ }, (0, dom_1.defrag)((0, util_1.unwrap)(bs))))]);
6452
6472
  bs.unshift(new parser_1.Node('('));
6473
+ const cs = parser({
6474
+ context
6475
+ });
6476
+ if (source[context.position] === ')') {
6477
+ cs && bs.import(cs);
6478
+ bs.push(new parser_1.Node(')'));
6479
+ context.position += 1;
6480
+ }
6453
6481
  return new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6454
6482
  class: 'bracket'
6455
- }, ['(', (0, dom_1.html)('span', {
6456
- class: 'bracket'
6457
- }, (0, dom_1.defrag)((0, util_1.unwrap)(bs)))]))]);
6483
+ }, (0, dom_1.defrag)((0, util_1.unwrap)(bs))))]);
6458
6484
  })));
6485
+ const parser = (0, combinator_1.lazy)(() => (0, combinator_1.precedence)(1, (0, combinator_1.some)(inline_1.inline, ')', [[')', 1]])));
6459
6486
  function deepunwrap(list) {
6460
6487
  let bottom = list.head.value;
6461
6488
  for (; bottom;) {
@@ -6786,7 +6813,7 @@ const p1 = (0, combinator_1.lazy)(() => (0, combinator_1.surround)((0, source_1.
6786
6813
  linebreak
6787
6814
  }) => {
6788
6815
  const str = linebreak === 0 ? source.slice(position - range + 1, position - 1) : '';
6789
- return linebreak === 0 && exports.indexA.test(str) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6816
+ return linebreak === 0 && (str === '' || exports.indexA.test(str)) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6790
6817
  class: 'paren'
6791
6818
  }, `(${str})`))]) : new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6792
6819
  class: 'bracket'
@@ -6799,7 +6826,7 @@ const p1 = (0, combinator_1.lazy)(() => (0, combinator_1.surround)((0, source_1.
6799
6826
  linebreak
6800
6827
  } = context;
6801
6828
  const str = linebreak === 0 ? source.slice(position - range + 1, position) : '';
6802
- return linebreak === 0 && exports.indexA.test(str) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6829
+ return linebreak === 0 && (str === '' || exports.indexA.test(str)) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6803
6830
  class: 'paren'
6804
6831
  }, `(${str}`))]) : new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6805
6832
  class: 'bracket'
@@ -6812,7 +6839,7 @@ const p2 = (0, combinator_1.lazy)(() => (0, combinator_1.surround)((0, source_1.
6812
6839
  linebreak
6813
6840
  }) => {
6814
6841
  const str = linebreak === 0 ? source.slice(position - range + 1, position - 1) : '';
6815
- return linebreak === 0 && indexF.test(str) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6842
+ return linebreak === 0 && (str === '' || indexF.test(str)) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6816
6843
  class: 'paren'
6817
6844
  }, `(${str})`))]) : new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6818
6845
  class: 'bracket'
@@ -6825,7 +6852,7 @@ const p2 = (0, combinator_1.lazy)(() => (0, combinator_1.surround)((0, source_1.
6825
6852
  linebreak
6826
6853
  } = context;
6827
6854
  const str = linebreak === 0 ? source.slice(position - range + 1, position) : '';
6828
- return linebreak === 0 && indexF.test(str) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6855
+ return linebreak === 0 && (str === '' || indexF.test(str)) ? new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6829
6856
  class: 'paren'
6830
6857
  }, `(${str}`))]) : new parser_1.List([new parser_1.Node((0, dom_1.html)('span', {
6831
6858
  class: 'bracket'
@@ -7946,12 +7973,12 @@ exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(64
7946
7973
  range,
7947
7974
  linebreak
7948
7975
  } = context;
7949
- if (linebreak === 0) {
7950
- return new parser_1.List([new parser_1.Node((0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, dom_1.defrag)((0, util_1.unwrap)((0, visibility_1.trimBlankNodeEnd)(ns))))]))]);
7951
- } else {
7952
- const head = position - range;
7976
+ const head = position - range;
7977
+ if (linebreak !== 0) {
7953
7978
  (0, combinator_1.setBacktrack)(context, 2 | 64 /* Backtrack.link */, head, 2);
7979
+ return;
7954
7980
  }
7981
+ return new parser_1.List([new parser_1.Node((0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, dom_1.defrag)((0, util_1.unwrap)((0, visibility_1.trimBlankNodeEnd)(ns))))]))]);
7955
7982
  }, (_, context) => {
7956
7983
  const {
7957
7984
  source,
@@ -8466,6 +8493,9 @@ function build(syntax, list, query, marker, splitter = '') {
8466
8493
  'data-marker': note ? undefined : marker(total + defs.size + 1, abbr)
8467
8494
  }, [content, (0, dom_1.html)('sup')]) : defs.get(identifier);
8468
8495
  initial && defs.set(identifier, def);
8496
+ if (!initial && content.innerHTML.length > def.firstElementChild.innerHTML.length) {
8497
+ def.firstElementChild.replaceWith(content);
8498
+ }
8469
8499
  const defIndex = initial ? info.defIndex = total + defs.size : info.defIndex;
8470
8500
  const title = info.title ||= text;
8471
8501
  (0, dom_1.define)(ref, {
@@ -8498,13 +8528,14 @@ function build(syntax, list, query, marker, splitter = '') {
8498
8528
  info.queue.push(ref);
8499
8529
  break;
8500
8530
  }
8501
- yield ref.appendChild((0, dom_1.html)('a', {
8531
+ ref.appendChild((0, dom_1.html)('a', {
8502
8532
  href: refId && defId && `#${defId}`
8503
8533
  }, marker(defIndex, abbr)));
8504
8534
  def.lastElementChild.appendChild((0, dom_1.html)('a', {
8505
8535
  href: refId && `#${refId}`,
8506
8536
  title: abbr && text || undefined
8507
8537
  }, `^${++refIndex}`));
8538
+ yield;
8508
8539
  }
8509
8540
  if (note || defs.size > 0) {
8510
8541
  const splitter = splitters[iSplitters++];
@@ -9467,7 +9498,6 @@ function render(source, opts = {}) {
9467
9498
  }
9468
9499
  exports.render = render;
9469
9500
  function render_(base, source, opts) {
9470
- if (source.classList.contains('invalid')) return;
9471
9501
  try {
9472
9502
  switch (true) {
9473
9503
  case !!opts.code && !source.firstElementChild && source.matches('pre.code'):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.297.5",
3
+ "version": "0.297.7",
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",
@@ -307,7 +307,7 @@ describe('Unit: parser/api/parse', () => {
307
307
  // ]);
308
308
  assert.deepStrictEqual(
309
309
  [...parse(`${'('.repeat(20)}0`).children].map(el => el.outerHTML),
310
- [`<p>${'<span class="bracket">('.repeat(19)}(0${'</span>'.repeat(19)}</p>`]);
310
+ [`<p>${'<span class="bracket">('.repeat(19)}<span class="paren">(0</span>${'</span>'.repeat(19)}</p>`]);
311
311
  assert.deepStrictEqual(
312
312
  [...parse(`${'('.repeat(21)}0`).children].map(el => el.outerHTML.replace(/:\w+/, ':rnd')),
313
313
  [
@@ -324,40 +324,40 @@ describe('Unit: parser/api/parse', () => {
324
324
  `<pre class="error" translate="no">${'['.repeat(21)}0</pre>`,
325
325
  ]);
326
326
  assert.deepStrictEqual(
327
- [...parse(`${'(('.repeat(5)}0${'))'.repeat(5)}`).children].map(el => el.tagName),
327
+ [...parse(`${'(('.repeat(2)}0${'))'.repeat(2)}`).children].map(el => el.tagName),
328
328
  ['P', 'OL']);
329
329
  assert.deepStrictEqual(
330
- [...parse(`${'(('.repeat(6)}0${'))'.repeat(6)}`).children].map(el => el.tagName),
330
+ [...parse(`${'(('.repeat(3)}0${'))'.repeat(3)}`).children].map(el => el.tagName),
331
331
  ['H1', 'PRE']);
332
332
  assert.deepStrictEqual(
333
- [...parse(`${'(('.repeat(5)}!${'))'.repeat(5)}`).children].map(el => el.tagName),
333
+ [...parse(`${'(('.repeat(2)}!${'))'.repeat(2)}`).children].map(el => el.tagName),
334
334
  ['P', 'OL']);
335
335
  assert.deepStrictEqual(
336
- [...parse(`${'(('.repeat(6)}!${'))'.repeat(6)}`).children].map(el => el.tagName),
336
+ [...parse(`${'(('.repeat(3)}!${'))'.repeat(3)}`).children].map(el => el.tagName),
337
337
  ['H1', 'PRE']);
338
338
  assert.deepStrictEqual(
339
- [...parse(`(${'(('.repeat(5)}0${'))'.repeat(5)}`).children].map(el => el.tagName),
339
+ [...parse(`(${'(('.repeat(2)}0${'))'.repeat(2)}`).children].map(el => el.tagName),
340
340
  ['P', 'OL']);
341
341
  assert.deepStrictEqual(
342
- [...parse(`(${'(('.repeat(6)}0${'))'.repeat(6)}`).children].map(el => el.tagName),
342
+ [...parse(`(${'(('.repeat(3)}0${'))'.repeat(3)}`).children].map(el => el.tagName),
343
343
  ['H1', 'PRE']);
344
344
  assert.deepStrictEqual(
345
- [...parse(`(${'(('.repeat(5)}!${'))'.repeat(5)}`).children].map(el => el.tagName),
345
+ [...parse(`(${'(('.repeat(2)}!${'))'.repeat(2)}`).children].map(el => el.tagName),
346
346
  ['P', 'OL']);
347
347
  assert.deepStrictEqual(
348
- [...parse(`(${'(('.repeat(6)}!${'))'.repeat(6)}`).children].map(el => el.tagName),
348
+ [...parse(`(${'(('.repeat(3)}!${'))'.repeat(3)}`).children].map(el => el.tagName),
349
349
  ['H1', 'PRE']);
350
350
  assert.deepStrictEqual(
351
- [...parse(`${'(('.repeat(5)}0${'))'.repeat(5)}${'(('.repeat(5)}0${'))'.repeat(5)}`).children].map(el => el.tagName),
351
+ [...parse(`${'(('.repeat(2)}0${'))'.repeat(2)}${'(('.repeat(2)}0${'))'.repeat(2)}`).children].map(el => el.tagName),
352
352
  ['P', 'OL']);
353
353
  assert.deepStrictEqual(
354
- [...parse(`${'(('.repeat(5)}0${'))'.repeat(5)}${'(('.repeat(6)}0${'))'.repeat(6)}`).children].map(el => el.tagName),
354
+ [...parse(`${'(('.repeat(2)}0${'))'.repeat(2)}${'(('.repeat(3)}0${'))'.repeat(3)}`).children].map(el => el.tagName),
355
355
  ['H1', 'PRE']);
356
356
  assert.deepStrictEqual(
357
- [...parse(`${'(('.repeat(5)}0${'))'.repeat(5)}${'(('.repeat(9)}0${'))'.repeat(5)}`).children].map(el => el.tagName),
357
+ [...parse(`${'(('.repeat(2)}0${'))'.repeat(2)}${'(('.repeat(9)}0${'))'.repeat(2)}`).children].map(el => el.tagName),
358
358
  ['P', 'OL']);
359
359
  assert.deepStrictEqual(
360
- [...parse(`${'(('.repeat(5)}0${'))'.repeat(5)}${'(('.repeat(9)}0${'))'.repeat(6)}`).children].map(el => el.tagName),
360
+ [...parse(`${'(('.repeat(2)}0${'))'.repeat(2)}${'(('.repeat(9)}0${'))'.repeat(3)}`).children].map(el => el.tagName),
361
361
  ['H1', 'PRE']);
362
362
  });
363
363
 
@@ -33,7 +33,7 @@ describe('Unit: parser/block/table', () => {
33
33
  assert.deepStrictEqual(inspect(parser, input('|||\n|-|-|\n|||', new Context())), [['<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr></tbody></table>'], '']);
34
34
  assert.deepStrictEqual(inspect(parser, input('|"|\n|-\n|', new Context())), [['<table><thead><tr><th>"</th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
35
35
  assert.deepStrictEqual(inspect(parser, input('|`|`|\n|-\n|', new Context())), [['<table><thead><tr><th><code data-src="`|`">|</code></th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
36
- assert.deepStrictEqual(inspect(parser, input('|((|\n|-\n|', new Context())), [['<table><thead><tr><th><span class="bracket">(<span class="bracket">(</span></span></th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
36
+ assert.deepStrictEqual(inspect(parser, input('|((|\n|-\n|', new Context())), [['<table><thead><tr><th><span class="bracket">(<span class="paren">(</span></span></th></tr></thead><tbody><tr></tr></tbody></table>'], '']);
37
37
  assert.deepStrictEqual(inspect(parser, input('|a|b|\n|-|-|\n|1|2|', new Context())), [['<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table>'], '']);
38
38
  assert.deepStrictEqual(inspect(parser, input('|a|b\n|-|-\n|1|2', new Context())), [['<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody><tr><td>1</td><td>2</td></tr></tbody></table>'], '']);
39
39
  assert.deepStrictEqual(inspect(parser, input('|a|\n|-|\n|1|', new Context())), [['<table><thead><tr><th>a</th></tr></thead><tbody><tr><td>1</td></tr></tbody></table>'], '']);
@@ -28,7 +28,7 @@ export class Context extends Ctx {
28
28
  public override segment: Segment;
29
29
  public buffer: List<Node<(string | HTMLElement)>>;
30
30
  public sequential: boolean;
31
- public recursion = new RecursionCounter(5);
31
+ public recursion = new RecursionCounter('annotation', 2);
32
32
  public readonly header: boolean;
33
33
  public readonly host?: URL;
34
34
  public readonly url?: URL;
@@ -42,14 +42,18 @@ export class Context extends Ctx {
42
42
  export type Options = Partial<Context>;
43
43
 
44
44
  class RecursionCounter {
45
- constructor(private readonly limit: number) {
45
+ constructor(
46
+ private readonly syntax: string,
47
+ private readonly limit: number,
48
+ ) {
46
49
  }
47
50
  private readonly stack: number[] = [];
48
51
  private index = 0;
49
52
  public add(depth: number): void {
50
53
  const { stack } = this
51
54
  for (; this.index > 0 && stack[this.index - 1] <= depth; --this.index);
52
- if (this.index === this.limit) throw new Error('Too much recursion');
55
+ // 内側から数えるので無効化処理できずエラーを投げるしかない。
56
+ if (this.index === this.limit) throw new Error(`Too much ${this.syntax} recursion`);
53
57
  stack[this.index] = depth;
54
58
  ++this.index;
55
59
  }
@@ -13,9 +13,15 @@ describe('Unit: parser/inline/annotation', () => {
13
13
  assert.deepStrictEqual(inspect(parser, input('(', new Context())), undefined);
14
14
  assert.deepStrictEqual(inspect(parser, input('()', new Context())), undefined);
15
15
  assert.deepStrictEqual(inspect(parser, input('((', new Context())), undefined);
16
- assert.deepStrictEqual(inspect(parser, input('(())', new Context())), [['<span class="bracket">(<span class="bracket">(</span></span>'], '))']);
17
- assert.deepStrictEqual(inspect(parser, input('(()))', new Context())), [['<span class="bracket">(<span class="bracket">(</span></span>'], ')))']);
16
+ assert.deepStrictEqual(inspect(parser, input('(())', new Context())), [['<span class="bracket">(<span class="paren">()</span>)</span>'], '']);
17
+ assert.deepStrictEqual(inspect(parser, input('(()))', new Context())), [['<span class="bracket">(<span class="paren">()</span>)</span>'], ')']);
18
18
  assert.deepStrictEqual(inspect(parser, input('(("))', new Context())), [['<span class="bracket">(<span class="bracket">("))</span></span>'], '']);
19
+ assert.deepStrictEqual(inspect(parser, input('((a', new Context())), [['<span class="bracket">(<span class="paren">(a</span></span>'], '']);
20
+ assert.deepStrictEqual(inspect(parser, input('((!', new Context())), [['<span class="bracket">(<span class="bracket">(!</span></span>'], '']);
21
+ assert.deepStrictEqual(inspect(parser, input('((a)', new Context())), [['<span class="bracket">(<span class="paren">(a)</span></span>'], '']);
22
+ assert.deepStrictEqual(inspect(parser, input('((!)', new Context())), [['<span class="bracket">(<span class="bracket">(!)</span></span>'], '']);
23
+ assert.deepStrictEqual(inspect(parser, input('((a)b)', new Context())), [['<span class="bracket">(<span class="paren">(a)</span>b)</span>'], '']);
24
+ assert.deepStrictEqual(inspect(parser, input('((!)b)', new Context())), [['<span class="bracket">(<span class="bracket">(!)</span>b)</span>'], '']);
19
25
  assert.deepStrictEqual(inspect(parser, input('(([))', new Context())), [['<span class="bracket">(<span class="bracket">([))</span></span>'], '']);
20
26
  assert.deepStrictEqual(inspect(parser, input('(([%))', new Context())), [['<span class="bracket">(<span class="bracket">([%))</span></span>'], '']);
21
27
  assert.deepStrictEqual(inspect(parser, input('(( ))', new Context())), undefined);
@@ -31,9 +37,7 @@ describe('Unit: parser/inline/annotation', () => {
31
37
  assert.deepStrictEqual(inspect(parser, input('((a\nb))', new Context())), [['<span class="bracket">(<span class="bracket">(a<br>b)</span>)</span>'], '']);
32
38
  assert.deepStrictEqual(inspect(parser, input('((a\\\nb))', new Context())), [['<span class="bracket">(<span class="bracket">(a<br>b)</span>)</span>'], '']);
33
39
  assert.deepStrictEqual(inspect(parser, input('((*a\nb*))', new Context())), [['<span class="bracket">(<span class="bracket">(<em>a<br>b</em>)</span>)</span>'], '']);
34
- assert.deepStrictEqual(inspect(parser, input('((\\))', new Context())), [['<span class="bracket">(<span class="bracket">()</span></span>'], ')']);
35
- assert.deepStrictEqual(inspect(parser, input('((a)b))', new Context())), [['<span class="bracket">((a</span>'], ')b))']);
36
- assert.deepStrictEqual(inspect(parser, input('((!)b))', new Context())), [['<span class="bracket">(<span class="bracket">(!</span></span>'], ')b))']);
40
+ assert.deepStrictEqual(inspect(parser, input('((\\))', new Context())), [['<span class="bracket">(<span class="bracket">())</span></span>'], '']);
37
41
  assert.deepStrictEqual(inspect(parser, input('(((a))', new Context())), [['<span class="bracket">(<sup class="annotation"><span>a</span></sup></span>'], '']);
38
42
  assert.deepStrictEqual(inspect(parser, input('(((!))', new Context())), [['<span class="bracket">(<sup class="annotation"><span>!</span></sup></span>'], '']);
39
43
  assert.deepStrictEqual(inspect(parser, input('(((*a*))', new Context())), [['<span class="bracket">(<sup class="annotation"><span><em>a</em></span></sup></span>'], '']);
@@ -57,6 +61,8 @@ describe('Unit: parser/inline/annotation', () => {
57
61
  assert.deepStrictEqual(inspect(parser, input('((@a))', new Context())), [['<sup class="annotation"><span><a class="account" href="/@a">@a</a></span></sup>'], '']);
58
62
  assert.deepStrictEqual(inspect(parser, input('((http://host))', new Context())), [['<sup class="annotation"><span><a class="url" href="http://host" target="_blank">http://host</a></span></sup>'], '']);
59
63
  assert.deepStrictEqual(inspect(parser, input('((![]{a}))', new Context())), [['<sup class="annotation"><span>!<a class="url" href="a">a</a></span></sup>'], '']);
64
+ assert.deepStrictEqual(inspect(parser, input('((a(())))', new Context())), [['<sup class="annotation"><span>a<span class="bracket">(<span class="paren">()</span>)</span></span></sup>'], '']);
65
+ assert.deepStrictEqual(inspect(parser, input('((a[[]]))', new Context())), [['<sup class="annotation"><span>a[[]]</span></sup>'], '']);
60
66
  assert.deepStrictEqual(inspect(parser, input('(([[a] ]))', new Context())), [['<sup class="annotation"><span>[[a] ]</span></sup>'], '']);
61
67
  assert.deepStrictEqual(inspect(parser, input('(((a)))', new Context())), [['<sup class="annotation"><span><span class="paren">(a)</span></span></sup>'], '']);
62
68
  assert.deepStrictEqual(inspect(parser, input('((((a))))', new Context())), [['<sup class="annotation"><span><sup class="annotation"><span>a</span></sup></span></sup>'], '']);
@@ -12,11 +12,17 @@ import { html, defrag } from 'typed-dom/dom';
12
12
  // 動的計画法を適用するか再帰数を制限する必要がある。
13
13
  // 動的計画法においては再帰的記録により指数空間計算量にならないよう下位の記録を消しながら記録しなければならない。
14
14
  // トリムも再帰的に行わないよう前後のトリムサイズの記録を要する。
15
- // しかし理論的には再帰数の制限はないがポップアップテキストの記録やハッシュの計算を行う言語仕様から指数計算量を
15
+ // しかし理論的には無制限の再帰が可能だがホバーテキストの記録やハッシュの計算を行う言語仕様から指数計算量を
16
16
  // 避けられないためAnnotation構文に限り再帰数の制限が必要となる。
17
- // シグネチャやハッシュは分割計算可能にすれば解決するがポップアップテキストは記録せず動的に再計算して
17
+ // シグネチャやハッシュは分割計算可能にすれば解決するがホバーテキストは記録せず動的に再計算して
18
18
  // 表示しなければ指数空間計算量を避けられない。
19
- // 注釈は本来再帰的に行うものではないため再帰数を制限して機能を優先するのが合理的となる。
19
+ // 注釈を除外すると重複排除により参照元が消滅し欠番が生じるため少なくとも直接注釈は残す必要があるが間接注釈は
20
+ // 除外できる。しかしこれを効率的に行うことは難しいため最大再帰数を1回に制限することで間接注釈を行えない
21
+ // ようにするのが合理的だろう。
22
+ // 原理的には逆順処理により圧縮後正順で再附番すればすべて解決するはずだがテキストとシグネチャとハッシュも
23
+ // 修正する必要があるためほぼ完全な二重処理が必要になり三重以上の注釈という不適切な使用のために
24
+ // 常に非常に非効率な処理を行い常時低速化するより三重以上の注釈を禁止して効率性を維持するのが妥当である。
25
+ const MAX_DEPTH = 20;
20
26
  export const annotation: AnnotationParser = lazy(() => constraint(State.annotation, surround(
21
27
  open('((', beforeNonblank),
22
28
  precedence(1, recursions([Recursion.annotation, Recursion.inline, Recursion.bracket, Recursion.bracket],
@@ -24,18 +30,20 @@ export const annotation: AnnotationParser = lazy(() => constraint(State.annotati
24
30
  '))',
25
31
  false, [],
26
32
  ([, ns], context) => {
27
- const { linebreak } = context;
28
- if (linebreak === 0) {
29
- context.recursion.add(20 - (context.resources?.recursions[Recursion.annotation] ?? context.resources?.recursions.at(-1) ?? 20));
30
- return new List([new Node(html('sup', { class: 'annotation' }, [html('span', defrag(unwrap(trimBlankNodeEnd(ns))))]))]);
33
+ const { linebreak, recursion, resources } = context;
34
+ if (linebreak !== 0) {
35
+ ns.unshift(new Node('('));
36
+ ns.push(new Node(')'));
37
+ return new List([new Node(html('span', { class: 'bracket' }, ['(', html('span', { class: 'bracket' }, defrag(unwrap(ns))), ')']))]);
31
38
  }
32
- ns.unshift(new Node('('));
33
- ns.push(new Node(')'));
34
- return new List([new Node(html('span', { class: 'bracket' }, ['(', html('span', { class: 'bracket' }, defrag(unwrap(ns))), ')']))]);
39
+ const depth = MAX_DEPTH - (resources?.recursions[Recursion.annotation] ?? resources?.recursions.at(-1) ?? MAX_DEPTH);
40
+ recursion.add(depth);
41
+ return new List([new Node(html('sup', { class: 'annotation' }, [html('span', defrag(unwrap(trimBlankNodeEnd(ns))))]))]);
35
42
  },
36
- ([, bs = new List()], context) => {
37
- const { source, position, range, linebreak } = context;
38
- if (linebreak === 0 && bs.length === 1 && source[position] === ')' && typeof bs.head?.value === 'object') {
43
+ ([, bs], context) => {
44
+ const { source, position, range, linebreak, recursion, resources } = context;
45
+ const depth = MAX_DEPTH - (resources?.recursions[Recursion.annotation] ?? resources?.recursions.at(-1) ?? MAX_DEPTH);
46
+ if (linebreak === 0 && bs && bs.length === 1 && source[position] === ')' && typeof bs.head?.value === 'object') {
39
47
  const { className } = bs.head.value;
40
48
  if (className === 'paren' || className === 'bracket') {
41
49
  const { firstChild, lastChild } = bs.head.value;
@@ -54,23 +62,37 @@ export const annotation: AnnotationParser = lazy(() => constraint(State.annotati
54
62
  lastChild!.nodeValue = lastChild!.nodeValue!.slice(0, -1);
55
63
  }
56
64
  context.position += 1;
57
- context.recursion.add(20 - (context.resources?.recursions[Recursion.annotation] ?? context.resources?.recursions.at(-1) ?? 20));
65
+ recursion.add(depth);
58
66
  return new List([new Node(html('span', { class: 'bracket' }, ['(', html('sup', { class: 'annotation' }, [html('span', bs.head.value.childNodes)])]))]);
59
67
  }
60
68
  if (className === 'annotation' && deepunwrap(bs)) {
61
69
  context.position += 1;
62
- context.recursion.add(20 - (context.resources?.recursions[Recursion.annotation] ?? context.resources?.recursions.at(-1) ?? 20));
70
+ recursion.add(depth);
63
71
  return new List([new Node(html('span', { class: 'bracket' }, ['(', html('sup', { class: 'annotation' }, [html('span', [bs.head.value])])]))]);
64
72
  }
65
73
  }
66
- const str = linebreak === 0 ? source.slice(position - range + 2, position) : '';
67
- if (linebreak === 0 && indexA.test(str)) {
68
- return new List([new Node(html('span', { class: 'bracket' }, ['((' + str]))]);
74
+ bs ??= new List();
75
+ bs.unshift(new Node('('));
76
+ if (source[context.position] === ')') {
77
+ bs.push(new Node(')'));
78
+ context.position += 1;
69
79
  }
80
+ const str = linebreak === 0 ? source.slice(position - range + 2, position) : '';
81
+ bs = linebreak === 0 && (str === '' || indexA.test(str))
82
+ ? new List([new Node(html('span', { class: 'paren' }, defrag(unwrap(bs))))])
83
+ : new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(bs))))]);
70
84
  bs.unshift(new Node('('));
71
- return new List([new Node(html('span', { class: 'bracket' }, ['(', html('span', { class: 'bracket' }, defrag(unwrap(bs)))]))]);
85
+ const cs = parser({ context });
86
+ if (source[context.position] === ')') {
87
+ cs && bs.import(cs);
88
+ bs.push(new Node(')'));
89
+ context.position += 1;
90
+ }
91
+ return new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(bs))))]);
72
92
  })));
73
93
 
94
+ const parser = lazy(() => precedence(1, some(inline, ')', [[')', 1]])));
95
+
74
96
  function deepunwrap(list: List<Node<string | HTMLElement>>): boolean {
75
97
  let bottom = list.head!.value as HTMLElement;
76
98
  for (; bottom;) {
@@ -9,8 +9,8 @@ describe('Unit: parser/inline/bracket', () => {
9
9
  const parser = some(bracket);
10
10
 
11
11
  it('(', () => {
12
- assert.deepStrictEqual(inspect(parser, input('(', new Context())), [['<span class="bracket">(</span>'], '']);
13
- assert.deepStrictEqual(inspect(parser, input('()', new Context())), [['<span class="bracket">()</span>'], '']);
12
+ assert.deepStrictEqual(inspect(parser, input('(', new Context())), [['<span class="paren">(</span>'], '']);
13
+ assert.deepStrictEqual(inspect(parser, input('()', new Context())), [['<span class="paren">()</span>'], '']);
14
14
  assert.deepStrictEqual(inspect(parser, input('(a', new Context())), [['<span class="paren">(a</span>'], '']);
15
15
  assert.deepStrictEqual(inspect(parser, input('(0)', new Context())), [['<span class="paren">(0)</span>'], '']);
16
16
  assert.deepStrictEqual(inspect(parser, input('(1)', new Context())), [['<span class="paren">(1)</span>'], '']);
@@ -41,7 +41,8 @@ describe('Unit: parser/inline/bracket', () => {
41
41
  assert.deepStrictEqual(inspect(parser, input('(ABBR, ABBR)', new Context())), [['<span class="paren">(ABBR, ABBR)</span>'], '']);
42
42
  assert.deepStrictEqual(inspect(parser, input('(\\a)', new Context())), [['<span class="bracket">(a)</span>'], '']);
43
43
  assert.deepStrictEqual(inspect(parser, input('(==)', new Context())), [['<span class="bracket">(==)</span>'], '']);
44
- assert.deepStrictEqual(inspect(parser, input('("(\n))"(")', new Context())), [['<span class="bracket">("<span class="bracket">(</span><br>)</span>'], ')"(")']);
44
+ assert.deepStrictEqual(inspect(parser, input('(()', new Context())), [['<span class="bracket">(<span class="paren">()</span></span>'], '']);
45
+ assert.deepStrictEqual(inspect(parser, input('("(\n))"(")', new Context())), [['<span class="bracket">("<span class="paren">(</span><br>)</span>'], ')"(")']);
45
46
  assert.deepStrictEqual(inspect(parser, input('($)$', new Context())), [['<span class="bracket">(<span class="math" translate="no" data-src="$)$">$)$</span></span>'], '']);
46
47
  assert.deepStrictEqual(inspect(parser, input(')', new Context())), undefined);
47
48
  assert.deepStrictEqual(inspect(parser, input('(1,2)', new Context())), [['<span class="paren">(1,2)</span>'], '']);
@@ -80,10 +81,10 @@ describe('Unit: parser/inline/bracket', () => {
80
81
  assert.deepStrictEqual(inspect(parser, input('""', new Context())), [['"', '"'], '']);
81
82
  assert.deepStrictEqual(inspect(parser, input('"a', new Context())), [['"', 'a'], '']);
82
83
  assert.deepStrictEqual(inspect(parser, input('"a"', new Context())), [['"', 'a', '"'], '']);
83
- assert.deepStrictEqual(inspect(parser, input('"(")"', new Context())), [['"', '<span class="bracket">(</span>', '"'], ')"']);
84
- assert.deepStrictEqual(inspect(parser, input('"(("', new Context())), [['"', '<span class="bracket">(<span class="bracket">(</span></span>', '"'], '']);
84
+ assert.deepStrictEqual(inspect(parser, input('"(")"', new Context())), [['"', '<span class="paren">(</span>', '"'], ')"']);
85
+ assert.deepStrictEqual(inspect(parser, input('"(("', new Context())), [['"', '<span class="bracket">(<span class="paren">(</span></span>', '"'], '']);
85
86
  assert.deepStrictEqual(inspect(parser, input('"(\\")"', new Context())), [['"', '<span class="bracket">(")</span>', '"'], '']);
86
- assert.deepStrictEqual(inspect(parser, input('"(\n)"(")', new Context())), [['"', '<span class="bracket">(</span>'], '\n)"(")']);
87
+ assert.deepStrictEqual(inspect(parser, input('"(\n)"(")', new Context())), [['"', '<span class="paren">(</span>'], '\n)"(")']);
87
88
  assert.deepStrictEqual(inspect(parser, input('"\n"', new Context())), [['"'], '\n"']);
88
89
  assert.deepStrictEqual(inspect(parser, input('"\n"(")', new Context())), [['"'], '\n"(")']);
89
90
  });
@@ -41,14 +41,14 @@ const p1 = lazy(() => surround(
41
41
  true, [],
42
42
  ([as, bs = [], cs], { source, position, range, linebreak }) => {
43
43
  const str = linebreak === 0 ? source.slice(position - range + 1, position - 1) : '';
44
- return linebreak === 0 && indexA.test(str)
44
+ return linebreak === 0 && (str === '' || indexA.test(str))
45
45
  ? new List([new Node(html('span', { class: 'paren' }, `(${str})`))])
46
46
  : new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(as.import(bs as List<Node<string>>).import(cs)))))]);
47
47
  },
48
48
  ([as, bs = new List()], context) => {
49
49
  const { source, position, range, linebreak } = context;
50
50
  const str = linebreak === 0 ? source.slice(position - range + 1, position) : '';
51
- return linebreak === 0 && indexA.test(str)
51
+ return linebreak === 0 && (str === '' || indexA.test(str))
52
52
  ? new List([new Node(html('span', { class: 'paren' }, `(${str}`))])
53
53
  : new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(as.import(bs as List<Node<string>>)))))]);
54
54
  }));
@@ -60,14 +60,14 @@ const p2 = lazy(() => surround(
60
60
  true, [],
61
61
  ([as, bs = [], cs], { source, position, range, linebreak }) => {
62
62
  const str = linebreak === 0 ? source.slice(position - range + 1, position - 1) : '';
63
- return linebreak === 0 && indexF.test(str)
63
+ return linebreak === 0 && (str === '' || indexF.test(str))
64
64
  ? new List([new Node(html('span', { class: 'paren' }, `(${str})`))])
65
65
  : new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(as.import(bs as List<Node<string>>).import(cs)))))]);
66
66
  },
67
67
  ([as, bs = new List()], context) => {
68
68
  const { source, position, range, linebreak } = context;
69
69
  const str = linebreak === 0 ? source.slice(position - range + 1, position) : '';
70
- return linebreak === 0 && indexF.test(str)
70
+ return linebreak === 0 && (str === '' || indexF.test(str))
71
71
  ? new List([new Node(html('span', { class: 'paren' }, `(${str}`))])
72
72
  : new List([new Node(html('span', { class: 'bracket' }, defrag(unwrap(as.import(bs as List<Node<string>>)))))]);
73
73
  }));
@@ -15,6 +15,7 @@ const MAX = 60;
15
15
  const ELLIPSIS = '...';
16
16
  const PART = (MAX - ELLIPSIS.length) / 2 | 0;
17
17
  const REM = MAX - PART * 2 - ELLIPSIS.length;
18
+ assert(PART * 2 + REM + ELLIPSIS.length === MAX);
18
19
  const table = [
19
20
  ...[...Array(36)].map((_, i) => i.toString(36)),
20
21
  ...[...Array(36)].map((_, i) => i.toString(36).toUpperCase()).slice(-26),
@@ -15,6 +15,9 @@ describe('Unit: parser/inline/reference', () => {
15
15
  assert.deepStrictEqual(inspect(parser, input('[[', new Context())), undefined);
16
16
  assert.deepStrictEqual(inspect(parser, input('[[]]', new Context())), undefined);
17
17
  assert.deepStrictEqual(inspect(parser, input('[[]]]', new Context())), undefined);
18
+ assert.deepStrictEqual(inspect(parser, input('[[a', new Context())), undefined);
19
+ assert.deepStrictEqual(inspect(parser, input('[[a]', new Context())), undefined);
20
+ assert.deepStrictEqual(inspect(parser, input('[[a]b]', new Context())), undefined);
18
21
  assert.deepStrictEqual(inspect(parser, input('[["]]', new Context())), undefined);
19
22
  assert.deepStrictEqual(inspect(parser, input('[[(]]', new Context())), undefined);
20
23
  assert.deepStrictEqual(inspect(parser, input('[[[%]]', new Context())), undefined);
@@ -51,6 +54,7 @@ describe('Unit: parser/inline/reference', () => {
51
54
  assert.deepStrictEqual(inspect(parser, input('[[@a]]', new Context())), [['<sup class="reference"><span><a class="account" href="/@a">@a</a></span></sup>'], '']);
52
55
  assert.deepStrictEqual(inspect(parser, input('[[http://host]]', new Context())), [['<sup class="reference"><span><a class="url" href="http://host" target="_blank">http://host</a></span></sup>'], '']);
53
56
  assert.deepStrictEqual(inspect(parser, input('[[![]{a}]]', new Context())), [['<sup class="reference"><span>!<a class="url" href="a">a</a></span></sup>'], '']);
57
+ assert.deepStrictEqual(inspect(parser, input('[[a[[]]]]', new Context())), [['<sup class="reference"><span>a[[]]</span></sup>'], '']);
54
58
  assert.deepStrictEqual(inspect(parser, input('[[[a]]]', new Context())), [['<sup class="reference"><span>[a]</span></sup>'], '']);
55
59
  assert.deepStrictEqual(inspect(parser, input('[[[[a]]]]', new Context())), [['<sup class="reference"><span>[[a]]</span></sup>'], '']);
56
60
  assert.deepStrictEqual(inspect(parser, input('[[((a))]]', new Context())), [['<sup class="reference"><span><span class="bracket">(<span class="paren">(a)</span>)</span></span></sup>'], '']);
@@ -21,13 +21,12 @@ export const reference: ReferenceParser = lazy(() => constraint(State.reference,
21
21
  [2, 1 | Backtrack.common, 3 | Backtrack.doublebracket],
22
22
  ([, ns], context) => {
23
23
  const { position, range, linebreak } = context;
24
- if (linebreak === 0) {
25
- return new List([new Node(html('sup', attributes(ns), [html('span', defrag(unwrap(trimBlankNodeEnd(ns))))]))]);
26
- }
27
- else {
28
- const head = position - range;
24
+ const head = position - range;
25
+ if (linebreak !== 0) {
29
26
  setBacktrack(context, 2 | Backtrack.link, head, 2);
27
+ return;
30
28
  }
29
+ return new List([new Node(html('sup', attributes(ns), [html('span', defrag(unwrap(trimBlankNodeEnd(ns))))]))]);
31
30
  },
32
31
  (_, context): undefined => {
33
32
  const { source, position, range, linebreak } = context;
@@ -83,11 +83,11 @@ describe('Unit: parser/inline', () => {
83
83
  assert.deepStrictEqual(inspect(parser, input('*++ a ++*', new Context())), [['<em><ins> a </ins></em>'], '']);
84
84
  assert.deepStrictEqual(inspect(parser, input('*<bdi>`a`</bdi>*', new Context())), [['<em><bdi><code data-src="`a`">a</code></bdi></em>'], '']);
85
85
  assert.deepStrictEqual(inspect(parser, input('*a"\nb*', new Context())), [['<em>a"<br>b</em>'], '']);
86
- assert.deepStrictEqual(inspect(parser, input('*a"\n"("b*', new Context())), [['<em>a"<br>"<span class="bracket">(</span>"b</em>'], '']);
86
+ assert.deepStrictEqual(inspect(parser, input('*a"\n"("b*', new Context())), [['<em>a"<br>"<span class="paren">(</span>"b</em>'], '']);
87
87
  assert.deepStrictEqual(inspect(parser, input('"*a\nb*', new Context())), [['"', '*', 'a', '<br>', 'b', '*'], '']);
88
88
  assert.deepStrictEqual(inspect(parser, input('"*a\n""b*', new Context())), [['"', '*', 'a', '<br>', '"', '"', 'b', '*'], '']);
89
89
  assert.deepStrictEqual(inspect(parser, input('"a\n"*b"c*', new Context())), [['"', 'a', '<br>', '"', '*', 'b', '"', 'c', '*'], '']);
90
- assert.deepStrictEqual(inspect(parser, input('"*a**b\nc**"("*', new Context())), [['"', '*', 'a', '**', 'b', '<br>', 'c', '**', '"', '<span class="bracket">(</span>', '"', '*'], '']);
90
+ assert.deepStrictEqual(inspect(parser, input('"*a**b\nc**"("*', new Context())), [['"', '*', 'a', '**', 'b', '<br>', 'c', '**', '"', '<span class="paren">(</span>', '"', '*'], '']);
91
91
  assert.deepStrictEqual(inspect(parser, input('[% a"\nb %]', new Context())), [['<span class="remark"><input type="checkbox"><span>[% a"<br>b %]</span></span>'], '']);
92
92
  assert.deepStrictEqual(inspect(parser, input('"<bdi>"a\n""b</bdi>"', new Context())), [['"', '<span class="invalid">&lt;bdi&gt;</span>', '"', 'a', '<br>', '"', '"', 'b', '</bdi', '>', '"'], '']);
93
93
  assert.deepStrictEqual(inspect(parser, input('<bdi>*<bdi>a</bdi>*</bdi>', new Context())), [['<bdi><em><bdi>a</bdi></em></bdi>'], '']);
@@ -140,7 +140,7 @@ describe('Unit: parser/inline', () => {
140
140
  assert.deepStrictEqual(inspect(parser, input('(([["*(*"] ]))', new Context())), [['<sup class="annotation"><span>[["*<span class="bracket">(*</span>"] ]</span></sup>'], '']);
141
141
  assert.deepStrictEqual(inspect(parser, input('(([:a\n]', new Context())), [['<span class="bracket">(<span class="bracket">(<span class="invalid">[:a\n]</span></span></span>'], '']);
142
142
  assert.deepStrictEqual(inspect(parser, input('(({{\n}}', new Context())), [['<span class="bracket">(<span class="bracket">(<span class="template">{{<br>}}</span></span></span>'], '']);
143
- assert.deepStrictEqual(inspect(parser, input('"((""))', new Context())), [['"', '<span class="bracket">(<span class="bracket">(</span></span>', '"', '"', ')', ')'], '']);
143
+ assert.deepStrictEqual(inspect(parser, input('"((""))', new Context())), [['"', '<span class="bracket">(<span class="paren">(</span></span>', '"', '"', ')', ')'], '']);
144
144
  assert.deepStrictEqual(inspect(parser, input('[[[a]]', new Context())), [['[', '<sup class="reference"><span>a</span></sup>'], '']);
145
145
  assert.deepStrictEqual(inspect(parser, input('[[[[a]]', new Context())), [['[', '[', '<sup class="reference"><span>a</span></sup>'], '']);
146
146
  assert.deepStrictEqual(inspect(parser, input('[[[[a]]]]', new Context())), [['<sup class="reference"><span>[[a]]</span></sup>'], '']);
@@ -222,72 +222,50 @@ describe('Unit: parser/processor/note', () => {
222
222
  });
223
223
 
224
224
  it('nest', () => {
225
- const target = parse('((a((b((c))))))((a))((b))((c))');
225
+ const target = parse('((a((b))))((a))((b))');
226
226
  for (let i = 0; i < 3; ++i) {
227
227
  [...note(target)];
228
228
  assert.deepStrictEqual(
229
229
  [...target.children].map(el => el.outerHTML),
230
230
  [
231
231
  html('p', [
232
- html('sup', { class: 'annotation', id: 'annotation::ref:a((b((c)))):1', title: 'a((b((c))))' }, [
233
- html('a', { href: '#annotation::def:a((b((c)))):1' }, '*1')
232
+ html('sup', { class: 'annotation', id: 'annotation::ref:a((b)):1', title: 'a((b))' }, [
233
+ html('a', { href: '#annotation::def:a((b)):1' }, '*1')
234
234
  ]),
235
235
  html('sup', { class: 'annotation', id: 'annotation::ref:a:1', title: 'a' }, [
236
- html('a', { href: '#annotation::def:a:1' }, '*4')
236
+ html('a', { href: '#annotation::def:a:1' }, '*3')
237
237
  ]),
238
- html('sup', { class: 'annotation', id: 'annotation::ref:b:1', title: 'b' }, [
239
- html('a', { href: '#annotation::def:b:1' }, '*5')
240
- ]),
241
- html('sup', { class: 'annotation', id: 'annotation::ref:c:2', title: 'c' }, [
242
- html('a', { href: '#annotation::def:c:1' }, '*3')
238
+ html('sup', { class: 'annotation', id: 'annotation::ref:b:2', title: 'b' }, [
239
+ html('a', { href: '#annotation::def:b:1' }, '*2')
243
240
  ]),
244
241
  ]).outerHTML,
245
242
  html('ol', { class: 'annotations' }, [
246
- html('li', { id: 'annotation::def:a((b((c)))):1', 'data-marker': '*1' }, [
243
+ html('li', { id: 'annotation::def:a((b)):1', 'data-marker': '*1' }, [
247
244
  html('span', [
248
245
  'a',
249
- html('sup', { class: 'annotation', id: 'annotation::ref:b((c)):1', title: 'b((c))' }, [
250
- html('a', { href: '#annotation::def:b((c)):1' }, '*2')
246
+ html('sup', { class: 'annotation', id: 'annotation::ref:b:1', title: 'b' }, [
247
+ html('a', { href: '#annotation::def:b:1' }, '*2')
251
248
  ]),
252
249
  ]),
253
250
  html('sup', [
254
- html('a', { href: '#annotation::ref:a((b((c)))):1' }, '^1'),
251
+ html('a', { href: '#annotation::ref:a((b)):1' }, '^1'),
255
252
  ])
256
253
  ]),
257
- html('li', { id: 'annotation::def:b((c)):1', 'data-marker': '*2' }, [
254
+ html('li', { id: 'annotation::def:b:1', 'data-marker': '*2' }, [
258
255
  html('span', [
259
256
  'b',
260
- html('sup', { class: 'annotation', id: 'annotation::ref:c:1', title: 'c' }, [
261
- html('a', { href: '#annotation::def:c:1' }, '*3')
262
- ]),
263
- ]),
264
- html('sup', [
265
- html('a', { href: '#annotation::ref:b((c)):1' }, '^2'),
266
- ])
267
- ]),
268
- html('li', { id: 'annotation::def:c:1', 'data-marker': '*3' }, [
269
- html('span', [
270
- 'c',
271
257
  ]),
272
258
  html('sup', [
273
- html('a', { href: '#annotation::ref:c:1' }, '^3'),
274
- html('a', { href: '#annotation::ref:c:2' }, '^6'),
259
+ html('a', { href: '#annotation::ref:b:1' }, '^2'),
260
+ html('a', { href: '#annotation::ref:b:2' }, '^4'),
275
261
  ])
276
262
  ]),
277
- html('li', { id: 'annotation::def:a:1', 'data-marker': '*4' }, [
263
+ html('li', { id: 'annotation::def:a:1', 'data-marker': '*3' }, [
278
264
  html('span', [
279
265
  'a',
280
266
  ]),
281
267
  html('sup', [
282
- html('a', { href: '#annotation::ref:a:1' }, '^4'),
283
- ])
284
- ]),
285
- html('li', { id: 'annotation::def:b:1', 'data-marker': '*5' }, [
286
- html('span', [
287
- 'b',
288
- ]),
289
- html('sup', [
290
- html('a', { href: '#annotation::ref:b:1' }, '^5'),
268
+ html('a', { href: '#annotation::ref:a:1' }, '^3'),
291
269
  ])
292
270
  ]),
293
271
  ]).outerHTML,
@@ -154,6 +154,9 @@ function build(
154
154
  : defs.get(identifier)!;
155
155
  initial && defs.set(identifier, def);
156
156
  assert(def.lastElementChild?.matches('sup'));
157
+ if (!initial && content.innerHTML.length > def.firstElementChild!.innerHTML.length) {
158
+ def.firstElementChild!.replaceWith(content);
159
+ }
157
160
  const defIndex = initial
158
161
  ? info.defIndex = total + defs.size
159
162
  : info.defIndex;
@@ -187,7 +190,7 @@ function build(
187
190
  info.queue.push(ref);
188
191
  break;
189
192
  }
190
- yield ref.appendChild(html('a', { href: refId && defId && `#${defId}` }, marker(defIndex, abbr)));
193
+ ref.appendChild(html('a', { href: refId && defId && `#${defId}` }, marker(defIndex, abbr)));
191
194
  assert(ref.title || ref.matches('.invalid'));
192
195
  def.lastElementChild!.appendChild(
193
196
  html('a',
@@ -196,6 +199,7 @@ function build(
196
199
  title: abbr && text || undefined,
197
200
  },
198
201
  `^${++refIndex}`));
202
+ yield;
199
203
  }
200
204
  if (note || defs.size > 0) {
201
205
  const splitter = splitters[iSplitters++];
@@ -19,7 +19,6 @@ export function render(source: HTMLElement, opts: RenderingOptions = {}): void {
19
19
  }
20
20
 
21
21
  function render_(base: string, source: HTMLElement, opts: RenderingOptions): void {
22
- if (source.classList.contains('invalid')) return;
23
22
  try {
24
23
  switch (true) {
25
24
  case !!opts.code