securemark 0.296.0 → 0.296.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.296.1
4
+
5
+ - Fix parsers to disallow leading blank characters.
6
+
3
7
  ## 0.296.0
4
8
 
5
- - Change link, media, annotation, and reference parsers to disallow invisible leading content.
9
+ - Change link, media, annotation, and reference parsers to disallow leading invisible characters.
6
10
 
7
11
  ## 0.295.9
8
12
 
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! securemark v0.296.0 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
1
+ /*! securemark v0.296.1 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"));
@@ -2585,7 +2585,7 @@ function block(parser, separation = true) {
2585
2585
  if (position === source.length) return;
2586
2586
  const result = parser(input);
2587
2587
  if (result === undefined) return;
2588
- if (separation && !(0, line_1.isBlankline)(source, context.position)) return;
2588
+ if (separation && !(0, line_1.isEmptyline)(source, context.position)) return;
2589
2589
  return context.position === source.length || source[context.position - 1] === '\n' ? result : undefined;
2590
2590
  });
2591
2591
  }
@@ -2629,7 +2629,7 @@ exports.verify = verify;
2629
2629
  Object.defineProperty(exports, "__esModule", ({
2630
2630
  value: true
2631
2631
  }));
2632
- exports.isBlankline = exports.firstline = exports.line = void 0;
2632
+ exports.isEmptyline = exports.firstline = exports.line = void 0;
2633
2633
  const parser_1 = __webpack_require__(605);
2634
2634
  function line(parser) {
2635
2635
  return (0, parser_1.failsafe)(({
@@ -2648,7 +2648,7 @@ function line(parser) {
2648
2648
  context.source = source;
2649
2649
  context.offset -= position;
2650
2650
  if (result === undefined) return;
2651
- if (context.position < position + line.length && !isBlankline(source, context.position)) return;
2651
+ if (context.position < position + line.length && !isEmptyline(source, context.position)) return;
2652
2652
  context.position = position + line.length;
2653
2653
  return result;
2654
2654
  });
@@ -2659,12 +2659,12 @@ function firstline(source, position) {
2659
2659
  return i === -1 ? source.slice(position) : source.slice(position, i + 1);
2660
2660
  }
2661
2661
  exports.firstline = firstline;
2662
- const blankline = /[^\S\n]*(?:$|\n)/y;
2663
- function isBlankline(source, position) {
2664
- blankline.lastIndex = position;
2665
- return source.length === position || source[position] === '\n' || blankline.test(source);
2662
+ const emptyline = /[^\S\n]*(?:$|\n)/y;
2663
+ function isEmptyline(source, position) {
2664
+ emptyline.lastIndex = position;
2665
+ return source.length === position || source[position] === '\n' || emptyline.test(source);
2666
2666
  }
2667
- exports.isBlankline = isBlankline;
2667
+ exports.isEmptyline = isEmptyline;
2668
2668
 
2669
2669
  /***/ },
2670
2670
 
@@ -2796,20 +2796,20 @@ function fence(opener, limit, separation = true) {
2796
2796
  context.position += matches[0].length;
2797
2797
  // Prevent annoying parsing in editing.
2798
2798
  const secondline = (0, line_1.firstline)(source, context.position);
2799
- if ((0, line_1.isBlankline)(secondline, 0) && (0, line_1.firstline)(source, context.position + secondline.length).trimEnd() !== delim) return;
2799
+ if ((0, line_1.isEmptyline)(secondline, 0) && (0, line_1.firstline)(source, context.position + secondline.length).trimEnd() !== delim) return;
2800
2800
  let block = '';
2801
2801
  let closer = '';
2802
2802
  let overflow = '';
2803
2803
  for (let count = 1;; ++count) {
2804
2804
  if (context.position === source.length) break;
2805
2805
  const line = (0, line_1.firstline)(source, context.position);
2806
- if ((closer || count > limit + 1) && (0, line_1.isBlankline)(line, 0)) break;
2806
+ if ((closer || count > limit + 1) && (0, line_1.isEmptyline)(line, 0)) break;
2807
2807
  if (closer) {
2808
2808
  overflow += line;
2809
2809
  }
2810
2810
  if (!closer && count <= limit + 1 && line.slice(0, delim.length) === delim && line.trimEnd() === delim) {
2811
2811
  closer = line;
2812
- if ((0, line_1.isBlankline)(source, context.position + line.length)) {
2812
+ if ((0, line_1.isEmptyline)(source, context.position + line.length)) {
2813
2813
  context.position += line.length;
2814
2814
  break;
2815
2815
  }
@@ -4386,7 +4386,7 @@ function parse(source) {
4386
4386
  Object.defineProperty(exports, "__esModule", ({
4387
4387
  value: true
4388
4388
  }));
4389
- exports.escape = exports.invisibleHTMLEntityNames = exports.normalize = void 0;
4389
+ exports.escape = exports.invisibleGraphHTMLEntityNames = exports.invisibleBlankHTMLEntityNames = exports.normalize = void 0;
4390
4390
  const dom_1 = __webpack_require__(394);
4391
4391
  const UNICODE_REPLACEMENT_CHARACTER = '\uFFFD';
4392
4392
  function normalize(source) {
@@ -4406,13 +4406,15 @@ function sanitize(source) {
4406
4406
  }
4407
4407
  // https://dev.w3.org/html5/html-author/charref
4408
4408
  // https://en.wikipedia.org/wiki/Whitespace_character
4409
- exports.invisibleHTMLEntityNames = ['Tab', 'NewLine', 'NonBreakingSpace', 'nbsp', 'shy', 'ensp', 'emsp', 'emsp13', 'emsp14', 'numsp', 'puncsp', 'ThinSpace', 'thinsp', 'VeryThinSpace', 'hairsp', 'ZeroWidthSpace', 'NegativeVeryThinSpace', 'NegativeThinSpace', 'NegativeMediumSpace', 'NegativeThickSpace', 'zwj', 'zwnj', 'lrm', 'rlm', 'MediumSpace', 'NoBreak', 'ApplyFunction', 'af', 'InvisibleTimes', 'it', 'InvisibleComma', 'ic'];
4409
+ const invisibleHTMLEntityNames = ['Tab', 'NewLine', 'NonBreakingSpace', 'nbsp', 'shy', 'ensp', 'emsp', 'emsp13', 'emsp14', 'numsp', 'puncsp', 'ThinSpace', 'thinsp', 'VeryThinSpace', 'hairsp', 'ZeroWidthSpace', 'NegativeVeryThinSpace', 'NegativeThinSpace', 'NegativeMediumSpace', 'NegativeThickSpace', 'zwj', 'zwnj', 'lrm', 'rlm', 'MediumSpace', 'NoBreak', 'ApplyFunction', 'af', 'InvisibleTimes', 'it', 'InvisibleComma', 'ic'];
4410
4410
  const parser = (el => entity => {
4411
4411
  if (entity === '&NewLine;') return entity;
4412
4412
  el.innerHTML = entity;
4413
4413
  return el.textContent;
4414
4414
  })((0, dom_1.html)('span'));
4415
- const unreadableEscapeHTMLEntityNames = exports.invisibleHTMLEntityNames.filter(name => !['Tab', 'NewLine', 'NonBreakingSpace', 'nbsp', 'zwj', 'zwnj'].includes(name));
4415
+ exports.invisibleBlankHTMLEntityNames = invisibleHTMLEntityNames.filter(name => parser(`&${name};`).trimStart() === '');
4416
+ exports.invisibleGraphHTMLEntityNames = invisibleHTMLEntityNames.filter(name => parser(`&${name};`).trimStart() !== '');
4417
+ const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name => !['Tab', 'NewLine', 'NonBreakingSpace', 'nbsp', 'zwj', 'zwnj'].includes(name));
4416
4418
  const unreadableEscapeCharacters = unreadableEscapeHTMLEntityNames.map(name => parser(`&${name};`));
4417
4419
  const unreadableEscapeCharacter = new RegExp(`[${unreadableEscapeCharacters.join('')}]`, 'g');
4418
4420
  // https://www.pandanoir.info/entry/2018/03/11/193000
@@ -5759,7 +5761,7 @@ const delimiter = new RegExp(`${cite_1.syntax.source}|${quote_1.syntax.source}`,
5759
5761
  exports.reply = (0, combinator_1.block)((0, combinator_1.validate)(cite_1.syntax, (0, combinator_1.fmap)((0, combinator_1.some)((0, combinator_1.union)([cite_1.cite, quote_1.quote, (0, combinator_1.rewrite)((0, combinator_1.some)(source_1.anyline, delimiter), (0, visibility_1.visualize)((0, combinator_1.fmap)((0, combinator_1.some)(inline_1.inline), (ns, {
5760
5762
  source,
5761
5763
  position
5762
- }) => source[position - 1] === '\n' ? ns : ns.push(new parser_1.Node((0, dom_1.html)('br'), 1 /* Flag.invisible */)) && ns)))])), ns => new parser_1.List([new parser_1.Node((0, dom_1.html)('p', (0, dom_1.defrag)((0, util_1.unwrap)((0, visibility_1.trimBlankNodeEnd)(ns)))))]))));
5764
+ }) => source[position - 1] === '\n' ? ns : ns.push(new parser_1.Node((0, dom_1.html)('br'), 1 /* Flag.blank */)) && ns)))])), ns => new parser_1.List([new parser_1.Node((0, dom_1.html)('p', (0, dom_1.defrag)((0, util_1.unwrap)((0, visibility_1.trimBlankNodeEnd)(ns)))))]))));
5763
5765
 
5764
5766
  /***/ },
5765
5767
 
@@ -5811,7 +5813,7 @@ exports.cite = (0, combinator_1.line)((0, combinator_1.fmap)((0, combinator_1.op
5811
5813
  ...(0, util_1.invalid)('cite', 'syntax', 'Invalid syntax')
5812
5814
  }, (0, dom_1.defrag)([`${quotes}>`, typeof node === 'object' ? (0, dom_1.define)(node, {
5813
5815
  'data-depth': `${quotes.length + 1}`
5814
- }, node.innerText.slice(1)) : node.slice(1)]))), new parser_1.Node((0, dom_1.html)('br'), 1 /* Flag.invisible */)]);
5816
+ }, node.innerText.slice(1)) : node.slice(1)]))), new parser_1.Node((0, dom_1.html)('br'), 1 /* Flag.blank */)]);
5815
5817
  }));
5816
5818
 
5817
5819
  /***/ },
@@ -5839,7 +5841,7 @@ exports.quote = (0, combinator_1.lazy)(() => (0, combinator_1.block)((0, combina
5839
5841
  math_1.math, autolink_1.autolink, source_1.unescsource])))), (ns, {
5840
5842
  source,
5841
5843
  position
5842
- }) => new parser_1.List([new parser_1.Node(source[position - 1] === '\n' ? ns.pop().value : (0, dom_1.html)('br'), 1 /* Flag.invisible */), new parser_1.Node((0, dom_1.html)('span', {
5844
+ }) => new parser_1.List([new parser_1.Node(source[position - 1] === '\n' ? ns.pop().value : (0, dom_1.html)('br'), 1 /* Flag.blank */), new parser_1.Node((0, dom_1.html)('span', {
5843
5845
  class: 'quote'
5844
5846
  }, (0, dom_1.defrag)((0, util_1.unwrap)(ns))))].reverse())), false));
5845
5847
 
@@ -7189,7 +7191,7 @@ Object.setPrototypeOf(attrspecs, null);
7189
7191
  Object.values(attrspecs).forEach(o => Object.setPrototypeOf(o, null));
7190
7192
  exports.html = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(/<[a-z]+(?=[ >])/yi, (0, combinator_1.union)([(0, combinator_1.surround)(
7191
7193
  // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
7192
- (0, source_1.str)(/<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[ >])/y), (0, combinator_1.precedence)(9, (0, combinator_1.some)((0, combinator_1.union)([exports.attribute]))), (0, combinator_1.open)((0, source_1.str)(/ ?/y), (0, source_1.str)('>'), true), true, [], ([as, bs = new parser_1.List(), cs], context) => new parser_1.List([new parser_1.Node(elem(as.head.value.slice(1), false, [...(0, util_1.unwrap)(as.import(bs).import(cs))], new parser_1.List(), new parser_1.List(), context), as.head.value === '<wbr' ? 1 /* Flag.invisible */ : 0 /* Flag.none */)]), ([as, bs = new parser_1.List()], context) => new parser_1.List([new parser_1.Node(elem(as.head.value.slice(1), false, [...(0, util_1.unwrap)(as.import(bs))], new parser_1.List(), new parser_1.List(), context))])), (0, combinator_1.match)(new RegExp(String.raw`<(${TAGS.join('|')})(?=[ >])`, 'y'), (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.precedence)(9, (0, combinator_1.some)(exports.attribute)), (0, combinator_1.open)((0, source_1.str)(/ ?/y), (0, source_1.str)('>'), true), true, [], ([as, bs = new parser_1.List(), cs]) => as.import(bs).import(cs), ([as, bs = new parser_1.List()]) => as.import(bs)),
7194
+ (0, source_1.str)(/<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[ >])/y), (0, combinator_1.precedence)(9, (0, combinator_1.some)((0, combinator_1.union)([exports.attribute]))), (0, combinator_1.open)((0, source_1.str)(/ ?/y), (0, source_1.str)('>'), true), true, [], ([as, bs = new parser_1.List(), cs], context) => new parser_1.List([new parser_1.Node(elem(as.head.value.slice(1), false, [...(0, util_1.unwrap)(as.import(bs).import(cs))], new parser_1.List(), new parser_1.List(), context), as.head.value === '<wbr' ? 1 /* Flag.blank */ : 0 /* Flag.none */)]), ([as, bs = new parser_1.List()], context) => new parser_1.List([new parser_1.Node(elem(as.head.value.slice(1), false, [...(0, util_1.unwrap)(as.import(bs))], new parser_1.List(), new parser_1.List(), context))])), (0, combinator_1.match)(new RegExp(String.raw`<(${TAGS.join('|')})(?=[ >])`, 'y'), (0, memoize_1.memoize)(([, tag]) => (0, combinator_1.surround)((0, combinator_1.surround)((0, source_1.str)(`<${tag}`), (0, combinator_1.precedence)(9, (0, combinator_1.some)(exports.attribute)), (0, combinator_1.open)((0, source_1.str)(/ ?/y), (0, source_1.str)('>'), true), true, [], ([as, bs = new parser_1.List(), cs]) => as.import(bs).import(cs), ([as, bs = new parser_1.List()]) => as.import(bs)),
7193
7195
  // 不可視のHTML構造が可視構造を変化させるべきでない。
7194
7196
  // 可視のHTMLは優先度変更を検討する。
7195
7197
  // このため`<>`記号は将来的に共通構造を変化させる可能性があり
@@ -7203,7 +7205,7 @@ function elem(tag, content, as, bs, cs, context) {
7203
7205
  if (content) {
7204
7206
  if (cs.length === 0) return ielem('tag', `Missing the closing HTML tag "</${tag}>"`, context);
7205
7207
  if (bs.length === 0) return ielem('content', `Missing the content`, context);
7206
- if (!(0, visibility_1.isLooseNodeStart)(bs)) return ielem('content', `Missing the visible content in the same line`, context);
7208
+ if (!(0, visibility_1.isNonblankFirstLine)(bs)) return ielem('content', `Missing the visible content in the same line`, context);
7207
7209
  }
7208
7210
  const [attrs] = attributes('html', attrspecs[tag], as.slice(1, as.at(-1) === '>' ? -1 : as.length));
7209
7211
  if (/(?<!\S)invalid(?!\S)/.test(attrs['class'] ?? '')) return ielem('attribute', 'Invalid HTML attribute', context);
@@ -7268,7 +7270,7 @@ const combinator_1 = __webpack_require__(3484);
7268
7270
  const source_1 = __webpack_require__(8745);
7269
7271
  const util_1 = __webpack_require__(4992);
7270
7272
  const dom_1 = __webpack_require__(394);
7271
- exports.unsafehtmlentity = (0, combinator_1.surround)((0, source_1.str)('&'), (0, source_1.str)(/[0-9A-Za-z]+/y), (0, source_1.str)(';'), false, [3 | 8 /* Backtrack.unescapable */], ([as, bs, cs]) => new parser_1.List([new parser_1.Node(parser(as.head.value + bs.head.value + cs.head.value), (0, node_1.isInvisibleHTMLEntityName)(bs.head.value) ? 1 /* Flag.invisible */ : 0 /* Flag.none */)]), ([as, bs]) => new parser_1.List([new parser_1.Node(as.head.value + (bs?.head?.value ?? ''))]));
7273
+ exports.unsafehtmlentity = (0, combinator_1.surround)((0, source_1.str)('&'), (0, source_1.str)(/[0-9A-Za-z]+/y), (0, source_1.str)(';'), false, [3 | 8 /* Backtrack.unescapable */], ([as, bs, cs]) => new parser_1.List([new parser_1.Node(parser(as.head.value + bs.head.value + cs.head.value), (0, node_1.isBlankHTMLEntityName)(bs.head.value) ? 1 /* Flag.blank */ : 0 /* Flag.none */)]), ([as, bs]) => new parser_1.List([new parser_1.Node(as.head.value + (bs?.head?.value ?? ''))]));
7272
7274
  exports.htmlentity = (0, combinator_1.fmap)((0, combinator_1.union)([exports.unsafehtmlentity]), ([{
7273
7275
  value,
7274
7276
  flags
@@ -7606,7 +7608,7 @@ exports.media = (0, combinator_1.lazy)(() => (0, combinator_1.constraint)(4 /* S
7606
7608
  }, {
7607
7609
  value: params
7608
7610
  }], context) => {
7609
- if (flags & 1 /* Flag.invisible */) return;
7611
+ if (flags & 1 /* Flag.blank */) return;
7610
7612
  if (text) {
7611
7613
  const tmp = text;
7612
7614
  text = text.trim();
@@ -7825,7 +7827,7 @@ const util_1 = __webpack_require__(4992);
7825
7827
  const dom_1 = __webpack_require__(394);
7826
7828
  exports.ruby = (0, combinator_1.lazy)(() => (0, combinator_1.bind)((0, combinator_1.inits)([(0, combinator_1.dup)((0, combinator_1.surround)('[', text, ']', false, [1 | 4 /* Backtrack.common */, 3 | 32 /* Backtrack.ruby */], ([, ns]) => {
7827
7829
  ns && ns.last?.value === '' && ns.pop();
7828
- return (0, visibility_1.isTightNodeStart)(ns) ? ns : undefined;
7830
+ return (0, visibility_1.isNonblankNodeStart)(ns) ? ns : undefined;
7829
7831
  })), (0, combinator_1.dup)((0, combinator_1.surround)('(', text, ')', false, [3 | 32 /* Backtrack.ruby */]))]), ([{
7830
7832
  value: texts
7831
7833
  }, {
@@ -7984,9 +7986,9 @@ const bracket = (0, combinator_1.lazy)(() => (0, combinator_1.union)([(0, combin
7984
7986
  Object.defineProperty(exports, "__esModule", ({
7985
7987
  value: true
7986
7988
  }));
7987
- exports.isInvisibleHTMLEntityName = void 0;
7989
+ exports.isBlankHTMLEntityName = void 0;
7988
7990
  const normalize_1 = __webpack_require__(4490);
7989
- exports.isInvisibleHTMLEntityName = eval(['name => {', 'switch(name){', normalize_1.invisibleHTMLEntityNames.map(name => `case '${name}':`).join(''), 'return true;', 'default:', 'return false;', '}', '}'].join(''));
7991
+ exports.isBlankHTMLEntityName = eval(['name => {', 'switch(name){', normalize_1.invisibleBlankHTMLEntityNames.map(name => `case '${name}':`).join(''), 'return true;', 'default:', 'return false;', '}', '}'].join(''));
7990
7992
 
7991
7993
  /***/ },
7992
7994
 
@@ -8491,7 +8493,7 @@ const escsource = ({
8491
8493
  return new parser_1.List();
8492
8494
  case '\n':
8493
8495
  context.linebreak ||= source.length - position;
8494
- return new parser_1.List([new parser_1.Node((0, dom_1.html)('br'))]);
8496
+ return new parser_1.List([new parser_1.Node((0, dom_1.html)('br'), 1 /* Flag.blank */)]);
8495
8497
  default:
8496
8498
  if (context.sequential) return new parser_1.List([new parser_1.Node(char)]);
8497
8499
  let i = (0, text_1.next)(source, position, state, delimiter);
@@ -8650,7 +8652,7 @@ const text = input => {
8650
8652
  return new parser_1.List();
8651
8653
  case '\n':
8652
8654
  context.linebreak ||= source.length - position;
8653
- return new parser_1.List([new parser_1.Node((0, dom_1.html)('br'), 1 /* Flag.invisible */)]);
8655
+ return new parser_1.List([new parser_1.Node((0, dom_1.html)('br'), 1 /* Flag.blank */)]);
8654
8656
  default:
8655
8657
  if (context.sequential) return new parser_1.List([new parser_1.Node(char)]);
8656
8658
  exports.nonWhitespace.lastIndex = position + 1;
@@ -8880,7 +8882,7 @@ const unescsource = ({
8880
8882
  return new parser_1.List();
8881
8883
  case '\n':
8882
8884
  context.linebreak ||= source.length - position;
8883
- return new parser_1.List([new parser_1.Node((0, dom_1.html)('br'))]);
8885
+ return new parser_1.List([new parser_1.Node((0, dom_1.html)('br'), 1 /* Flag.blank */)]);
8884
8886
  default:
8885
8887
  if (context.sequential) return new parser_1.List([new parser_1.Node(char)]);
8886
8888
  text_1.nonWhitespace.lastIndex = position + 1;
@@ -9036,18 +9038,18 @@ exports.stringify = stringify;
9036
9038
  Object.defineProperty(exports, "__esModule", ({
9037
9039
  value: true
9038
9040
  }));
9039
- exports.trimBlankNodeEnd = exports.trimBlankEnd = exports.trimBlank = exports.isTightNodeStart = exports.isLooseNodeStart = exports.beforeNonblank = exports.blankWith = exports.afterNonblank = exports.visualize = void 0;
9041
+ exports.trimBlankNodeEnd = exports.trimBlankEnd = exports.trimBlank = exports.isNonblankNodeStart = exports.isNonblankFirstLine = exports.beforeNonblank = exports.blankWith = exports.afterNonblank = exports.visualize = void 0;
9040
9042
  const parser_1 = __webpack_require__(605);
9041
9043
  const combinator_1 = __webpack_require__(3484);
9042
9044
  const normalize_1 = __webpack_require__(4490);
9043
- var invisible;
9044
- (function (invisible) {
9045
- invisible.line = new RegExp(/((?:^|\n)[^\S\n]*(?=\S))((?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)+(?=$|\n))/g.source.replace('IHN', `(?:${normalize_1.invisibleHTMLEntityNames.join('|')})`), 'g');
9046
- invisible.start = new RegExp(/(?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)+/y.source.replace('IHN', `(?:${normalize_1.invisibleHTMLEntityNames.join('|')})`), 'y');
9047
- invisible.unit = new RegExp(/(?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)/y.source.replace('IHN', `(?:${normalize_1.invisibleHTMLEntityNames.join('|')})`), 'y');
9048
- })(invisible || (invisible = {}));
9045
+ var blank;
9046
+ (function (blank) {
9047
+ blank.line = new RegExp(/((?:^|\n)[^\S\n]*(?=\S))((?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)+(?=$|\n))/g.source.replace('IBHN', `(?:${normalize_1.invisibleBlankHTMLEntityNames.join('|')})`), 'g');
9048
+ blank.start = new RegExp(/(?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)+/y.source.replace('IBHN', `(?:${normalize_1.invisibleBlankHTMLEntityNames.join('|')})`), 'y');
9049
+ blank.unit = new RegExp(/(?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)/y.source.replace('IBHN', `(?:${normalize_1.invisibleBlankHTMLEntityNames.join('|')})`), 'y');
9050
+ })(blank || (blank = {}));
9049
9051
  function visualize(parser) {
9050
- return (0, combinator_1.convert)(source => source.replace(invisible.line, `$1${"\u001B" /* Command.Escape */}$2`), parser);
9052
+ return (0, combinator_1.convert)(source => source.replace(blank.line, `$1${"\u001B" /* Command.Escape */}$2`), parser);
9051
9053
  }
9052
9054
  exports.visualize = visualize;
9053
9055
  exports.afterNonblank = nonblankWith('');
@@ -9055,17 +9057,17 @@ function blankWith(starts, delimiter) {
9055
9057
  return new RegExp([
9056
9058
  // 空行除去
9057
9059
  // 完全な空行はエスケープ済みなので再帰的バックトラックにはならない。
9058
- String.raw`(?:${starts}(?:\\?\s|&(?:${normalize_1.invisibleHTMLEntityNames.join('|')});|<wbr ?>)*)?`, typeof delimiter === 'string' ? delimiter.replace(/[*+()\[\]]/g, '\\$&') : delimiter.source].join(''), 'y');
9060
+ String.raw`(?:${starts}(?:\\?\s|&(?:${normalize_1.invisibleBlankHTMLEntityNames.join('|')});|<wbr ?>)*)?`, typeof delimiter === 'string' ? delimiter.replace(/[*+()\[\]]/g, '\\$&') : delimiter.source].join(''), 'y');
9059
9061
  }
9060
9062
  exports.blankWith = blankWith;
9061
9063
  function nonblankWith(delimiter) {
9062
- return new RegExp([String.raw`(?<!\s|&(?:${normalize_1.invisibleHTMLEntityNames.join('|')});|<wbr ?>)`, typeof delimiter === 'string' ? delimiter.replace(/[*+()\[\]]/g, '\\$&') : delimiter.source].join(''), 'y');
9064
+ return new RegExp([String.raw`(?<!\s|&(?:${normalize_1.invisibleBlankHTMLEntityNames.join('|')});|<wbr ?>)`, typeof delimiter === 'string' ? delimiter.replace(/[*+()\[\]]/g, '\\$&') : delimiter.source].join(''), 'y');
9063
9065
  }
9064
9066
  function beforeNonblank(parser) {
9065
- return input => isTightStart(input) ? parser(input) : undefined;
9067
+ return input => isNonblankStart(input) ? parser(input) : undefined;
9066
9068
  }
9067
9069
  exports.beforeNonblank = beforeNonblank;
9068
- function isTightStart(input) {
9070
+ function isNonblankStart(input) {
9069
9071
  const {
9070
9072
  context
9071
9073
  } = input;
@@ -9081,34 +9083,30 @@ function isTightStart(input) {
9081
9083
  case '\n':
9082
9084
  return false;
9083
9085
  default:
9084
- const reg = invisible.unit;
9086
+ const reg = blank.unit;
9085
9087
  reg.lastIndex = position;
9086
9088
  return !reg.test(source);
9087
9089
  }
9088
9090
  }
9089
- function isLooseNodeStart(nodes) {
9091
+ function isNonblankFirstLine(nodes) {
9090
9092
  if (nodes.length === 0) return true;
9091
9093
  for (const node of nodes) {
9092
- if (isVisible(node)) return true;
9093
- if (typeof node.value === 'object' && node.value.tagName === 'BR') break;
9094
+ if (isNonblank(node)) return true;
9095
+ if (node.flags & 1 /* Flag.blank */ && typeof node.value === 'object' && node.value.tagName === 'BR') break;
9094
9096
  }
9095
9097
  return false;
9096
9098
  }
9097
- exports.isLooseNodeStart = isLooseNodeStart;
9098
- function isTightNodeStart(nodes) {
9099
+ exports.isNonblankFirstLine = isNonblankFirstLine;
9100
+ function isNonblankNodeStart(nodes) {
9099
9101
  if (nodes.length === 0) return true;
9100
- return isVisible(nodes.head, 0);
9101
- }
9102
- exports.isTightNodeStart = isTightNodeStart;
9103
- //export function isTightNodeEnd(nodes: readonly (HTMLElement | string)[]): boolean {
9104
- // if (nodes.length === 0) return true;
9105
- // return isVisible(nodes.at(-1)!, -1);
9106
- //}
9107
- function isVisible({
9102
+ return isNonblank(nodes.head, 0);
9103
+ }
9104
+ exports.isNonblankNodeStart = isNonblankNodeStart;
9105
+ function isNonblank({
9108
9106
  value: node,
9109
9107
  flags
9110
9108
  }, strpos) {
9111
- if (flags & 1 /* Flag.invisible */) return false;
9109
+ if (flags & 1 /* Flag.blank */) return false;
9112
9110
  if (typeof node !== 'string') return true;
9113
9111
  const str = node && strpos !== undefined ? node[strpos >= 0 ? strpos : node.length + strpos] : node;
9114
9112
  switch (str) {
@@ -9135,7 +9133,7 @@ function trimBlankStart(parser) {
9135
9133
  position
9136
9134
  } = context;
9137
9135
  if (position === source.length) return;
9138
- const reg = invisible.start;
9136
+ const reg = blank.start;
9139
9137
  reg.lastIndex = position;
9140
9138
  reg.test(source);
9141
9139
  context.position = reg.lastIndex || position;
@@ -9146,37 +9144,19 @@ function trimBlankEnd(parser) {
9146
9144
  return (0, combinator_1.fmap)(parser, trimBlankNodeEnd);
9147
9145
  }
9148
9146
  exports.trimBlankEnd = trimBlankEnd;
9149
- //export function trimBlankNode<N extends HTMLElement | string>(nodes: N[]): N[] {
9150
- // return trimBlankNodeStart(trimBlankNodeEnd(nodes));
9151
- //}
9152
- //function trimBlankNodeStart<N extends HTMLElement | string>(nodes: N[]): N[] {
9153
- // for (let node = nodes[0]; nodes.length > 0 && !isVisible(node = nodes[0], 0);) {
9154
- // if (typeof node === 'string') {
9155
- // const pos = node.trimStart().length;
9156
- // if (pos > 0) {
9157
- // nodes[0] = node.slice(-pos) as N;
9158
- // break;
9159
- // }
9160
- // }
9161
- // else if (nodes.length === 1 && node.className === 'indexer') {
9162
- // break;
9163
- // }
9164
- // nodes.shift();
9165
- // }
9166
- // return nodes;
9167
- //}
9168
9147
  function trimBlankNodeEnd(nodes) {
9169
- const skip = nodes.last && ~nodes.last.flags & 1 /* Flag.invisible */ && typeof nodes.last.value === 'object' ? nodes.last.value.className === 'indexer' : false;
9148
+ const skip = nodes.last && ~nodes.last.flags & 1 /* Flag.blank */ && typeof nodes.last.value === 'object' ? nodes.last.value.className === 'indexer' : false;
9170
9149
  for (let node = skip ? nodes.last?.prev : nodes.last; node;) {
9171
- const visible = ~node.flags & 1 /* Flag.invisible */;
9172
- if (visible && typeof node.value === 'string') {
9173
- const str = node.value.trimEnd();
9174
- if (str.length > 0) {
9175
- node.value = str;
9150
+ if (~node.flags & 1 /* Flag.blank */) {
9151
+ if (typeof node.value === 'string') {
9152
+ const str = node.value.trimEnd();
9153
+ if (str.length > 0) {
9154
+ node.value = str;
9155
+ break;
9156
+ }
9157
+ } else {
9176
9158
  break;
9177
9159
  }
9178
- } else if (visible) {
9179
- break;
9180
9160
  }
9181
9161
  const target = node;
9182
9162
  node = node.prev;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.296.0",
3
+ "version": "0.296.1",
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",
@@ -1,5 +1,5 @@
1
1
  import { Parser, failsafe } from '../../data/parser';
2
- import { isBlankline } from './line';
2
+ import { isEmptyline } from './line';
3
3
 
4
4
  export function block<P extends Parser>(parser: P, separation?: boolean): P;
5
5
  export function block<N>(parser: Parser<N>, separation = true): Parser<N> {
@@ -10,7 +10,7 @@ export function block<N>(parser: Parser<N>, separation = true): Parser<N> {
10
10
  if (position === source.length) return;
11
11
  const result = parser(input);
12
12
  if (result === undefined) return;
13
- if (separation && !isBlankline(source, context.position)) return;
13
+ if (separation && !isEmptyline(source, context.position)) return;
14
14
  assert(context.position === source.length || source[context.position - 1] === '\n');
15
15
  return context.position === source.length || source[context.position - 1] === '\n'
16
16
  ? result
@@ -14,7 +14,7 @@ export function line<N>(parser: Parser<N>): Parser<N> {
14
14
  context.source = source;
15
15
  context.offset -= position;
16
16
  if (result === undefined) return;
17
- if (context.position < position + line.length && !isBlankline(source, context.position)) return;
17
+ if (context.position < position + line.length && !isEmptyline(source, context.position)) return;
18
18
  context.position = position + line.length;
19
19
  return result;
20
20
  });
@@ -27,10 +27,10 @@ export function firstline(source: string, position: number): string {
27
27
  : source.slice(position, i + 1);
28
28
  }
29
29
 
30
- const blankline = /[^\S\n]*(?:$|\n)/y;
31
- export function isBlankline(source: string, position: number): boolean {
32
- blankline.lastIndex = position;
30
+ const emptyline = /[^\S\n]*(?:$|\n)/y;
31
+ export function isEmptyline(source: string, position: number): boolean {
32
+ emptyline.lastIndex = position;
33
33
  return source.length === position
34
34
  || source[position] === '\n'
35
- || blankline.test(source);
35
+ || emptyline.test(source);
36
36
  }
@@ -1,6 +1,6 @@
1
1
  import { Parser, List, Node, Context, failsafe } from '../../data/parser';
2
2
  import { consume } from '../../../combinator';
3
- import { firstline, isBlankline } from '../constraint/line';
3
+ import { firstline, isEmptyline } from '../constraint/line';
4
4
  import { push } from 'spica/array';
5
5
 
6
6
  export function fence<C extends Context, D extends Parser<unknown, C>[]>(opener: RegExp, limit: number, separation = true): Parser<string, C, D> {
@@ -20,20 +20,20 @@ export function fence<C extends Context, D extends Parser<unknown, C>[]>(opener:
20
20
  context.position += matches[0].length;
21
21
  // Prevent annoying parsing in editing.
22
22
  const secondline = firstline(source, context.position);
23
- if (isBlankline(secondline, 0) && firstline(source, context.position + secondline.length).trimEnd() !== delim) return;
23
+ if (isEmptyline(secondline, 0) && firstline(source, context.position + secondline.length).trimEnd() !== delim) return;
24
24
  let block = '';
25
25
  let closer = '';
26
26
  let overflow = '';
27
27
  for (let count = 1; ; ++count) {
28
28
  if (context.position === source.length) break;
29
29
  const line = firstline(source, context.position);
30
- if ((closer || count > limit + 1) && isBlankline(line, 0)) break;
30
+ if ((closer || count > limit + 1) && isEmptyline(line, 0)) break;
31
31
  if(closer) {
32
32
  overflow += line;
33
33
  }
34
34
  if (!closer && count <= limit + 1 && line.slice(0, delim.length) === delim && line.trimEnd() === delim) {
35
35
  closer = line;
36
- if (isBlankline(source, context.position + line.length)) {
36
+ if (isEmptyline(source, context.position + line.length)) {
37
37
  context.position += line.length;
38
38
  break;
39
39
  }
@@ -24,7 +24,7 @@ function sanitize(source: string): string {
24
24
 
25
25
  // https://dev.w3.org/html5/html-author/charref
26
26
  // https://en.wikipedia.org/wiki/Whitespace_character
27
- export const invisibleHTMLEntityNames = [
27
+ const invisibleHTMLEntityNames = [
28
28
  'Tab',
29
29
  'NewLine',
30
30
  'NonBreakingSpace',
@@ -63,6 +63,10 @@ const parser = (el => (entity: string): string => {
63
63
  el.innerHTML = entity;
64
64
  return el.textContent!;
65
65
  })(html('span'));
66
+ export const invisibleBlankHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames
67
+ .filter(name => parser(`&${name};`).trimStart() === '');
68
+ export const invisibleGraphHTMLEntityNames: readonly string[] = invisibleHTMLEntityNames
69
+ .filter(name => parser(`&${name};`).trimStart() !== '');
66
70
  const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name => ![
67
71
  'Tab',
68
72
  'NewLine',
@@ -34,6 +34,6 @@ export const cite: ReplyParser.CiteParser = line(fmap(
34
34
  ? define(node, { 'data-depth': `${quotes.length + 1}` }, node.innerText.slice(1))
35
35
  : node.slice(1),
36
36
  ]))),
37
- new Node(html('br'), Flag.invisible),
37
+ new Node(html('br'), Flag.blank),
38
38
  ]);
39
39
  }));
@@ -22,7 +22,7 @@ export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
22
22
  unescsource,
23
23
  ])))),
24
24
  (ns, { source, position }) => new List([
25
- new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br'), Flag.invisible),
25
+ new Node(source[position - 1] === '\n' ? ns.pop()!.value as HTMLBRElement : html('br'), Flag.blank),
26
26
  new Node(html('span', { class: 'quote' }, defrag(unwrap(ns)))),
27
27
  ].reverse())),
28
28
  false));
@@ -21,6 +21,6 @@ export const reply: ReplyParser = block(validate(csyntax, fmap(
21
21
  visualize(fmap(some(inline), (ns, { source, position }) =>
22
22
  source[position - 1] === '\n'
23
23
  ? ns
24
- : ns.push(new Node(html('br'), Flag.invisible)) && ns)))
24
+ : ns.push(new Node(html('br'), Flag.blank)) && ns)))
25
25
  ])),
26
26
  ns => new List([new Node(html('p', defrag(unwrap(trimBlankNodeEnd(ns)))))]))));
@@ -5,7 +5,7 @@ import { Flag } from '../node';
5
5
  import { union, some, recursion, precedence, validate, surround, open, match, lazy } from '../../combinator';
6
6
  import { inline } from '../inline';
7
7
  import { str } from '../source';
8
- import { isLooseNodeStart, blankWith } from '../visibility';
8
+ import { isNonblankFirstLine, blankWith } from '../visibility';
9
9
  import { invalid, unwrap } from '../util';
10
10
  import { memoize } from 'spica/memoize';
11
11
  import { html as h, defrag } from 'typed-dom/dom';
@@ -29,7 +29,7 @@ export const html: HTMLParser = lazy(() => validate(/<[a-z]+(?=[ >])/yi,
29
29
  open(str(/ ?/y), str('>'), true),
30
30
  true, [],
31
31
  ([as, bs = new List(), cs], context) =>
32
- new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context), as.head!.value === '<wbr' ? Flag.invisible : Flag.none)]),
32
+ new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs).import(cs))], new List(), new List(), context), as.head!.value === '<wbr' ? Flag.blank : Flag.none)]),
33
33
  ([as, bs = new List()], context) =>
34
34
  new List([new Node(elem(as.head!.value.slice(1), false, [...unwrap(as.import(bs))], new List(), new List(), context))])),
35
35
  match(
@@ -85,7 +85,7 @@ function elem(tag: string, content: boolean, as: readonly string[], bs: List<Nod
85
85
  if (content) {
86
86
  if (cs.length === 0) return ielem('tag', `Missing the closing HTML tag "</${tag}>"`, context);
87
87
  if (bs.length === 0) return ielem('content', `Missing the content`, context);
88
- if (!isLooseNodeStart(bs)) return ielem('content', `Missing the visible content in the same line`, context);
88
+ if (!isNonblankFirstLine(bs)) return ielem('content', `Missing the visible content in the same line`, context);
89
89
  }
90
90
  const [attrs] = attributes('html', attrspecs[tag], as.slice(1, as.at(-1) === '>' ? -1 : as.length));
91
91
  if (/(?<!\S)invalid(?!\S)/.test(attrs['class'] ?? '')) return ielem('attribute', 'Invalid HTML attribute', context)
@@ -1,7 +1,7 @@
1
1
  import { HTMLEntityParser, UnsafeHTMLEntityParser } from '../inline';
2
2
  import { Backtrack } from '../context';
3
3
  import { List, Node } from '../../combinator/data/parser';
4
- import { Flag, isInvisibleHTMLEntityName } from '../node';
4
+ import { Flag, isBlankHTMLEntityName } from '../node';
5
5
  import { union, surround, fmap } from '../../combinator';
6
6
  import { str } from '../source';
7
7
  import { invalid } from '../util';
@@ -15,7 +15,7 @@ export const unsafehtmlentity: UnsafeHTMLEntityParser = surround(
15
15
  new List([
16
16
  new Node(
17
17
  parser(as.head!.value + bs.head!.value + cs.head!.value),
18
- isInvisibleHTMLEntityName(bs.head!.value) ? Flag.invisible : Flag.none)
18
+ isBlankHTMLEntityName(bs.head!.value) ? Flag.blank : Flag.none)
19
19
  ]),
20
20
  ([as, bs]) =>
21
21
  new List([new Node(as.head!.value + (bs?.head?.value ?? ''))]));
@@ -84,7 +84,6 @@ describe('Unit: parser/inline/link', () => {
84
84
  assert.deepStrictEqual(inspect(parser, input('[\\ ]{b}', new Context())), undefined);
85
85
  assert.deepStrictEqual(inspect(parser, input('[\\\n]{b}', new Context())), undefined);
86
86
  assert.deepStrictEqual(inspect(parser, input('[&Tab;]{b}', new Context())), undefined);
87
- assert.deepStrictEqual(inspect(parser, input('[&zwj;]{b}', new Context())), undefined);
88
87
  assert.deepStrictEqual(inspect(parser, input('[[]{b}', new Context())), undefined);
89
88
  assert.deepStrictEqual(inspect(parser, input('[]]{b}', new Context())), undefined);
90
89
  assert.deepStrictEqual(inspect(parser, input('[a]{}', new Context())), undefined);
@@ -49,7 +49,6 @@ describe('Unit: parser/inline/media', () => {
49
49
  assert.deepStrictEqual(inspect(parser, input('![\\ ]{b}', new Context())), undefined);
50
50
  assert.deepStrictEqual(inspect(parser, input('![\\\n]{b}', new Context())), undefined);
51
51
  assert.deepStrictEqual(inspect(parser, input('![&Tab;]{b}', new Context())), undefined);
52
- assert.deepStrictEqual(inspect(parser, input('![&zwj;]{b}', new Context())), undefined);
53
52
  assert.deepStrictEqual(inspect(parser, input('![&a;]{b}', new Context())), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="&amp;a;"></a>'], '']);
54
53
  assert.deepStrictEqual(inspect(parser, input('![[]{b}', new Context())), undefined);
55
54
  assert.deepStrictEqual(inspect(parser, input('![]]{b}', new Context())), undefined);
@@ -53,7 +53,7 @@ export const media: MediaParser = lazy(() => constraint(State.media, open(
53
53
  ? new List<Node<List<Node<string>>>>([new Node(new List([new Node('')])), nodes.delete(nodes.head!)])
54
54
  : new List<Node<List<Node<string>>>>([new Node(new List([new Node(nodes.head!.value.foldl((acc, { value }) => acc + value, ''), nodes.head!.value.head?.flags)])), nodes.delete(nodes.last!)])),
55
55
  ([{ value: [{ value: text, flags }] }, { value: params }], context) => {
56
- if (flags & Flag.invisible) return;
56
+ if (flags & Flag.blank) return;
57
57
  if (text) {
58
58
  const tmp = text;
59
59
  text = text.trim();
@@ -46,7 +46,12 @@ describe('Unit: parser/inline/ruby', () => {
46
46
  assert.deepStrictEqual(inspect(parser, input('[AB](a b c)', new Context())), [['<ruby>AB<rp>(</rp><rt>a b c</rt><rp>)</rp></ruby>'], '']);
47
47
  assert.deepStrictEqual(inspect(parser, input('[A B](ab)', new Context())), [['<ruby>A<rp>(</rp><rt>ab</rt><rp>)</rp>B<rt></rt></ruby>'], '']);
48
48
  assert.deepStrictEqual(inspect(parser, input('[A B](a b)', new Context())), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rp>(</rp><rt>b</rt><rp>)</rp></ruby>'], '']);
49
+ assert.deepStrictEqual(inspect(parser, input('[A B](a b )', new Context())), [['<ruby>A B<rp>(</rp><rt>a b</rt><rp>)</rp></ruby>'], '']);
50
+ assert.deepStrictEqual(inspect(parser, input('[ABC](a )', new Context())), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rt></rt>C<rt></rt></ruby>'], '']);
51
+ assert.deepStrictEqual(inspect(parser, input('[ABC](a )', new Context())), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rt></rt>C<rt></rt></ruby>'], '']);
52
+ assert.deepStrictEqual(inspect(parser, input('[ABC]( b)', new Context())), [['<ruby>A<rt></rt>B<rp>(</rp><rt>b</rt><rp>)</rp>C<rt></rt></ruby>'], '']);
49
53
  assert.deepStrictEqual(inspect(parser, input('[ABC]( b )', new Context())), [['<ruby>A<rt></rt>B<rp>(</rp><rt>b</rt><rp>)</rp>C<rt></rt></ruby>'], '']);
54
+ assert.deepStrictEqual(inspect(parser, input('[ABC]( c)', new Context())), [['<ruby>A<rt></rt>B<rt></rt>C<rp>(</rp><rt>c</rt><rp>)</rp></ruby>'], '']);
50
55
  assert.deepStrictEqual(inspect(parser, input('[ABC](a c)', new Context())), [['<ruby>A<rp>(</rp><rt>a</rt><rp>)</rp>B<rt></rt>C<rp>(</rp><rt>c</rt><rp>)</rp></ruby>'], '']);
51
56
  assert.deepStrictEqual(inspect(parser, input('[東方](とう ほう)', new Context())), [['<ruby>東<rp>(</rp><rt>とう</rt><rp>)</rp>方<rp>(</rp><rt>ほう</rt><rp>)</rp></ruby>'], '']);
52
57
  assert.deepStrictEqual(inspect(parser, input('[秦 \\  こころ](はた の こころ)', new Context())), [['<ruby>秦<rp>(</rp><rt>はた</rt><rp>)</rp> <rp>(</rp><rt>の</rt><rp>)</rp>こころ<rp>(</rp><rt>こころ</rt><rp>)</rp></ruby>'], '']);
@@ -4,7 +4,7 @@ import { List, Node } from '../../combinator/data/parser';
4
4
  import { inits, surround, setBacktrack, dup, lazy, bind } from '../../combinator';
5
5
  import { unsafehtmlentity } from './htmlentity';
6
6
  import { txt } from '../source';
7
- import { isTightNodeStart } from '../visibility';
7
+ import { isNonblankNodeStart } from '../visibility';
8
8
  import { unwrap } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
@@ -16,7 +16,7 @@ export const ruby: RubyParser = lazy(() => bind(
16
16
  [1 | Backtrack.common, 3 | Backtrack.ruby],
17
17
  ([, ns]) => {
18
18
  ns && ns.last?.value === '' && ns.pop();
19
- return isTightNodeStart(ns) ? ns : undefined;
19
+ return isNonblankNodeStart(ns) ? ns : undefined;
20
20
  })),
21
21
  dup(surround(
22
22
  '(', text, ')',
@@ -1,14 +1,14 @@
1
- import { invisibleHTMLEntityNames } from './api/normalize';
1
+ import { invisibleBlankHTMLEntityNames } from './api/normalize';
2
2
 
3
3
  export const enum Flag {
4
4
  none,
5
- invisible,
5
+ blank,
6
6
  }
7
7
 
8
- export const isInvisibleHTMLEntityName: (name: string) => boolean = eval([
8
+ export const isBlankHTMLEntityName: (name: string) => boolean = eval([
9
9
  'name => {',
10
10
  'switch(name){',
11
- invisibleHTMLEntityNames.map(name => `case '${name}':`).join(''),
11
+ invisibleBlankHTMLEntityNames.map(name => `case '${name}':`).join(''),
12
12
  'return true;',
13
13
  'default:',
14
14
  'return false;',
@@ -1,5 +1,6 @@
1
1
  import { EscapableSourceParser } from '../source';
2
2
  import { Command } from '../context';
3
+ import { Flag } from '../node';
3
4
  import { List, Node } from '../../combinator/data/parser';
4
5
  import { consume } from '../../combinator';
5
6
  import { next } from './text';
@@ -34,7 +35,7 @@ export const escsource: EscapableSourceParser = ({ context }) => {
34
35
  return new List();
35
36
  case '\n':
36
37
  context.linebreak ||= source.length - position;
37
- return new List([new Node(html('br'))]);
38
+ return new List([new Node(html('br'), Flag.blank)]);
38
39
  default:
39
40
  assert(char !== '\n');
40
41
  if (context.sequential) return new List([new Node(char)]);
@@ -33,7 +33,7 @@ export const text: TextParser = input => {
33
33
  return new List();
34
34
  case '\n':
35
35
  context.linebreak ||= source.length - position;
36
- return new List([new Node(html('br'), Flag.invisible)]);
36
+ return new List([new Node(html('br'), Flag.blank)]);
37
37
  default:
38
38
  assert(char !== '\n');
39
39
  if (context.sequential) return new List([new Node(char)]);
@@ -1,5 +1,6 @@
1
1
  import { UnescapableSourceParser } from '../source';
2
2
  import { Command } from '../context';
3
+ import { Flag } from '../node';
3
4
  import { List, Node } from '../../combinator/data/parser';
4
5
  import { consume } from '../../combinator';
5
6
  import { nonWhitespace, canSkip, next } from './text';
@@ -22,7 +23,7 @@ export const unescsource: UnescapableSourceParser = ({ context }) => {
22
23
  return new List();
23
24
  case '\n':
24
25
  context.linebreak ||= source.length - position;
25
- return new List([new Node(html('br'))]);
26
+ return new List([new Node(html('br'), Flag.blank)]);
26
27
  default:
27
28
  assert(char !== '\n');
28
29
  if (context.sequential) return new List([new Node(char)]);
@@ -2,26 +2,26 @@ import { Parser, Input, List, Node, failsafe } from '../combinator/data/parser';
2
2
  import { Context, Command } from './context';
3
3
  import { Flag } from './node';
4
4
  import { convert, fmap } from '../combinator';
5
- import { invisibleHTMLEntityNames } from './api/normalize';
5
+ import { invisibleBlankHTMLEntityNames } from './api/normalize';
6
6
 
7
- namespace invisible {
7
+ namespace blank {
8
8
  export const line = new RegExp(
9
- /((?:^|\n)[^\S\n]*(?=\S))((?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)+(?=$|\n))/g.source
10
- .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
9
+ /((?:^|\n)[^\S\n]*(?=\S))((?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)+(?=$|\n))/g.source
10
+ .replace('IBHN', `(?:${invisibleBlankHTMLEntityNames.join('|')})`),
11
11
  'g');
12
12
  export const start = new RegExp(
13
- /(?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)+/y.source
14
- .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
13
+ /(?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)+/y.source
14
+ .replace('IBHN', `(?:${invisibleBlankHTMLEntityNames.join('|')})`),
15
15
  'y');
16
16
  export const unit = new RegExp(
17
- /(?:[^\S\n]|\\(?=$|\s)|&IHN;|<wbr ?>)/y.source
18
- .replace('IHN', `(?:${invisibleHTMLEntityNames.join('|')})`),
17
+ /(?:[^\S\n]|\\(?=$|\s)|&IBHN;|<wbr ?>)/y.source
18
+ .replace('IBHN', `(?:${invisibleBlankHTMLEntityNames.join('|')})`),
19
19
  'y');
20
20
  }
21
21
 
22
22
  export function visualize<P extends Parser>(parser: P): P {
23
23
  return convert(
24
- source => source.replace(invisible.line, `$1${Command.Escape}$2`),
24
+ source => source.replace(blank.line, `$1${Command.Escape}$2`),
25
25
  parser);
26
26
  }
27
27
 
@@ -30,7 +30,7 @@ export function blankWith(starts: '\n', delimiter: string | RegExp): RegExp {
30
30
  return new RegExp([
31
31
  // 空行除去
32
32
  // 完全な空行はエスケープ済みなので再帰的バックトラックにはならない。
33
- String.raw`(?:${starts}(?:\\?\s|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr ?>)*)?`,
33
+ String.raw`(?:${starts}(?:\\?\s|&(?:${invisibleBlankHTMLEntityNames.join('|')});|<wbr ?>)*)?`,
34
34
  typeof delimiter === 'string'
35
35
  ? delimiter.replace(/[*+()\[\]]/g, '\\$&')
36
36
  : delimiter.source,
@@ -38,32 +38,21 @@ export function blankWith(starts: '\n', delimiter: string | RegExp): RegExp {
38
38
  }
39
39
  function nonblankWith(delimiter: string | RegExp): RegExp {
40
40
  return new RegExp([
41
- String.raw`(?<!\s|&(?:${invisibleHTMLEntityNames.join('|')});|<wbr ?>)`,
41
+ String.raw`(?<!\s|&(?:${invisibleBlankHTMLEntityNames.join('|')});|<wbr ?>)`,
42
42
  typeof delimiter === 'string'
43
43
  ? delimiter.replace(/[*+()\[\]]/g, '\\$&')
44
44
  : delimiter.source,
45
45
  ].join(''), 'y');
46
46
  }
47
47
 
48
- //export function looseStart<P extends Parser<HTMLElement | string>>(parser: P): P;
49
- //export function looseStart<N extends HTMLElement | string>(parser: Parser<N>): Parser<N> {
50
- // return input =>
51
- // isLooseStart(input)
52
- // ? parser(input)
53
- // : undefined;
54
- //}
55
- //const isLooseStart = reduce(({ source, context }: Input<Context>): boolean => {
56
- // return isTightStart({ source: source.replace(invisible.start, ''), context });
57
- //}, ({ source }) => `${source}${Command.Separator}`);
58
-
59
48
  export function beforeNonblank<P extends Parser>(parser: P): P;
60
49
  export function beforeNonblank<N>(parser: Parser<N>): Parser<N, Context> {
61
50
  return input =>
62
- isTightStart(input)
51
+ isNonblankStart(input)
63
52
  ? parser(input)
64
53
  : undefined;
65
54
  }
66
- function isTightStart(input: Input<Context>): boolean {
55
+ function isNonblankStart(input: Input<Context>): boolean {
67
56
  const { context } = input;
68
57
  const { source, position } = context;
69
58
  if (position === source.length) return true;
@@ -74,30 +63,26 @@ function isTightStart(input: Input<Context>): boolean {
74
63
  case '\n':
75
64
  return false;
76
65
  default:
77
- const reg = invisible.unit;
66
+ const reg = blank.unit;
78
67
  reg.lastIndex = position;
79
68
  return !reg.test(source);
80
69
  }
81
70
  }
82
71
 
83
- export function isLooseNodeStart(nodes: List<Node<HTMLElement | string>>): boolean {
72
+ export function isNonblankFirstLine(nodes: List<Node<HTMLElement | string>>): boolean {
84
73
  if (nodes.length === 0) return true;
85
74
  for (const node of nodes) {
86
- if (isVisible(node)) return true;
87
- if (typeof node.value === 'object' && node.value.tagName === 'BR') break;
75
+ if (isNonblank(node)) return true;
76
+ if (node.flags & Flag.blank && typeof node.value === 'object' && node.value.tagName === 'BR') break;
88
77
  }
89
78
  return false;
90
79
  }
91
- export function isTightNodeStart(nodes: List<Node<HTMLElement | string>>): boolean {
80
+ export function isNonblankNodeStart(nodes: List<Node<HTMLElement | string>>): boolean {
92
81
  if (nodes.length === 0) return true;
93
- return isVisible(nodes.head!, 0);
82
+ return isNonblank(nodes.head!, 0);
94
83
  }
95
- //export function isTightNodeEnd(nodes: readonly (HTMLElement | string)[]): boolean {
96
- // if (nodes.length === 0) return true;
97
- // return isVisible(nodes.at(-1)!, -1);
98
- //}
99
- function isVisible({ value: node, flags }: Node<HTMLElement | string>, strpos?: number): boolean {
100
- if (flags & Flag.invisible) return false;
84
+ function isNonblank({ value: node, flags }: Node<HTMLElement | string>, strpos?: number): boolean {
85
+ if (flags & Flag.blank) return false;
101
86
  if (typeof node !== 'string') return true;
102
87
  const str = node && strpos !== undefined
103
88
  ? node[strpos >= 0 ? strpos : node.length + strpos]
@@ -124,7 +109,7 @@ function trimBlankStart<N>(parser: Parser<N>): Parser<N> {
124
109
  const { context } = input;
125
110
  const { source, position } = context;
126
111
  if (position === source.length) return;
127
- const reg = invisible.start;
112
+ const reg = blank.start;
128
113
  reg.lastIndex = position;
129
114
  reg.test(source);
130
115
  context.position = reg.lastIndex || position;
@@ -137,41 +122,23 @@ export function trimBlankEnd<P extends Parser<HTMLElement | string>>(parser: P):
137
122
  export function trimBlankEnd<N extends HTMLElement>(parser: Parser<N>): Parser<string | N> {
138
123
  return fmap(parser, trimBlankNodeEnd);
139
124
  }
140
- //export function trimBlankNode<N extends HTMLElement | string>(nodes: N[]): N[] {
141
- // return trimBlankNodeStart(trimBlankNodeEnd(nodes));
142
- //}
143
- //function trimBlankNodeStart<N extends HTMLElement | string>(nodes: N[]): N[] {
144
- // for (let node = nodes[0]; nodes.length > 0 && !isVisible(node = nodes[0], 0);) {
145
- // if (typeof node === 'string') {
146
- // const pos = node.trimStart().length;
147
- // if (pos > 0) {
148
- // nodes[0] = node.slice(-pos) as N;
149
- // break;
150
- // }
151
- // }
152
- // else if (nodes.length === 1 && node.className === 'indexer') {
153
- // break;
154
- // }
155
- // nodes.shift();
156
- // }
157
- // return nodes;
158
- //}
159
125
  export function trimBlankNodeEnd<N extends HTMLElement>(nodes: List<Node<string | N>>): List<Node<string | N>> {
160
- const skip = nodes.last && ~nodes.last.flags & Flag.invisible && typeof nodes.last.value === 'object'
126
+ const skip = nodes.last && ~nodes.last.flags & Flag.blank && typeof nodes.last.value === 'object'
161
127
  ? nodes.last.value.className === 'indexer'
162
128
  : false;
163
129
  for (let node = skip ? nodes.last?.prev : nodes.last; node;) {
164
- const visible = ~node.flags & Flag.invisible;
165
- if (visible && typeof node.value === 'string') {
166
- const str = node.value.trimEnd();
167
- if (str.length > 0) {
168
- node.value = str;
130
+ if (~node.flags & Flag.blank) {
131
+ if (typeof node.value === 'string') {
132
+ const str = node.value.trimEnd();
133
+ if (str.length > 0) {
134
+ node.value = str;
135
+ break;
136
+ }
137
+ }
138
+ else {
169
139
  break;
170
140
  }
171
141
  }
172
- else if (visible) {
173
- break;
174
- }
175
142
  const target = node;
176
143
  node = node.prev;
177
144
  nodes.delete(target);