securemark 0.258.4 → 0.258.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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.258.7
4
+
5
+ - Refactoring.
6
+
7
+ ## 0.258.6
8
+
9
+ - Refactoring.
10
+
11
+ ## 0.258.5
12
+
13
+ - Refactoring.
14
+
3
15
  ## 0.258.4
4
16
 
5
17
  - Refactoring.
package/design.md CHANGED
@@ -284,17 +284,25 @@ CodeMirrorが素では速いがVimModeでは数万文字程度でも耐え難く
284
284
 
285
285
  ### バックトラック
286
286
 
287
- SecuremarkのAnnotation構文に典型的であるように文脈を変更する構文とその文脈に依存し変更される他の構文が存在する場合文脈の相違から解析結果を再利用不能な(`αA'β | αAB`)バックトラックが生じる。ここで解析中に文脈依存構文に遭遇した時、現在の文脈での解析を継続する場合はバックトラックが再帰的に生じ線形時間で解析できず、現在の文脈での解析を中止し直前の文脈を継続または新たな文脈を開始する場合は線形時間で解析できる。
288
- CommonMarkはLink構文から明らかなように後者の解析方法により線形時間で解析できる言語であるがこれは同時に文脈依存構文の正当な入れ子表現すら解析できず文脈依存構文の存在する数に応じて指数関数的に構文の壊れやすさの増す脆く拡張性の低い言語であることを意味する。
289
- 従って線形時間で解析可能な文法に制約されるCommonMarkには文脈依存構文を入れ子表現の広範な制限ならびに構文の可読性および開始記号の信頼性の指数関数的な低下と引き換えにしか追加できないという拡張性の限界が存在する。
290
- 対してSecuremarkは線形時間で解析不能な前者の解析方法を採用し解析時間と解析範囲の局限により線形時間で解析不能な入力の影響を局限することでこの制限を回避している。
291
- ただし実際には文脈依存構文の入れ子表現はほとんどがリンクの入れ子不可制約として正当に制限されており開始記号の文字列(終端記号)としての正当な使用も稀と思われるため実用上差異が生じることはほとんどないと思われる。
287
+ SecuremarkのAnnotation構文に典型的であるように文脈を変更する構文の中にその文脈に依存し変更される他の構文が存在する場合一般に文脈の相違から解析結果を再利用不能な(`αA'β | αAB`)バックトラックが生じる。文脈依存構文の解析中に文脈により解釈の異なる字句(`[a[`)または現在の文脈を終了する字句(ただの括弧がスコープを作らない場合の`(([a))`)に遭遇したとき、現在の文脈での解析の継続を試行する解析方法はメモ化なしではバックトラックが再帰的に生じ線形時間で解析できず、現在の文脈での解析を中止し直前の文脈を継続または新たな文脈を開始する解析方法はバックトラックなしで線形時間で解析できる。
288
+ CommonMarkはLink構文から明らかなように後者の解析方法により重篤なバックトラックなしで(インラインでは)ほぼ1回の走査で解析できる言語であるが同時にこれは文脈依存構文の正当な入れ子表現(`[<a@b>]`)を解析できないか不正な表現(`[<a@b>]()`)を除外できず二重リンク(`<a><a></a></a>`)を生成する、文脈依存構文の存在する数に応じて多項式的(おそらく$n^{2+c}$)に構文の壊れやすさの増す脆く拡張性の低い言語であることを意味する(構文の決定を遅延できれば解決できるがこれにより他の構文の解釈が非決定的にならない場合に限られる)。この問題はおそらくメモ化により解決できるがCommonMarkは実行性能追及のためメモ化を廃止しており二重リンクも放置しているためメモ化により性能を低下させてまで解決するつもりはないと思われる(すなわちCommonMarkは機械を至上とし人間に制約を課す低水準の言語であり人間の需要を至上とするSecuremarkとは対極に位置する)。
289
+ 従ってほぼ1回の走査で解析可能な文法に制約されるCommonMarkには文脈依存構文を入れ子表現の広範な制限ならびに構文の可読性および開始記号の信頼性の多項式的な低下と引き換えにしか追加できないという拡張性の欠陥が存在する。CommonMarkの仕様策定者が構文の拡張に(名称を維持するか否かにかかわらず)不自然なまでに消極的または進展がないのは正当な理由や怠慢からでなく文脈依存構文を追加するにつれて構文解析戦略の欠陥が明白になっていくためおよび現在の高い実行性能を低下させたくないためである。`~~a~~`のような文脈自由構文は容易に追加できるがこうしたマージンを失えばもはや後はない。
290
+ Securemarkは線形時間で解析不能な前者の解析方法を各種最適化によりおおよそ4n以下の最悪計算量に改善しさらに解析時間と解析範囲の局限により一定時間内で解析不能な入力の影響を局限することでこれらの問題を解決している。この解析方法はほとんどの自然な入力に対して線形に近い時間で効率的に動作し、最悪計算量で低速に動作させる少数の機械的攻撃入力に対してもサーバーで多数のユーザーのリクエストに応じるには低速で脆弱性となる可能性があるがクライアントで単一のユーザーの操作に応じるには十分高速であるためクライアントで解析する限り解析の効率または速度が実用上問題となることはない。
291
+
292
+ ### メモ化
293
+
294
+ 一部の文脈依存言語を線形時間で解析できるようになるとしても状態数に応じて複数回走査が必要なこと、これにより時間効率が定性的には改善されても定量的には文脈自由言語より数倍悪いこと、オーバーヘッドが有意に大きいこと、時間の代わりに空間効率が非線形に悪化する可能性があることなどから理論上同じ線形時間だとしても実用上文脈自由言語と同等の実行性能にはならず文脈依存言語を文脈自由言語と等価の選択肢にするものではない。
295
+
296
+ ### 最適化
297
+
298
+ 現在Securemarkのボトルネックはプロファイルによると単なるパーサーの呼び出しが並んでおり引数の入力文字列を値渡しのためコピーするコストである可能性が疑われる。アルゴリズムとしてはメモ化したパース結果を複製せず永続データ構造として使用できるようおよび後処理に必要となるノードを収集しやすいようCSTを木構造に置き換える必要がある。またDOM出力の分離によりバックトラックのコストを削減する。
292
299
 
293
300
  ### 標準化
294
301
 
295
302
  Markdownのように自然言語と自身の文法を分離する汎用構造がないメタ言語は構文を拡張する際に旧構文を破壊する新構文が自然言語の中に潜在せざるをえず後方互換性を保証することが不可能である。
296
303
  このためCommonMarkは拡張構文を標準化した次期標準仕様との互換性を確保する役には立たずその有用性は構文の拡張を考慮しない範囲での効率的な実装方法の例示およびテストケースの集積による個別実装の支援ならびにその結果としての限定的互換性にとどまる。
297
304
  さらにCommonMarkは前述の解析時間の制約から拡張性が低く、高度な構文や機能の実装可能性を考慮していないことから拡張仕様において準拠すべき技術的正当性もない。
305
+ 実際GFMが二種類のフェンスドブロックのセマンティクスを分けずCommonMarkに準拠して単なるエイリアスとして有用な記号を浪費したのは顕著かつ明白な失敗だったと言えよう(Securemarkでは入力文字列の無変換表示(シンタックスハイライト)用と変換表示(拡張構文)用に分かれている)。
298
306
 
299
307
  よってMarkdownの標準化は後方互換性確保が不可能であることから発展性がなくスナップショット以上の技術的意味を持たない。
300
308
  MarkdownはGFMのように最初から高機能で完成度の高い拡張不要な独自実装のほうが標準としての互換性を確保でき、構文に曖昧さがない形式言語と異なりまず最小限の標準仕様を策定しのちに拡張していく通常の標準化方法が適さない特殊な言語である。
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.258.4 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.258.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("DOMPurify"), require("Prism"));
@@ -2856,42 +2856,28 @@ function apply(parser, source, context, changes, values) {
2856
2856
  }
2857
2857
 
2858
2858
  function syntax(syntax, precedence, cost, parser) {
2859
- return (source, context) => {
2859
+ return creation(cost, (source, context) => {
2860
2860
  if (source === '') return;
2861
2861
  const memo = context.memo ??= new memo_1.Memo();
2862
2862
  context.memorable ??= ~0;
2863
2863
  const p = context.precedence;
2864
2864
  context.precedence = precedence;
2865
- const {
2866
- resources = {
2867
- clock: 1,
2868
- recursion: 1
2869
- }
2870
- } = context;
2871
- if (resources.clock <= 0) throw new Error('Too many creations');
2872
- if (resources.recursion <= 0) throw new Error('Too much recursion');
2873
- --resources.recursion;
2874
2865
  const position = source.length;
2875
2866
  const state = context.state ?? 0;
2876
2867
  const cache = syntax && memo.get(position, syntax, state);
2877
2868
  const result = cache ? cache.length === 0 ? global_1.undefined : [cache[0], source.slice(cache[1])] : parser(source, context);
2878
- ++resources.recursion;
2879
2869
 
2880
- if (result && !cache) {
2881
- resources.clock -= cost;
2870
+ if (syntax && state & context.memorable) {
2871
+ cache ?? memo.set(position, syntax, state, (0, parser_1.eval)(result), source.length - (0, parser_1.exec)(result, '').length);
2882
2872
  }
2883
2873
 
2884
- if (syntax) {
2885
- if (state & context.memorable) {
2886
- cache ?? memo.set(position, syntax, state, (0, parser_1.eval)(result), source.length - (0, parser_1.exec)(result, '').length);
2887
- } else if (result && memo.length >= position) {
2888
- memo.clear(position);
2889
- }
2874
+ if (result && !state && memo.length >= position) {
2875
+ memo.clear(position);
2890
2876
  }
2891
2877
 
2892
2878
  context.precedence = p;
2893
2879
  return result;
2894
- };
2880
+ });
2895
2881
  }
2896
2882
 
2897
2883
  exports.syntax = syntax;
@@ -3039,13 +3025,18 @@ class Delimiters {
3039
3025
  } = this;
3040
3026
 
3041
3027
  for (let i = 0; i < matchers.length; ++i) {
3042
- switch (matchers[i][3](source)) {
3028
+ const matcher = matchers[i];
3029
+ if (precedence >= matcher[2]) continue;
3030
+
3031
+ switch (matcher[3](source)) {
3043
3032
  case true:
3044
- if (precedence < matchers[i][2]) return true;
3045
- continue;
3033
+ return true;
3046
3034
 
3047
3035
  case false:
3048
3036
  return false;
3037
+
3038
+ default:
3039
+ continue;
3049
3040
  }
3050
3041
  }
3051
3042
 
@@ -3108,7 +3099,7 @@ class Memo {
3108
3099
 
3109
3100
  for (let i = position + this.offset, len = memory.length; i < len; ++i) {
3110
3101
  memory.pop();
3111
- } //console.log('clear', position);
3102
+ } //console.log('clear', position + this.offset + 1);
3112
3103
 
3113
3104
  }
3114
3105
 
@@ -4825,7 +4816,7 @@ function format(rows) {
4825
4816
  const cell = isVirtual ? (0, array_1.splice)(cells, j, 0, global_1.undefined) && ranges[i][j] : cells[j];
4826
4817
  const isHeadCell = cell.tagName === 'TH';
4827
4818
  heads |= (0, global_1.BigInt)(isHeadCell) << jn;
4828
- highlights |= (0, global_1.BigInt)(cell.classList.contains('highlight')) << jn;
4819
+ highlights |= (0, global_1.BigInt)(cell.className === 'highlight') << jn;
4829
4820
  hasDataCell ||= !isHeadCell;
4830
4821
 
4831
4822
  if (isHeadCell && !hasDataCell) {
@@ -5177,7 +5168,7 @@ function initial(type) {
5177
5168
  }
5178
5169
 
5179
5170
  function format(el, type, form) {
5180
- if (el.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
5171
+ if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
5181
5172
  el.setAttribute('class', 'checklist');
5182
5173
  }
5183
5174
 
@@ -5359,7 +5350,7 @@ const qblock = (source, context) => {
5359
5350
  continue;
5360
5351
  }
5361
5352
 
5362
- if (child.classList.contains('cite') || child.classList.contains('quote')) {
5353
+ if (child.className === 'cite' || child.classList.contains('quote')) {
5363
5354
  context.resources && (context.resources.clock -= child.childNodes.length);
5364
5355
  nodes.splice(i, 1, ...child.childNodes);
5365
5356
  --i;
@@ -5448,7 +5439,7 @@ const head = (0, combinator_1.creation)((0, combinator_1.fmap)(cell, ns => [(0,
5448
5439
  const data = (0, combinator_1.creation)((0, combinator_1.fmap)(cell, ns => [(0, dom_1.html)('td', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))]));
5449
5440
 
5450
5441
  function format(rows) {
5451
- const aligns = rows[0].classList.contains('invalid') ? [] : (0, duff_1.duffReduce)(rows.shift().children, (acc, el) => (0, array_1.push)(acc, [el.textContent]), []);
5442
+ const aligns = rows[0].className === 'invalid' ? [] : (0, duff_1.duffReduce)(rows.shift().children, (acc, el) => (0, array_1.push)(acc, [el.textContent]), []);
5452
5443
 
5453
5444
  for (let i = 0; i < rows.length; ++i) {
5454
5445
  (0, duff_1.duffEach)(rows[i].children, (col, i) => {
@@ -5506,7 +5497,7 @@ function fillFirstLine(ns) {
5506
5497
  exports.fillFirstLine = fillFirstLine;
5507
5498
 
5508
5499
  function format(el) {
5509
- if (el.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
5500
+ if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
5510
5501
  el.setAttribute('class', 'checklist');
5511
5502
  }
5512
5503
 
@@ -5703,8 +5694,6 @@ const combinator_1 = __webpack_require__(2087);
5703
5694
 
5704
5695
  const inline_1 = __webpack_require__(1160);
5705
5696
 
5706
- const link_1 = __webpack_require__(9628);
5707
-
5708
5697
  const visibility_1 = __webpack_require__(7618);
5709
5698
 
5710
5699
  const dom_1 = __webpack_require__(3252);
@@ -5721,7 +5710,7 @@ exports.annotation = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('((
5721
5710
  delimiters: global_1.undefined
5722
5711
  }, (0, combinator_1.some)((0, combinator_1.union)([inline_1.inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')')))), '))', false, ([, ns], rest) => [[(0, dom_1.html)('sup', {
5723
5712
  class: 'annotation'
5724
- }, [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest], ([, ns, rest], next) => next[0] === ')' ? global_1.undefined : (0, link_1.optimize)('((', ns, rest, next)));
5713
+ }, [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest]));
5725
5714
 
5726
5715
  /***/ }),
5727
5716
 
@@ -6476,7 +6465,7 @@ const visibility_1 = __webpack_require__(7618);
6476
6465
 
6477
6466
  const dom_1 = __webpack_require__(3252);
6478
6467
 
6479
- const array_1 = __webpack_require__(8112); // Don't use the symbols already used: !#$@&*+~=
6468
+ const array_1 = __webpack_require__(8112); // Don't use the symbols already used: !#$%@&*+~=
6480
6469
  // All syntax surrounded by square brackets shouldn't contain line breaks.
6481
6470
 
6482
6471
 
@@ -6528,11 +6517,11 @@ const attrspecs = {
6528
6517
  };
6529
6518
  global_1.Object.setPrototypeOf(attrspecs, null);
6530
6519
  global_1.Object.values(attrspecs).forEach(o => global_1.Object.setPrototypeOf(o, null));
6531
- exports.html = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('<', (0, combinator_1.validate)(/^<[a-z]+(?=[^\S\n]|>)/, (0, combinator_1.syntax)(0
6520
+ exports.html = (0, combinator_1.lazy)(() => (0, combinator_1.validate)('<', (0, combinator_1.validate)(/^<[a-z]+(?=[^\S\n]|>)/i, (0, combinator_1.syntax)(0
6532
6521
  /* Syntax.none */
6533
- , 5, 1, (0, combinator_1.union)([(0, combinator_1.focus)(/^<wbr[^\S\n]*>/, () => [[(0, dom_1.html)('wbr')], '']), (0, combinator_1.focus)( // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
6534
- /^<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[^\S\n]|>)/, source => [[source], '']), (0, combinator_1.match)(new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`), (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)(/^[^\S\n]*>/), true), (0, combinator_1.subsequence)([(0, combinator_1.focus)(/^[^\S\n]*\n/, (0, combinator_1.some)(inline_1.inline)), (0, combinator_1.some)((0, combinator_1.open)(/^\n?/, (0, combinator_1.some)(inline_1.inline, (0, visibility_1.blankWith)('\n', `</${tag}>`), [[(0, visibility_1.blankWith)('\n', `</${tag}>`), 5]]), true))]), (0, source_1.str)(`</${tag}>`), true, ([as, bs = [], cs], rest) => [[elem(tag, as, bs, cs)], rest], ([as, bs = []], rest) => [[elem(tag, as, bs, [])], rest]), ([, tag]) => TAGS.indexOf(tag), [])), (0, combinator_1.match)(/^<([a-z]+)(?=[^\S\n]|>)/, (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)(/^[^\S\n]*>/), true), (0, combinator_1.subsequence)([(0, combinator_1.focus)(/^[^\S\n]*\n/, (0, combinator_1.some)(inline_1.inline)), (0, combinator_1.some)(inline_1.inline, `</${tag}>`, [[`</${tag}>`, 5]])]), (0, source_1.str)(`</${tag}>`), true, ([as, bs = [], cs], rest) => [[elem(tag, as, bs, cs)], rest], ([as, bs = []], rest) => [[elem(tag, as, bs, [])], rest]), ([, tag]) => tag, new cache_1.Cache(10000)))])))));
6535
- exports.attribute = (0, combinator_1.union)([(0, source_1.str)(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|>)/)]); // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
6522
+ , 5, 1, (0, combinator_1.union)([(0, combinator_1.focus)(/^<wbr[^\S\n]*>/i, () => [[(0, dom_1.html)('wbr')], '']), (0, combinator_1.surround)( // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
6523
+ (0, source_1.str)(/^<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[^\S\n]|>)/i), (0, combinator_1.some)((0, combinator_1.union)([exports.attribute])), (0, source_1.str)(/^[^\S\n]*>/), true, ([as, bs = [], cs], rest) => [[elem(as[0].slice(1), (0, array_1.push)((0, array_1.unshift)(as, bs), cs), [], [])], rest]), (0, combinator_1.match)(new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`), (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)(/^[^\S\n]*>/), true), (0, combinator_1.subsequence)([(0, combinator_1.focus)(/^[^\S\n]*\n/, (0, combinator_1.some)(inline_1.inline)), (0, combinator_1.some)((0, combinator_1.open)(/^\n?/, (0, combinator_1.some)(inline_1.inline, (0, visibility_1.blankWith)('\n', `</${tag}>`), [[(0, visibility_1.blankWith)('\n', `</${tag}>`), 5]]), true))]), (0, source_1.str)(`</${tag}>`), true, ([as, bs = [], cs], rest) => [[elem(tag, as, bs, cs)], rest], ([as, bs = []], rest) => [[elem(tag, as, bs, [])], rest]), ([, tag]) => TAGS.indexOf(tag), [])), (0, combinator_1.match)(/^<([a-z]+)(?=[^\S\n]|>)/i, (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.some)(exports.attribute), (0, source_1.str)(/^[^\S\n]*>/), true), (0, combinator_1.subsequence)([(0, combinator_1.focus)(/^[^\S\n]*\n/, (0, combinator_1.some)(inline_1.inline)), (0, combinator_1.some)(inline_1.inline, `</${tag}>`, [[`</${tag}>`, 5]])]), (0, source_1.str)(`</${tag}>`), true, ([as, bs = [], cs], rest) => [[elem(tag, as, bs, cs)], rest], ([as, bs = []], rest) => [[elem(tag, as, bs, [])], rest]), ([, tag]) => tag, new cache_1.Cache(10000)))])))));
6524
+ exports.attribute = (0, combinator_1.union)([(0, source_1.str)(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|>)/i)]); // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
6536
6525
  // [...document.querySelectorAll('tbody > tr > td:first-child')].map(el => el.textContent.slice(1, -1))
6537
6526
 
6538
6527
  const TAGS = global_1.Object.freeze(["html", "base", "head", "link", "meta", "style", "title", "body", "address", "article", "aside", "footer", "header", "h1", "h2", "h3", "h4", "h5", "h6", "main", "nav", "section", "blockquote", "dd", "div", "dl", "dt", "figcaption", "figure", "hr", "li", "menu", "ol", "p", "pre", "ul", "a", "abbr", "b", "bdi", "bdo", "br", "cite", "code", "data", "dfn", "em", "i", "kbd", "mark", "q", "rp", "rt", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "time", "u", "var", "wbr", "area", "audio", "img", "map", "track", "video", "embed", "iframe", "object", "picture", "portal", "source", "svg", "math", "canvas", "noscript", "script", "del", "ins", "caption", "col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "button", "datalist", "fieldset", "form", "input", "label", "legend", "meter", "optgroup", "option", "output", "progress", "select", "textarea", "details", "dialog", "summary", "slot", "template", "acronym", "applet", "basefont", "bgsound", "big", "blink", "center", "content", "dir", "font", "frame", "frameset", "hgroup", "image", "keygen", "marquee", "menuitem", "nobr", "noembed", "noframes", "param", "plaintext", "rb", "rtc", "shadow", "spacer", "strike", "tt", "xmp"]);
@@ -6660,7 +6649,7 @@ exports.insertion = (0, combinator_1.lazy)(() => (0, combinator_1.surround)((0,
6660
6649
  Object.defineProperty(exports, "__esModule", ({
6661
6650
  value: true
6662
6651
  }));
6663
- exports.optimize = exports.resolve = exports.option = exports.uri = exports.textlink = exports.link = void 0;
6652
+ exports.resolve = exports.option = exports.uri = exports.textlink = exports.link = void 0;
6664
6653
 
6665
6654
  const global_1 = __webpack_require__(4128);
6666
6655
 
@@ -6706,7 +6695,7 @@ exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[', '{'
6706
6695
  /* State.media */
6707
6696
  | 1
6708
6697
  /* State.autolink */
6709
- , (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2]])), ']', true, global_1.undefined, ([, ns = [], rest], next) => next[0] === ']' ? global_1.undefined : optimize('[', ns, rest, next))]))), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))], nodes => nodes[0][0] !== ''), ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []]))), ([content, params], rest, context) => {
6698
+ , (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2]])), ']', true)]))), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))], nodes => nodes[0][0] !== ''), ([as, bs = []]) => bs[0] === '\r' && bs.shift() ? [as, bs] : as[0] === '\r' && as.shift() ? [[], as] : [as, []]))), ([content, params], rest, context) => {
6710
6699
  if (content[0] === '') return [content, rest];
6711
6700
  if (params.length === 0) return;
6712
6701
  if (content.length !== 0 && (0, visibility_1.trimNode)(content).length === 0) return;
@@ -6719,7 +6708,7 @@ exports.link = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[', '{'
6719
6708
 
6720
6709
  const INSECURE_URI = params.shift();
6721
6710
  const el = elem(INSECURE_URI, (0, dom_1.defrag)(content), new url_1.ReadonlyURL(resolve(INSECURE_URI, context.host ?? global_1.location, context.url ?? context.host ?? global_1.location), context.host?.href || global_1.location.href), context.host?.origin || global_1.location.origin);
6722
- if (el.classList.contains('invalid')) return [[el], rest];
6711
+ if (el.className === 'invalid') return [[el], rest];
6723
6712
  return [[(0, dom_1.define)(el, (0, html_1.attributes)('link', [], optspec, params))], rest];
6724
6713
  })));
6725
6714
  exports.textlink = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(['[', '{'], (0, combinator_1.bind)((0, combinator_1.creation)(10, (0, combinator_1.precedence)(2, (0, combinator_1.reverse)((0, combinator_1.tails)([(0, combinator_1.dup)((0, combinator_1.surround)('[', (0, combinator_1.some)((0, combinator_1.union)([source_1.unescsource]), ']'), ']')), (0, combinator_1.dup)((0, combinator_1.surround)(/^{(?![{}])/, (0, combinator_1.inits)([exports.uri, (0, combinator_1.some)(exports.option)]), /^[^\S\n]*}/))])))), ([params, content = []], rest, context) => {
@@ -6806,22 +6795,6 @@ function decode(uri) {
6806
6795
  }
6807
6796
  }
6808
6797
 
6809
- function optimize(opener, ns, rest, next) {
6810
- if (next[+(next[0] === '\\')] === '\n') return;
6811
- let count = 0;
6812
-
6813
- for (let i = 0; i < ns.length - 1; i += 2) {
6814
- const fst = ns[i];
6815
- const snd = ns[i + 1];
6816
- if (fst !== '' || snd[0] !== opener[0]) break;
6817
- count += snd.length;
6818
- }
6819
-
6820
- return [['', opener[0].repeat(opener.length + count)], rest.slice(count)];
6821
- }
6822
-
6823
- exports.optimize = optimize;
6824
-
6825
6798
  /***/ }),
6826
6799
 
6827
6800
  /***/ 2480:
@@ -7017,8 +6990,6 @@ const combinator_1 = __webpack_require__(2087);
7017
6990
 
7018
6991
  const inline_1 = __webpack_require__(1160);
7019
6992
 
7020
- const link_1 = __webpack_require__(9628);
7021
-
7022
6993
  const source_1 = __webpack_require__(6743);
7023
6994
 
7024
6995
  const visibility_1 = __webpack_require__(7618);
@@ -7039,7 +7010,7 @@ exports.reference = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('[['
7039
7010
  /* State.media */
7040
7011
  , (0, visibility_1.startLoose)((0, combinator_1.context)({
7041
7012
  delimiters: global_1.undefined
7042
- }, (0, combinator_1.subsequence)([abbr, (0, combinator_1.open)((0, source_1.stropt)(/^(?=\^)/), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])])), ']')))), ']]', false, ([, ns], rest) => [[(0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest], ([, ns, rest], next) => next[0] === ']' ? global_1.undefined : (0, link_1.optimize)('[[', ns, rest, next)));
7013
+ }, (0, combinator_1.subsequence)([abbr, (0, combinator_1.open)((0, source_1.stropt)(/^(?=\^)/), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])), (0, combinator_1.some)(inline_1.inline, ']', [[/^\\?\n/, 9], [']', 2], [']]', 6]])])), ']')))), ']]', false, ([, ns], rest) => [[(0, dom_1.html)('sup', attributes(ns), [(0, dom_1.html)('span', (0, visibility_1.trimNode)((0, dom_1.defrag)(ns)))])], rest]));
7043
7014
  const abbr = (0, combinator_1.creation)((0, combinator_1.bind)((0, combinator_1.surround)('^', (0, combinator_1.union)([(0, source_1.str)(/^(?![0-9]+\s?[|\]])[0-9A-Za-z]+(?:(?:-|(?=\W)(?!'\d)'?(?!\.\d)\.?(?!,\S),? ?)[0-9A-Za-z]+)*(?:-|'?\.?,? ?)?/)]), /^\|?(?=]])|^\|[^\S\n]*/), ([source], rest) => [[(0, dom_1.html)('abbr', source)], rest.replace(visibility_1.regBlankStart, '')]));
7044
7015
 
7045
7016
  function attributes(ns) {
@@ -7228,8 +7199,6 @@ const global_1 = __webpack_require__(4128);
7228
7199
 
7229
7200
  const combinator_1 = __webpack_require__(2087);
7230
7201
 
7231
- const link_1 = __webpack_require__(9628);
7232
-
7233
7202
  const source_1 = __webpack_require__(6743);
7234
7203
 
7235
7204
  const dom_1 = __webpack_require__(3252);
@@ -7240,7 +7209,7 @@ exports.template = (0, combinator_1.lazy)(() => (0, combinator_1.surround)('{{',
7240
7209
  /* Syntax.none */
7241
7210
  , 2, 1, (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), '}')), '}}', true, ([, ns = []], rest) => [[(0, dom_1.html)('span', {
7242
7211
  class: 'template'
7243
- }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest], ([, ns = [], rest], next) => next[0] === '}' ? global_1.undefined : (0, link_1.optimize)('{{', ns, rest, next)));
7212
+ }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest]));
7244
7213
  const bracket = (0, combinator_1.lazy)(() => (0, combinator_1.creation)((0, combinator_1.union)([(0, combinator_1.surround)((0, source_1.str)('('), (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), ')'), (0, source_1.str)(')'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('['), (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), ']'), (0, source_1.str)(']'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('{'), (0, combinator_1.some)((0, combinator_1.union)([bracket, source_1.escsource]), '}'), (0, source_1.str)('}'), true, global_1.undefined, ([as, bs = []], rest) => [(0, array_1.unshift)(as, bs), rest]), (0, combinator_1.surround)((0, source_1.str)('"'), (0, combinator_1.precedence)(8, (0, combinator_1.some)(source_1.escsource, /^"|^\\?\n/)), (0, source_1.str)('"'), true)])));
7245
7214
 
7246
7215
  /***/ }),
@@ -8014,7 +7983,9 @@ exports.delimiter = /[\s\x00-\x7F]|\S[#>]|[()、。!?][^\S\n]*(?=\\\n)/;
8014
7983
  exports.nonWhitespace = /[\S\n]|$/;
8015
7984
  exports.nonAlphanumeric = /[^0-9A-Za-z]|\S[#>]|$/;
8016
7985
  const repeat = (0, str_1.str)(/^(.)\1*/);
8017
- exports.text = (0, combinator_1.creation)((source, context) => {
7986
+ exports.text = (0, combinator_1.syntax)(0
7987
+ /* Syntax.none */
7988
+ , 1, 1, (source, context) => {
8018
7989
  if (source === '') return;
8019
7990
  const i = source.search(exports.delimiter);
8020
7991
 
package/markdown.d.ts CHANGED
@@ -963,15 +963,15 @@ export namespace MarkdownParser {
963
963
  // <bdi>abc</bdi>
964
964
  Inline<'html'>,
965
965
  Parser<HTMLElement | string, Context, [
966
- HTMLParser.OpenTagParser,
967
- HTMLParser.OpenTagParser,
966
+ HTMLParser.VoidTagParser,
967
+ HTMLParser.VoidTagParser,
968
968
  HTMLParser.TagParser,
969
969
  HTMLParser.TagParser,
970
970
  ]> {
971
971
  }
972
972
  export namespace HTMLParser {
973
- export interface OpenTagParser extends
974
- Inline<'html/opentag'>,
973
+ export interface VoidTagParser extends
974
+ Inline<'html/voidtag'>,
975
975
  Parser<HTMLElement | string, Context, [
976
976
  AttributeParser,
977
977
  ]> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.258.4",
3
+ "version": "0.258.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",
@@ -57,12 +57,15 @@ export class Delimiters {
57
57
  public match(source: string, precedence = 1): boolean {
58
58
  const { matchers } = this;
59
59
  for (let i = 0; i < matchers.length; ++i) {
60
- switch (matchers[i][3](source)) {
60
+ const matcher = matchers[i];
61
+ if (precedence >= matcher[2]) continue;
62
+ switch (matcher[3](source)) {
61
63
  case true:
62
- if (precedence < matchers[i][2]) return true;
63
- continue;
64
+ return true;
64
65
  case false:
65
66
  return false;
67
+ default:
68
+ continue;
66
69
  }
67
70
  }
68
71
  return false;
@@ -34,6 +34,6 @@ export class Memo {
34
34
  for (let i = position + this.offset, len = memory.length; i < len; ++i) {
35
35
  memory.pop();
36
36
  }
37
- //console.log('clear', position);
37
+ //console.log('clear', position + this.offset + 1);
38
38
  }
39
39
  }
@@ -58,16 +58,12 @@ function apply<T>(parser: Parser<T>, source: string, context: Ctx, changes: [str
58
58
 
59
59
  export function syntax<P extends Parser<unknown>>(syntax: number, precedence: number, cost: number, parser: P): P;
60
60
  export function syntax<T>(syntax: number, precedence: number, cost: number, parser?: Parser<T>): Parser<T> {
61
- return (source, context) => {
61
+ return creation(cost, (source, context) => {
62
62
  if (source === '') return;
63
63
  const memo = context.memo ??= new Memo();
64
64
  context.memorable ??= ~0;
65
65
  const p = context.precedence;
66
66
  context.precedence = precedence;
67
- const { resources = { clock: 1, recursion: 1 } } = context;
68
- if (resources.clock <= 0) throw new Error('Too many creations');
69
- if (resources.recursion <= 0) throw new Error('Too much recursion');
70
- --resources.recursion;
71
67
  const position = source.length;
72
68
  const state = context.state ?? 0;
73
69
  const cache = syntax && memo.get(position, syntax, state);
@@ -76,23 +72,17 @@ export function syntax<T>(syntax: number, precedence: number, cost: number, pars
76
72
  ? undefined
77
73
  : [cache[0], source.slice(cache[1])]
78
74
  : parser!(source, context);
79
- ++resources.recursion;
80
- if (result && !cache) {
81
- resources.clock -= cost;
75
+ if (syntax && state & context.memorable!) {
76
+ cache ?? memo.set(position, syntax, state, eval(result), source.length - exec(result, '').length);
77
+ assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, state));
82
78
  }
83
- if (syntax) {
84
- if (state & context.memorable!) {
85
- cache ?? memo.set(position, syntax, state, eval(result), source.length - exec(result, '').length);
86
- assert.deepStrictEqual(cache && cache, cache && memo.get(position, syntax, state));
87
- }
88
- else if (result && memo.length! >= position) {
89
- assert(!(state & context.memorable!));
90
- memo.clear(position);
91
- }
79
+ if (result && !state && memo.length! >= position) {
80
+ assert(!(state & context.memorable!));
81
+ memo.clear(position);
92
82
  }
93
83
  context.precedence = p;
94
84
  return result;
95
- };
85
+ });
96
86
  }
97
87
 
98
88
  export function creation<P extends Parser<unknown>>(parser: P): P;
@@ -228,7 +228,7 @@ function format(rows: Tree<RowParser>[]): HTMLTableSectionElement[] {
228
228
  : cells[j];
229
229
  const isHeadCell = cell.tagName === 'TH';
230
230
  heads |= BigInt(isHeadCell) << jn;
231
- highlights |= BigInt(cell.classList.contains('highlight')) << jn;
231
+ highlights |= BigInt(cell.className === 'highlight') << jn;
232
232
  hasDataCell ||= !isHeadCell;
233
233
  if (isHeadCell && !hasDataCell) {
234
234
  lHeadCellIdx = jn;
@@ -115,7 +115,7 @@ function initial(type: string): RegExp {
115
115
  }
116
116
 
117
117
  function format(el: HTMLOListElement, type: string, form: string): HTMLOListElement {
118
- if (el.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
118
+ if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
119
119
  el.setAttribute('class', 'checklist');
120
120
  }
121
121
  define(el, {
@@ -57,7 +57,7 @@ const qblock: ReplyParser.QuoteParser.BlockParser = (source, context) => {
57
57
  ++i;
58
58
  continue;
59
59
  }
60
- if (child.classList.contains('cite') || child.classList.contains('quote')) {
60
+ if (child.className === 'cite' || child.classList.contains('quote')) {
61
61
  context.resources && (context.resources.clock -= child.childNodes.length);
62
62
  nodes.splice(i, 1, ...child.childNodes as NodeListOf<HTMLElement>);
63
63
  --i;
@@ -61,7 +61,7 @@ const data: CellParser.DataParser = creation(fmap(
61
61
  ns => [html('td', trimNode(defrag(ns)))]));
62
62
 
63
63
  function format(rows: HTMLTableRowElement[]): HTMLTableRowElement[] {
64
- const aligns = rows[0].classList.contains('invalid')
64
+ const aligns = rows[0].className === 'invalid'
65
65
  ? []
66
66
  : duffReduce(rows.shift()!.children, (acc, el) => push(acc, [el.textContent!]), [] as string[]);
67
67
  for (let i = 0; i < rows.length; ++i) {
@@ -41,7 +41,7 @@ export function fillFirstLine(ns: (HTMLElement | string)[]): (HTMLElement | stri
41
41
  }
42
42
 
43
43
  function format(el: HTMLUListElement): HTMLUListElement {
44
- if (el.firstElementChild?.firstElementChild?.classList.contains('checkbox')) {
44
+ if (el.firstElementChild?.firstElementChild?.className === 'checkbox') {
45
45
  el.setAttribute('class', 'checklist');
46
46
  }
47
47
  return el;
@@ -14,7 +14,7 @@ describe('Unit: parser/inline/annotation', () => {
14
14
  assert.deepStrictEqual(inspect(parser('(())')), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('(()))')), undefined);
16
16
  assert.deepStrictEqual(inspect(parser('(( ))')), undefined);
17
- assert.deepStrictEqual(inspect(parser('(( (a')), [['', '(('], ' (a']);
17
+ assert.deepStrictEqual(inspect(parser('(( (a')), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('((\n))')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('((\na))')), undefined);
20
20
  assert.deepStrictEqual(inspect(parser('((\\\na))')), undefined);
@@ -2,7 +2,6 @@ import { undefined } from 'spica/global';
2
2
  import { AnnotationParser } from '../inline';
3
3
  import { union, some, context, syntax, constraint, state, surround, lazy } from '../../combinator';
4
4
  import { inline } from '../inline';
5
- import { optimize } from './link';
6
5
  import { Syntax, State } from '../context';
7
6
  import { startLoose, trimNode } from '../visibility';
8
7
  import { html, defrag } from 'typed-dom/dom';
@@ -17,5 +16,4 @@ export const annotation: AnnotationParser = lazy(() => surround(
17
16
  some(union([inline]), ')', [[/^\\?\n/, 9], [')', 2], ['))', 6]])), ')')))),
18
17
  '))',
19
18
  false,
20
- ([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNode(defrag(ns)))])], rest],
21
- ([, ns, rest], next) => next[0] === ')' ? undefined : optimize('((', ns, rest, next)));
19
+ ([, ns], rest) => [[html('sup', { class: 'annotation' }, [html('span', trimNode(defrag(ns)))])], rest]));
@@ -75,7 +75,7 @@ describe('Unit: parser/inline/bracket', () => {
75
75
  assert.deepStrictEqual(inspect(parser('"a')), [['"', 'a'], '']);
76
76
  assert.deepStrictEqual(inspect(parser('"a"')), [['"', 'a', '"'], '']);
77
77
  assert.deepStrictEqual(inspect(parser('"(")"')), [['"', '', '(', '"'], ')"']);
78
- assert.deepStrictEqual(inspect(parser('"(("')), [['"', '', '((', '"'], '']);
78
+ assert.deepStrictEqual(inspect(parser('"(("')), [['"', '', '(', '', '(', '"'], '']);
79
79
  assert.deepStrictEqual(inspect(parser('"(\\")"')), [['"', '<span class="paren">(")</span>', '"'], '']);
80
80
  assert.deepStrictEqual(inspect(parser('"(\n)"')), [['"', '<span class="paren">(<br>)</span>', '"'], '']);
81
81
  assert.deepStrictEqual(inspect(parser('"(\\\n)"')), [['"', '<span class="paren">(<span class="linebreak"> </span>)</span>', '"'], '']);
@@ -7,7 +7,7 @@ import { startTight } from '../../visibility';
7
7
  import { html, defrag } from 'typed-dom/dom';
8
8
  import { unshift } from 'spica/array';
9
9
 
10
- // Don't use the symbols already used: !#$@&*+~=
10
+ // Don't use the symbols already used: !#$%@&*+~=
11
11
 
12
12
  // All syntax surrounded by square brackets shouldn't contain line breaks.
13
13
 
@@ -21,10 +21,10 @@ describe('Unit: parser/inline/html', () => {
21
21
  it('invalid', () => {
22
22
  assert.deepStrictEqual(inspect(parser('')), undefined);
23
23
  assert.deepStrictEqual(inspect(parser('<0>')), undefined);
24
- assert.deepStrictEqual(inspect(parser('<aT>')), undefined);
24
+ assert.deepStrictEqual(inspect(parser('<aT>')), [['<span class="invalid">&lt;aT&gt;</span>'], '']);
25
25
  assert.deepStrictEqual(inspect(parser('<a,b>')), undefined);
26
26
  assert.deepStrictEqual(inspect(parser('<a, b>')), undefined);
27
- assert.deepStrictEqual(inspect(parser('<T>')), undefined);
27
+ assert.deepStrictEqual(inspect(parser('<T>')), [['<span class="invalid">&lt;T&gt;</span>'], '']);
28
28
  assert.deepStrictEqual(inspect(parser('<bdi>')), [['<span class="invalid">&lt;bdi&gt;</span>'], '']);
29
29
  assert.deepStrictEqual(inspect(parser('<bdi>z')), [['<span class="invalid">&lt;bdi&gt;z</span>'], '']);
30
30
  assert.deepStrictEqual(inspect(parser('<bdi></bdi>')), [['<span class="invalid">&lt;bdi&gt;&lt;/bdi&gt;</span>'], '']);
@@ -39,8 +39,8 @@ describe('Unit: parser/inline/html', () => {
39
39
  assert.deepStrictEqual(inspect(parser('<bdi>\\\na</bdi>')), [['<span class="invalid">&lt;bdi&gt;<span class="linebreak"> </span>a&lt;/bdi&gt;</span>'], '']);
40
40
  assert.deepStrictEqual(inspect(parser('<bdi>a')), [['<span class="invalid">&lt;bdi&gt;a</span>'], '']);
41
41
  assert.deepStrictEqual(inspect(parser('<bdi>a</BDO>')), [['<span class="invalid">&lt;bdi&gt;a&lt;/BDO&gt;</span>'], '']);
42
- assert.deepStrictEqual(inspect(parser('<BDI>a</BDI>')), undefined);
43
- assert.deepStrictEqual(inspect(parser('<BDI>a</bdo>')), undefined);
42
+ assert.deepStrictEqual(inspect(parser('<BDI>a</BDI>')), [['<span class="invalid">&lt;BDI&gt;a&lt;/BDI&gt;</span>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('<BDI>a</bdo>')), [['<span class="invalid">&lt;BDI&gt;a&lt;/bdo&gt;</span>'], '']);
44
44
  assert.deepStrictEqual(inspect(parser('</bdi>')), undefined);
45
45
  assert.deepStrictEqual(inspect(parser('<bdi/>')), undefined);
46
46
  assert.deepStrictEqual(inspect(parser('<b><b><b>a</b></b></b>')), [['<span class="invalid">&lt;b&gt;<span class="invalid">&lt;b&gt;<span class="invalid">&lt;b&gt;a&lt;/b&gt;</span>&lt;/b&gt;</span>&lt;/b&gt;</span>'], '']);
@@ -87,9 +87,9 @@ describe('Unit: parser/inline/html', () => {
87
87
  assert.deepStrictEqual(inspect(parser('<a>')), [['<span class="invalid">&lt;a&gt;</span>'], '']);
88
88
  assert.deepStrictEqual(inspect(parser('<bdi><a>a</a></bdi>')), [['<bdi><span class="invalid">&lt;a&gt;a&lt;/a&gt;</span></bdi>'], '']);
89
89
  assert.deepStrictEqual(inspect(parser('<bdi>a<a>b</a>c</bdi>')), [['<bdi>a<span class="invalid">&lt;a&gt;b&lt;/a&gt;</span>c</bdi>'], '']);
90
- assert.deepStrictEqual(inspect(parser('<img>')), [['<img'], '>']);
91
- assert.deepStrictEqual(inspect(parser('<bdi><img></bdi>')), [['<bdi>&lt;img&gt;</bdi>'], '']);
92
- assert.deepStrictEqual(inspect(parser('<img />')), [['<img'], ' />']);
90
+ assert.deepStrictEqual(inspect(parser('<img>')), [['<span class="invalid">&lt;img&gt;</span>'], '']);
91
+ assert.deepStrictEqual(inspect(parser('<bdi><img></bdi>')), [['<bdi><span class="invalid">&lt;img&gt;</span></bdi>'], '']);
92
+ assert.deepStrictEqual(inspect(parser('<img />')), undefined);
93
93
  assert.deepStrictEqual(inspect(parser('<bdi><img /></bdi>')), [['<bdi>&lt;img /&gt;</bdi>'], '']);
94
94
  });
95
95
 
@@ -100,15 +100,15 @@ describe('Unit: parser/inline/html', () => {
100
100
  assert.deepStrictEqual(inspect(parser('<bdi >a</bdi>')), [['<bdi>a</bdi>'], '']);
101
101
  assert.deepStrictEqual(inspect(parser('<bdi __proto__>a</bdi>')), undefined);
102
102
  assert.deepStrictEqual(inspect(parser('<bdi constructor>a</bdi>')), [['<span class="invalid">&lt;bdi constructor&gt;a&lt;/bdi&gt;</span>'], '']);
103
- assert.deepStrictEqual(inspect(parser('<bdi toString>a</bdi>')), undefined);
104
- assert.deepStrictEqual(inspect(parser('<bdi X>a</bdi>')), undefined);
103
+ assert.deepStrictEqual(inspect(parser('<bdi toString>a</bdi>')), [['<span class="invalid">&lt;bdi toString&gt;a&lt;/bdi&gt;</span>'], '']);
104
+ assert.deepStrictEqual(inspect(parser('<bdi X>a</bdi>')), [['<span class="invalid">&lt;bdi X&gt;a&lt;/bdi&gt;</span>'], '']);
105
105
  assert.deepStrictEqual(inspect(parser('<bdi x>a</bdi>')), [['<span class="invalid">&lt;bdi x&gt;a&lt;/bdi&gt;</span>'], '']);
106
106
  assert.deepStrictEqual(inspect(parser('<bdo>a</bdo>')), [['<span class="invalid">&lt;bdo&gt;a&lt;/bdo&gt;</span>'], '']);
107
107
  assert.deepStrictEqual(inspect(parser('<bdo >a</bdo>')), [['<span class="invalid">&lt;bdo &gt;a&lt;/bdo&gt;</span>'], '']);
108
108
  assert.deepStrictEqual(inspect(parser('<bdo __proto__>a</bdo>')), undefined);
109
109
  assert.deepStrictEqual(inspect(parser('<bdo constructor>a</bdo>')), [['<span class="invalid">&lt;bdo constructor&gt;a&lt;/bdo&gt;</span>'], '']);
110
- assert.deepStrictEqual(inspect(parser('<bdo toString>a</bdo>')), undefined);
111
- assert.deepStrictEqual(inspect(parser('<bdo X>a</bdo>')), undefined);
110
+ assert.deepStrictEqual(inspect(parser('<bdo toString>a</bdo>')), [['<span class="invalid">&lt;bdo toString&gt;a&lt;/bdo&gt;</span>'], '']);
111
+ assert.deepStrictEqual(inspect(parser('<bdo X>a</bdo>')), [['<span class="invalid">&lt;bdo X&gt;a&lt;/bdo&gt;</span>'], '']);
112
112
  assert.deepStrictEqual(inspect(parser('<bdo x>a</bdo>')), [['<span class="invalid">&lt;bdo x&gt;a&lt;/bdo&gt;</span>'], '']);
113
113
  assert.deepStrictEqual(inspect(parser('<bdo dir>a</bdo>')), [['<span class="invalid">&lt;bdo dir&gt;a&lt;/bdo&gt;</span>'], '']);
114
114
  assert.deepStrictEqual(inspect(parser('<bdo dir=>a</bdo>')), undefined);
@@ -116,16 +116,16 @@ describe('Unit: parser/inline/html', () => {
116
116
  assert.deepStrictEqual(inspect(parser('<bdo dir=">a</bdo>')), undefined);
117
117
  assert.deepStrictEqual(inspect(parser('<bdo dir="">a</bdo>')), [['<span class="invalid">&lt;bdo dir=""&gt;a&lt;/bdo&gt;</span>'], '']);
118
118
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl" dir="rtl">a</bdo>')), [['<span class="invalid">&lt;bdo dir="rtl" dir="rtl"&gt;a&lt;/bdo&gt;</span>'], '']);
119
- assert.deepStrictEqual(inspect(parser('<bdo diR="rtl">a</bdo>')), undefined);
119
+ assert.deepStrictEqual(inspect(parser('<bdo diR="rtl">a</bdo>')), [['<span class="invalid">&lt;bdo diR="rtl"&gt;a&lt;/bdo&gt;</span>'], '']);
120
120
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl">a</bdo>')), [['<bdo dir="rtl">a</bdo>'], '']);
121
121
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl" >a</bdo>')), [['<bdo dir="rtl">a</bdo>'], '']);
122
122
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl" >a</bdo>')), [['<bdo dir="rtl">a</bdo>'], '']);
123
123
  assert.deepStrictEqual(inspect(parser('<bdo dir="rtl">a</bdo>')), [['<bdo dir="rtl">a</bdo>'], '']);
124
124
  assert.deepStrictEqual(inspect(parser('<wbr\n>')), undefined);
125
125
  assert.deepStrictEqual(inspect(parser('<wbr >')), [['<wbr>'], '']);
126
- assert.deepStrictEqual(inspect(parser('<wbr constructor>')), [['<wbr'], ' constructor>']);
127
- assert.deepStrictEqual(inspect(parser('<wbr X>')), [['<wbr'], ' X>']);
128
- assert.deepStrictEqual(inspect(parser('<wbr x>')), [['<wbr'], ' x>']);
126
+ assert.deepStrictEqual(inspect(parser('<wbr constructor>')), [['<span class="invalid">&lt;wbr constructor&gt;</span>'], '']);
127
+ assert.deepStrictEqual(inspect(parser('<wbr X>')), [['<span class="invalid">&lt;wbr X&gt;</span>'], '']);
128
+ assert.deepStrictEqual(inspect(parser('<wbr x>')), [['<span class="invalid">&lt;wbr x&gt;</span>'], '']);
129
129
  });
130
130
 
131
131
  });
@@ -19,14 +19,15 @@ const attrspecs = {
19
19
  Object.setPrototypeOf(attrspecs, null);
20
20
  Object.values(attrspecs).forEach(o => Object.setPrototypeOf(o, null));
21
21
 
22
- export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^\S\n]|>)/, syntax(Syntax.none, 5, 1, union([
22
+ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^\S\n]|>)/i, syntax(Syntax.none, 5, 1, union([
23
23
  focus(
24
- /^<wbr[^\S\n]*>/,
24
+ /^<wbr[^\S\n]*>/i,
25
25
  () => [[h('wbr')], '']),
26
- focus(
26
+ surround(
27
27
  // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
28
- /^<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[^\S\n]|>)/,
29
- source => [[source], '']),
28
+ str(/^<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[^\S\n]|>)/i), some(union([attribute])), str(/^[^\S\n]*>/), true,
29
+ ([as, bs = [], cs], rest) =>
30
+ [[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest]),
30
31
  match(
31
32
  new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`),
32
33
  memoize(
@@ -44,7 +45,7 @@ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^
44
45
  [[elem(tag, as, bs, [])], rest]),
45
46
  ([, tag]) => TAGS.indexOf(tag), [])),
46
47
  match(
47
- /^<([a-z]+)(?=[^\S\n]|>)/,
48
+ /^<([a-z]+)(?=[^\S\n]|>)/i,
48
49
  memoize(
49
50
  ([, tag]) =>
50
51
  surround<HTMLParser.TagParser, string>(surround(
@@ -63,7 +64,7 @@ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^
63
64
  ])))));
64
65
 
65
66
  export const attribute: HTMLParser.AttributeParser = union([
66
- str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|>)/),
67
+ str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="(?:\\[^\n]|[^\\\n"])*")?(?=[^\S\n]|>)/i),
67
68
  ]);
68
69
 
69
70
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
@@ -45,7 +45,7 @@ describe('Unit: parser/inline/link', () => {
45
45
  it('invalid', () => {
46
46
  assert.deepStrictEqual(inspect(parser('')), undefined);
47
47
  assert.deepStrictEqual(inspect(parser('{}')), undefined);
48
- assert.deepStrictEqual(inspect(parser('[{b}')), [['', '[', '<a href="b">b</a>'], '']);
48
+ assert.deepStrictEqual(inspect(parser('[{b}')), undefined);
49
49
  assert.deepStrictEqual(inspect(parser('[]')), undefined);
50
50
  assert.deepStrictEqual(inspect(parser('[]{}')), undefined);
51
51
  assert.deepStrictEqual(inspect(parser('[]{ }')), undefined);
@@ -65,7 +65,7 @@ describe('Unit: parser/inline/link', () => {
65
65
  assert.deepStrictEqual(inspect(parser('[\\ ]{b}')), undefined);
66
66
  assert.deepStrictEqual(inspect(parser('[\\\n]{b}')), undefined);
67
67
  assert.deepStrictEqual(inspect(parser('[&Tab;]{b}')), undefined);
68
- assert.deepStrictEqual(inspect(parser('[[]{b}')), [['', '[', '<a href="b">b</a>'], '']);
68
+ assert.deepStrictEqual(inspect(parser('[[]{b}')), undefined);
69
69
  assert.deepStrictEqual(inspect(parser('[]]{b}')), undefined);
70
70
  assert.deepStrictEqual(inspect(parser('[a]{}')), undefined);
71
71
  assert.deepStrictEqual(inspect(parser('[a\nb]{b}')), undefined);
@@ -1,6 +1,6 @@
1
1
  import { undefined, location, encodeURI, decodeURI, Location } from 'spica/global';
2
2
  import { LinkParser, TextLinkParser } from '../inline';
3
- import { Result, eval, exec } from '../../combinator/data/parser';
3
+ import { eval, exec } from '../../combinator/data/parser';
4
4
  import { union, inits, tails, subsequence, some, constraint, syntax, creation, precedence, state, validate, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
5
5
  import { inline, media, shortmedia } from '../inline';
6
6
  import { attributes } from './html';
@@ -30,9 +30,7 @@ export const link: LinkParser = lazy(() => validate(['[', '{'], bind(
30
30
  state(State.annotation | State.reference | State.index | State.label | State.media | State.autolink,
31
31
  some(inline, ']', [[/^\\?\n/, 9], [']', 2]])),
32
32
  ']',
33
- true,
34
- undefined,
35
- ([, ns = [], rest], next) => next[0] === ']' ? undefined : optimize('[', ns, rest, next)),
33
+ true),
36
34
  ]))),
37
35
  dup(surround(/^{(?![{}])/, inits([uri, some(option)]), /^[^\S\n]*}/)),
38
36
  ], nodes => nodes[0][0] !== ''),
@@ -59,7 +57,7 @@ export const link: LinkParser = lazy(() => validate(['[', '{'], bind(
59
57
  resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
60
58
  context.host?.href || location.href),
61
59
  context.host?.origin || location.origin);
62
- if (el.classList.contains('invalid')) return [[el], rest];
60
+ if (el.className === 'invalid') return [[el], rest];
63
61
  assert(el.classList.length === 0);
64
62
  return [[define(el, attributes('link', [], optspec, params))], rest];
65
63
  })));
@@ -85,7 +83,7 @@ export const textlink: TextLinkParser = lazy(() => validate(['[', '{'], bind(
85
83
  resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
86
84
  context.host?.href || location.href),
87
85
  context.host?.origin || location.origin);
88
- assert(!el.classList.contains('invalid'));
86
+ assert(el.className !== 'invalid');
89
87
  assert(el.classList.length === 0);
90
88
  return [[define(el, attributes('link', [], optspec, params))], rest];
91
89
  })));
@@ -191,16 +189,3 @@ function decode(uri: string): string {
191
189
  return uri.replace(/\s+/g, encodeURI);
192
190
  }
193
191
  }
194
-
195
- export function optimize(opener: string, ns: readonly (string | HTMLElement)[], rest: string, next: string): Result<string> {
196
- if (next[+(next[0] === '\\')] === '\n') return;
197
- let count = 0;
198
- for (let i = 0; i < ns.length - 1; i += 2) {
199
- const fst = ns[i];
200
- const snd = ns[i + 1] as string;
201
- assert(typeof snd === 'string');
202
- if (fst !== '' || snd[0] !== opener[0]) break;
203
- count += snd.length;
204
- }
205
- return [['', opener[0].repeat(opener.length + count)], rest.slice(count)];
206
- }
@@ -14,7 +14,7 @@ describe('Unit: parser/inline/reference', () => {
14
14
  assert.deepStrictEqual(inspect(parser('[[]]')), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('[[]]]')), undefined);
16
16
  assert.deepStrictEqual(inspect(parser('[[ ]]')), undefined);
17
- assert.deepStrictEqual(inspect(parser('[[ [a')), [['', '[['], ' [a']);
17
+ assert.deepStrictEqual(inspect(parser('[[ [a')), undefined);
18
18
  assert.deepStrictEqual(inspect(parser('[[\n]]')), undefined);
19
19
  assert.deepStrictEqual(inspect(parser('[[\na]]')), undefined);
20
20
  assert.deepStrictEqual(inspect(parser('[[\\\na]]')), undefined);
@@ -2,7 +2,6 @@ import { undefined } from 'spica/global';
2
2
  import { ReferenceParser } from '../inline';
3
3
  import { union, subsequence, some, context, syntax, creation, constraint, state, surround, open, lazy, bind } from '../../combinator';
4
4
  import { inline } from '../inline';
5
- import { optimize } from './link';
6
5
  import { str, stropt } from '../source';
7
6
  import { Syntax, State } from '../context';
8
7
  import { regBlankStart, startLoose, trimNode } from '../visibility';
@@ -23,8 +22,7 @@ export const reference: ReferenceParser = lazy(() => surround(
23
22
  ])), ']')))),
24
23
  ']]',
25
24
  false,
26
- ([, ns], rest) => [[html('sup', attributes(ns), [html('span', trimNode(defrag(ns)))])], rest],
27
- ([, ns, rest], next) => next[0] === ']' ? undefined : optimize('[[', ns, rest, next)));
25
+ ([, ns], rest) => [[html('sup', attributes(ns), [html('span', trimNode(defrag(ns)))])], rest]));
28
26
 
29
27
  const abbr: ReferenceParser.AbbrParser = creation(bind(surround(
30
28
  '^',
@@ -10,7 +10,7 @@ describe('Unit: parser/inline/template', () => {
10
10
  assert.deepStrictEqual(inspect(parser('')), undefined);
11
11
  assert.deepStrictEqual(inspect(parser('{')), undefined);
12
12
  assert.deepStrictEqual(inspect(parser('{}')), undefined);
13
- assert.deepStrictEqual(inspect(parser('{{')), [['', '{{'], '']);
13
+ assert.deepStrictEqual(inspect(parser('{{')), undefined);
14
14
  assert.deepStrictEqual(inspect(parser('{{\\}}')), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('{{a}b}')), undefined);
16
16
  assert.deepStrictEqual(inspect(parser('{{{a}}')), undefined);
@@ -1,7 +1,6 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { TemplateParser } from '../inline';
3
3
  import { union, some, syntax, creation, precedence, surround, lazy } from '../../combinator';
4
- import { optimize } from './link';
5
4
  import { escsource, str } from '../source';
6
5
  import { Syntax } from '../context';
7
6
  import { html } from 'typed-dom/dom';
@@ -9,8 +8,7 @@ import { unshift } from 'spica/array';
9
8
 
10
9
  export const template: TemplateParser = lazy(() => surround(
11
10
  '{{', syntax(Syntax.none, 2, 1, some(union([bracket, escsource]), '}')), '}}', true,
12
- ([, ns = []], rest) => [[html('span', { class: 'template' }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest],
13
- ([, ns = [], rest], next) => next[0] === '}' ? undefined : optimize('{{', ns, rest, next)));
11
+ ([, ns = []], rest) => [[html('span', { class: 'template' }, `{{${ns.join('').replace(/\x1B/g, '')}}}`)], rest]));
14
12
 
15
13
  const bracket: TemplateParser.BracketParser = lazy(() => creation(union([
16
14
  surround(str('('), some(union([bracket, escsource]), ')'), str(')'), true, undefined, ([as, bs = []], rest) => [unshift(as, bs), rest]),
@@ -143,23 +143,23 @@ describe('Unit: parser/inline', () => {
143
143
  assert.deepStrictEqual(inspect(parser('Di$ney Micro$oft')), [['Di', '$', 'ney', ' ', 'Micro', '$', 'oft'], '']);
144
144
  assert.deepStrictEqual(inspect(parser('Di$ney, Micro$oft')), [['Di', '$', 'ney', ',', ' ', 'Micro', '$', 'oft'], '']);
145
145
  assert.deepStrictEqual(inspect(parser('(((a))')), [['', '(', '<sup class="annotation"><span>a</span></sup>'], '']);
146
- assert.deepStrictEqual(inspect(parser('((((a))')), [['', '((', '<sup class="annotation"><span>a</span></sup>'], '']);
146
+ assert.deepStrictEqual(inspect(parser('((((a))')), [['', '(', '', '(', '<sup class="annotation"><span>a</span></sup>'], '']);
147
147
  assert.deepStrictEqual(inspect(parser('((((a))))')), [['<sup class="annotation"><span><span class="paren">((a))</span></span></sup>'], '']);
148
148
  assert.deepStrictEqual(inspect(parser('((<bdi>))')), [['<sup class="annotation"><span><span class="invalid">&lt;bdi&gt;</span></span></sup>'], '']);
149
- assert.deepStrictEqual(inspect(parser('((${))}$')), [['', '((', '<span class="math" translate="no" data-src="${))}$">${))}$</span>'], '']);
149
+ assert.deepStrictEqual(inspect(parser('((${))}$')), [['', '(', '', '(', '<span class="math" translate="no" data-src="${))}$">${))}$</span>'], '']);
150
150
  assert.deepStrictEqual(inspect(parser('"((""))')), [['"', '<sup class="annotation"><span>""</span></sup>'], '']);
151
151
  assert.deepStrictEqual(inspect(parser('[[[a]]')), [['', '[', '<sup class="reference"><span>a</span></sup>'], '']);
152
- assert.deepStrictEqual(inspect(parser('[[[[a]]')), [['', '[[', '<sup class="reference"><span>a</span></sup>'], '']);
152
+ assert.deepStrictEqual(inspect(parser('[[[[a]]')), [['', '[', '', '[', '<sup class="reference"><span>a</span></sup>'], '']);
153
153
  assert.deepStrictEqual(inspect(parser('[[[[a]]]]')), [['<sup class="reference"><span>[[a]]</span></sup>'], '']);
154
154
  assert.deepStrictEqual(inspect(parser('[[[$-1]]]')), [['<sup class="reference"><span><a class="label" data-label="$-1">$-1</a></span></sup>'], '']);
155
155
  assert.deepStrictEqual(inspect(parser('[[[]{a}]]')), [['<sup class="reference"><span><a href="a">a</a></span></sup>'], '']);
156
156
  assert.deepStrictEqual(inspect(parser('[[[a]{b}]]')), [['<sup class="reference"><span><a href="b">a</a></span></sup>'], '']);
157
157
  assert.deepStrictEqual(inspect(parser('[(([a]{#}))]{#}')), [['<a href="#"><span class="paren">(<span class="paren">([a]{#})</span>)</span></a>'], '']);
158
158
  assert.deepStrictEqual(inspect(parser('[[<bdi>]]')), [['<sup class="reference"><span><span class="invalid">&lt;bdi&gt;</span></span></sup>'], '']);
159
- assert.deepStrictEqual(inspect(parser('[[${]]}$')), [['', '[[', '<span class="math" translate="no" data-src="${]]}$">${]]}$</span>'], '']);
159
+ assert.deepStrictEqual(inspect(parser('[[${]]}$')), [['', '[', '', '[', '<span class="math" translate="no" data-src="${]]}$">${]]}$</span>'], '']);
160
160
  assert.deepStrictEqual(inspect(parser('"[[""]]')), [['"', '<sup class="reference"><span>""</span></sup>'], '']);
161
161
  assert.deepStrictEqual(inspect(parser('[[a](b)]{c}')), [['<a href="c"><ruby>a<rp>(</rp><rt>b</rt><rp>)</rp></ruby></a>'], '']);
162
- assert.deepStrictEqual(inspect(parser('[[[[[[[{a}')), [['', '[[[[[[[', '<a href="a">a</a>'], '']);
162
+ assert.deepStrictEqual(inspect(parser('[[[[[[[{a}')), [['', '[', '', '[', '', '[', '', '[', '', '[', '', '[', '', '[', '<a href="a">a</a>'], '']);
163
163
  assert.deepStrictEqual(inspect(parser('<http://host>')), [['<', '<a href="http://host" target="_blank">http://host</a>', '>'], '']);
164
164
  assert.deepStrictEqual(inspect(parser('[~http://host')), [['', '[', '~', '<a href="http://host" target="_blank">http://host</a>'], '']);
165
165
  assert.deepStrictEqual(inspect(parser('[~a@b')), [['', '[', '~', '<a class="email" href="mailto:a@b">a@b</a>'], '']);
@@ -1,7 +1,8 @@
1
1
  import { undefined } from 'spica/global';
2
2
  import { TextParser, TxtParser, LinebreakParser } from '../source';
3
- import { union, creation, focus } from '../../combinator';
3
+ import { union, syntax, focus } from '../../combinator';
4
4
  import { str } from './str';
5
+ import { Syntax } from '../context';
5
6
  import { html } from 'typed-dom/dom';
6
7
 
7
8
  export const delimiter = /[\s\x00-\x7F]|\S[#>]|[()、。!?][^\S\n]*(?=\\\n)/;
@@ -9,7 +10,7 @@ export const nonWhitespace = /[\S\n]|$/;
9
10
  export const nonAlphanumeric = /[^0-9A-Za-z]|\S[#>]|$/;
10
11
  const repeat = str(/^(.)\1*/);
11
12
 
12
- export const text: TextParser = creation((source, context) => {
13
+ export const text: TextParser = syntax(Syntax.none, 1, 1, (source, context) => {
13
14
  if (source === '') return;
14
15
  const i = source.search(delimiter);
15
16
  switch (i) {