securemark 0.294.5 → 0.294.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 +8 -0
- package/dist/index.js +79 -57
- package/markdown.d.ts +3 -1
- package/package.json +1 -1
- package/src/combinator/control/manipulation/indent.ts +3 -5
- package/src/combinator/control/manipulation/surround.ts +43 -14
- package/src/combinator/data/parser.ts +3 -0
- package/src/parser/api/normalize.test.ts +9 -1
- package/src/parser/api/normalize.ts +23 -18
- package/src/parser/api/parse.test.ts +3 -3
- package/src/parser/inline/autolink/account.ts +2 -3
- package/src/parser/inline/autolink/anchor.ts +1 -2
- package/src/parser/inline/autolink/channel.ts +5 -5
- package/src/parser/inline/autolink/hashnum.ts +4 -5
- package/src/parser/inline/autolink/hashtag.test.ts +6 -2
- package/src/parser/inline/autolink/hashtag.ts +6 -6
- package/src/parser/inline/autolink.ts +36 -24
- package/src/parser/inline/html.ts +1 -1
- package/src/parser/inline/htmlentity.ts +14 -17
- package/src/parser/inline/ruby.ts +7 -2
- package/src/parser/inline.ts +2 -1
- package/src/parser/source/text.ts +6 -4
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! securemark v0.294.
|
|
1
|
+
/*! securemark v0.294.7 https://github.com/falsandtru/securemark | (c) 2017, falsandtru | UNLICENSED License */
|
|
2
2
|
(function webpackUniversalModuleDefinition(root, factory) {
|
|
3
3
|
if(typeof exports === 'object' && typeof module === 'object')
|
|
4
4
|
module.exports = factory(require("Prism"), require("DOMPurify"));
|
|
@@ -2894,9 +2894,7 @@ function indent(opener, parser = false, separation = false) {
|
|
|
2894
2894
|
} = context;
|
|
2895
2895
|
context.position = source.length;
|
|
2896
2896
|
return new parser_1.List([new parser_1.Data(source.slice(position))]);
|
|
2897
|
-
}))), ([indent]) => indent.length * 2 + -(indent[0] === ' '), [], 2 ** 4 - 1)), separation), (lines, context) =>
|
|
2898
|
-
return parser((0, parser_1.subinput)(trimBlockEnd(lines.foldl((acc, node) => acc + node.value, '')), context));
|
|
2899
|
-
}));
|
|
2897
|
+
}))), ([indent]) => indent.length * 2 + -(indent[0] === ' '), [], 2 ** 4 - 1)), separation), (lines, context) => parser((0, parser_1.subinput)(trimBlockEnd(lines.foldl((acc, node) => acc + node.value, '')), context))));
|
|
2900
2898
|
}
|
|
2901
2899
|
exports.indent = indent;
|
|
2902
2900
|
function trimBlockEnd(block) {
|
|
@@ -3094,6 +3092,11 @@ function surround(opener, parser, closer, optional = false, f, g, backtracks = [
|
|
|
3094
3092
|
case 'object':
|
|
3095
3093
|
opener = (0, combinator_1.clear)((0, combinator_1.matcher)(opener, true));
|
|
3096
3094
|
}
|
|
3095
|
+
switch (typeof parser) {
|
|
3096
|
+
case 'string':
|
|
3097
|
+
case 'object':
|
|
3098
|
+
parser = (0, combinator_1.clear)((0, combinator_1.matcher)(parser, true));
|
|
3099
|
+
}
|
|
3097
3100
|
switch (typeof closer) {
|
|
3098
3101
|
case 'string':
|
|
3099
3102
|
case 'object':
|
|
@@ -3124,16 +3127,14 @@ function surround(opener, parser, closer, optional = false, f, g, backtracks = [
|
|
|
3124
3127
|
if (!nodesM && !optional) {
|
|
3125
3128
|
setBacktrack(context, backtracks, position);
|
|
3126
3129
|
const result = g?.([nodesO, nodesM], context);
|
|
3127
|
-
revert(context, linebreak);
|
|
3128
|
-
return result;
|
|
3130
|
+
return result || void revert(context, linebreak);
|
|
3129
3131
|
}
|
|
3130
3132
|
const nodesC = nodesM || optional ? closer(input) : undefined;
|
|
3131
3133
|
context.range = context.position - position;
|
|
3132
3134
|
if (!nodesC) {
|
|
3133
3135
|
setBacktrack(context, backtracks, position);
|
|
3134
3136
|
const result = g?.([nodesO, nodesM], context);
|
|
3135
|
-
revert(context, linebreak);
|
|
3136
|
-
return result;
|
|
3137
|
+
return result || void revert(context, linebreak);
|
|
3137
3138
|
}
|
|
3138
3139
|
if (context.position === position) {
|
|
3139
3140
|
return void revert(context, linebreak);
|
|
@@ -3142,10 +3143,8 @@ function surround(opener, parser, closer, optional = false, f, g, backtracks = [
|
|
|
3142
3143
|
const result = f ? f([nodesO, nodesM, nodesC], context) : nodesO.import(nodesM ?? new parser_1.List()).import(nodesC);
|
|
3143
3144
|
if (result) {
|
|
3144
3145
|
context.linebreak ||= linebreak;
|
|
3145
|
-
} else {
|
|
3146
|
-
revert(context, linebreak);
|
|
3147
3146
|
}
|
|
3148
|
-
return result;
|
|
3147
|
+
return result || void revert(context, linebreak);
|
|
3149
3148
|
});
|
|
3150
3149
|
}
|
|
3151
3150
|
exports.surround = surround;
|
|
@@ -3183,7 +3182,7 @@ function isBacktrack(context, backtracks, position = context.position, length =
|
|
|
3183
3182
|
}
|
|
3184
3183
|
exports.isBacktrack = isBacktrack;
|
|
3185
3184
|
function setBacktrack(context, backtracks, position, length = 1) {
|
|
3186
|
-
//
|
|
3185
|
+
// バックトラックの可能性がなく記録不要の場合もあるが判別が面倒なので省略
|
|
3187
3186
|
const {
|
|
3188
3187
|
source
|
|
3189
3188
|
} = context;
|
|
@@ -4396,15 +4395,16 @@ exports.normalize = normalize;
|
|
|
4396
4395
|
function format(source) {
|
|
4397
4396
|
return source.replace(/\r\n?/g, '\n');
|
|
4398
4397
|
}
|
|
4398
|
+
const invalid = new RegExp([/(?![\t\r\n])[\x00-\x1F\x7F]/g.source, /(?!\u200D)[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]|(?<![\u1820\u1821])\u180E/g.source, /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g.source].join('|'), 'g');
|
|
4399
4399
|
function sanitize(source) {
|
|
4400
|
-
return source.replace(
|
|
4400
|
+
return source.replace(invalid, UNICODE_REPLACEMENT_CHARACTER);
|
|
4401
4401
|
}
|
|
4402
4402
|
// https://dev.w3.org/html5/html-author/charref
|
|
4403
4403
|
// https://en.wikipedia.org/wiki/Whitespace_character
|
|
4404
4404
|
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'];
|
|
4405
|
-
const
|
|
4406
|
-
const
|
|
4407
|
-
const
|
|
4405
|
+
const unreadableEscapeHTMLEntityNames = exports.invisibleHTMLEntityNames.filter(name => !['Tab', 'NewLine', 'NonBreakingSpace', 'nbsp', 'zwj', 'zwnj'].includes(name));
|
|
4406
|
+
const unreadableEscapeCharacters = unreadableEscapeHTMLEntityNames.map(name => (0, htmlentity_1.unsafehtmlentity)((0, parser_1.input)(`&${name};`, {})).head.value);
|
|
4407
|
+
const unreadableEscapeCharacter = new RegExp(`[${unreadableEscapeCharacters.join('')}]`, 'g');
|
|
4408
4408
|
// https://www.pandanoir.info/entry/2018/03/11/193000
|
|
4409
4409
|
// http://anti.rosx.net/etc/memo/002_space.html
|
|
4410
4410
|
// http://nicowiki.com/%E7%A9%BA%E7%99%BD%E3%83%BB%E7%89%B9%E6%AE%8A%E8%A8%98%E5%8F%B7.html
|
|
@@ -4416,7 +4416,7 @@ const unreadableSpecialCharacters = (/* unused pure expression or super */ null
|
|
|
4416
4416
|
// ZERO WIDTH NON-JOINER
|
|
4417
4417
|
'\u200C',
|
|
4418
4418
|
// ZERO WIDTH JOINER
|
|
4419
|
-
'\u200D',
|
|
4419
|
+
//'\u200D',
|
|
4420
4420
|
// LEFT-TO-RIGHT MARK
|
|
4421
4421
|
'\u200E',
|
|
4422
4422
|
// RIGHT-TO-LEFT MARK
|
|
@@ -4439,7 +4439,7 @@ const unreadableSpecialCharacters = (/* unused pure expression or super */ null
|
|
|
4439
4439
|
'\uFEFF']));
|
|
4440
4440
|
// 特殊不可視文字はエディタおよびソースビューアでは等幅および強調表示により可視化する
|
|
4441
4441
|
function escape(source) {
|
|
4442
|
-
return source.replace(
|
|
4442
|
+
return source.replace(unreadableEscapeCharacter, char => `&${unreadableEscapeHTMLEntityNames[unreadableEscapeCharacters.indexOf(char)]};`);
|
|
4443
4443
|
}
|
|
4444
4444
|
exports.escape = escape;
|
|
4445
4445
|
|
|
@@ -6041,6 +6041,7 @@ const reference_1 = __webpack_require__(9047);
|
|
|
6041
6041
|
const template_1 = __webpack_require__(4510);
|
|
6042
6042
|
const remark_1 = __webpack_require__(8948);
|
|
6043
6043
|
const extension_1 = __webpack_require__(2743);
|
|
6044
|
+
const label_1 = __webpack_require__(2178);
|
|
6044
6045
|
const link_1 = __webpack_require__(3628);
|
|
6045
6046
|
const ruby_1 = __webpack_require__(7304);
|
|
6046
6047
|
const html_1 = __webpack_require__(5013);
|
|
@@ -6096,7 +6097,7 @@ exports.inline = (0, combinator_1.lazy)(() => (0, combinator_1.union)([input =>
|
|
|
6096
6097
|
return (0, html_1.html)(input);
|
|
6097
6098
|
case '$':
|
|
6098
6099
|
if (source[position + 1] === '{') return (0, math_1.math)(input);
|
|
6099
|
-
return (0,
|
|
6100
|
+
return (0, label_1.label)(input) || (0, math_1.math)(input);
|
|
6100
6101
|
case '+':
|
|
6101
6102
|
if (source[position + 1] === '+') return (0, insertion_1.insertion)(input);
|
|
6102
6103
|
break;
|
|
@@ -6208,16 +6209,45 @@ const account_1 = __webpack_require__(4107);
|
|
|
6208
6209
|
const hashtag_1 = __webpack_require__(5764);
|
|
6209
6210
|
const hashnum_1 = __webpack_require__(8684);
|
|
6210
6211
|
const anchor_1 = __webpack_require__(8535);
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6212
|
+
const text_1 = __webpack_require__(5655);
|
|
6213
|
+
exports.autolink = (0, combinator_1.lazy)(() => (0, combinator_1.state)(~1 /* State.autolink */, input => {
|
|
6214
|
+
const {
|
|
6215
|
+
context: {
|
|
6216
|
+
source,
|
|
6217
|
+
position
|
|
6218
|
+
}
|
|
6219
|
+
} = input;
|
|
6220
|
+
if (position === source.length) return;
|
|
6221
|
+
const fst = source[position];
|
|
6222
|
+
switch (fst) {
|
|
6223
|
+
case '@':
|
|
6224
|
+
return (0, channel_1.channel)(input) || (0, account_1.account)(input);
|
|
6225
|
+
case '#':
|
|
6226
|
+
return (0, hashtag_1.hashtag)(input) || (0, hashnum_1.hashnum)(input);
|
|
6227
|
+
case '>':
|
|
6228
|
+
return (0, anchor_1.anchor)(input);
|
|
6229
|
+
case '!':
|
|
6230
|
+
if (!source.startsWith('http', position + 1)) break;
|
|
6231
|
+
if (position === 0) return (0, url_1.lineurl)(input);
|
|
6232
|
+
switch (source[position - 1]) {
|
|
6233
|
+
case '\r':
|
|
6234
|
+
case '\n':
|
|
6235
|
+
return (0, url_1.lineurl)(input);
|
|
6236
|
+
}
|
|
6237
|
+
break;
|
|
6238
|
+
case 'h':
|
|
6239
|
+
if (!source.startsWith('http', position)) return;
|
|
6240
|
+
if (position === 0) return (0, url_1.lineurl)(input) || (0, url_1.url)(input) || (0, email_1.email)(input);
|
|
6241
|
+
switch (source[position - 1]) {
|
|
6242
|
+
case '\r':
|
|
6243
|
+
case '\n':
|
|
6244
|
+
return (0, url_1.lineurl)(input) || (0, url_1.url)(input) || (0, email_1.email)(input);
|
|
6245
|
+
}
|
|
6246
|
+
return (0, url_1.url)(input) || (0, email_1.email)(input);
|
|
6247
|
+
default:
|
|
6248
|
+
if ((0, text_1.isAlphanumeric)(fst)) return (0, email_1.email)(input);
|
|
6249
|
+
}
|
|
6250
|
+
}));
|
|
6221
6251
|
|
|
6222
6252
|
/***/ },
|
|
6223
6253
|
|
|
@@ -6234,10 +6264,9 @@ exports.account = void 0;
|
|
|
6234
6264
|
const parser_1 = __webpack_require__(605);
|
|
6235
6265
|
const combinator_1 = __webpack_require__(3484);
|
|
6236
6266
|
const link_1 = __webpack_require__(3628);
|
|
6237
|
-
const source_1 = __webpack_require__(8745);
|
|
6238
6267
|
const dom_1 = __webpack_require__(394);
|
|
6239
6268
|
// https://example/@user must be a user page or a redirect page going there.
|
|
6240
|
-
exports.account = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.surround)(/(?<![0-9a-z])@/yi,
|
|
6269
|
+
exports.account = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.surround)(/(?<![0-9a-z])@/yi, /[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*\//yi, /[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*(?![-.]?[0-9a-z@#]|>>|:\S)/yi, true, undefined, undefined, [3 | 0 /* Backtrack.autolink */]), (0, combinator_1.constraint)(1 /* State.autolink */, (0, combinator_1.state)(1 /* State.autolink */, (0, combinator_1.fmap)((0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1).replace('/', '/@')}` : `/${source}`} }`, (0, combinator_1.union)([link_1.unsafelink]), false), ([{
|
|
6241
6270
|
value
|
|
6242
6271
|
}]) => new parser_1.List([new parser_1.Data((0, dom_1.define)(value, {
|
|
6243
6272
|
class: 'account'
|
|
@@ -6258,7 +6287,6 @@ exports.anchor = void 0;
|
|
|
6258
6287
|
const parser_1 = __webpack_require__(605);
|
|
6259
6288
|
const combinator_1 = __webpack_require__(3484);
|
|
6260
6289
|
const link_1 = __webpack_require__(3628);
|
|
6261
|
-
const source_1 = __webpack_require__(8745);
|
|
6262
6290
|
const dom_1 = __webpack_require__(394);
|
|
6263
6291
|
// Timeline(pseudonym): user/tid
|
|
6264
6292
|
// Thread(anonymous): cid
|
|
@@ -6267,7 +6295,7 @@ const dom_1 = __webpack_require__(394);
|
|
|
6267
6295
|
// cid: YYYY-MMDD-HHMM-SSmmm
|
|
6268
6296
|
// 内部表現はUnixTimeに統一する(時系列順)
|
|
6269
6297
|
// 外部表現は投稿ごとに投稿者の投稿時のタイムゾーンに統一する(非時系列順)
|
|
6270
|
-
exports.anchor = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.open)(/(?<![0-9a-z])>>/yi,
|
|
6298
|
+
exports.anchor = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.open)(/(?<![0-9a-z])>>/yi, /(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*\/)?[0-9a-z]+(?:-[0-9a-z]+)*(?!-?[0-9a-z@#]|>>|:\S)/yi, false, [3 | 0 /* Backtrack.autolink */]), (0, combinator_1.constraint)(1 /* State.autolink */, (0, combinator_1.state)(1 /* State.autolink */, (0, combinator_1.fmap)((0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `/@${source.slice(2).replace('/', '/timeline?at=')}` : `?at=${source.slice(2)}`} }`, (0, combinator_1.union)([link_1.unsafelink]), false), ([{
|
|
6271
6299
|
value
|
|
6272
6300
|
}]) => new parser_1.List([new parser_1.Data((0, dom_1.define)(value, {
|
|
6273
6301
|
class: 'anchor'
|
|
@@ -6292,7 +6320,7 @@ const hashtag_1 = __webpack_require__(5764);
|
|
|
6292
6320
|
const source_1 = __webpack_require__(8745);
|
|
6293
6321
|
const dom_1 = __webpack_require__(394);
|
|
6294
6322
|
// https://example/@user?ch=a+b must be a user channel page or a redirect page going there.
|
|
6295
|
-
exports.channel = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.sequence)([(0, combinator_1.surround)(/(?<![0-9a-z])@/yi,
|
|
6323
|
+
exports.channel = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.sequence)([(0, combinator_1.surround)(/(?<![0-9a-z])@/yi, /[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*\//yi, /[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*(?![-.]?[0-9a-z@]|>>|:\S)/yi, true, undefined, undefined, [3 | 0 /* Backtrack.autolink */]), (0, combinator_1.some)((0, combinator_1.verify)((0, combinator_1.surround)('#', (0, source_1.str)(new RegExp([/(?!['_])(?:[^\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^\p{C}\p{S}\p{P}\s]|emoji))+/yu.source].join('|').replace(/emoji/g, hashtag_1.emoji.source), 'yu')), new RegExp([/(?![0-9a-z@]|>>|:\S|[^\p{C}\p{S}\p{P}\s]|emoji)/yu.source].join('|').replace(/emoji/g, hashtag_1.emoji.source), 'yu'), false, undefined, undefined, [3 | 0 /* Backtrack.autolink */]), ([{
|
|
6296
6324
|
value
|
|
6297
6325
|
}]) => !/^[0-9]{1,4}$|^[0-9]{5}/.test(value)))]), (0, combinator_1.constraint)(1 /* State.autolink */, (0, combinator_1.state)(1 /* State.autolink */, (0, combinator_1.fmap)((0, combinator_1.convert)(source => `[${source}]{ ${source.includes('/') ? `https://${source.slice(1, source.indexOf('#')).replace('/', '/@')}` : `/${source.slice(0, source.indexOf('#'))}`} }`, (0, combinator_1.union)([link_1.unsafelink]), false), ([{
|
|
6298
6326
|
value: el
|
|
@@ -6353,9 +6381,8 @@ const parser_1 = __webpack_require__(605);
|
|
|
6353
6381
|
const combinator_1 = __webpack_require__(3484);
|
|
6354
6382
|
const link_1 = __webpack_require__(3628);
|
|
6355
6383
|
const hashtag_1 = __webpack_require__(5764);
|
|
6356
|
-
const source_1 = __webpack_require__(8745);
|
|
6357
6384
|
const dom_1 = __webpack_require__(394);
|
|
6358
|
-
exports.hashnum = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.open)(new RegExp([/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/
|
|
6385
|
+
exports.hashnum = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.open)(new RegExp([/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/yu.source].join('|').replace(/emoji/g, hashtag_1.emoji.source), 'yu'), new RegExp([/[0-9]{1,9}(?![0-9a-z@#]|>>|:\S|[^\p{C}\p{S}\p{P}\s]|emoji)/yu.source].join('|').replace(/emoji/g, hashtag_1.emoji.source), 'yu'), false, [1 | 0 /* Backtrack.autolink */]), (0, combinator_1.constraint)(1 /* State.autolink */, (0, combinator_1.state)(1 /* State.autolink */, (0, combinator_1.fmap)((0, combinator_1.convert)(source => `[${source}]{ ${source.slice(1)} }`, (0, combinator_1.union)([link_1.unsafelink]), false), ([{
|
|
6359
6386
|
value
|
|
6360
6387
|
}]) => new parser_1.List([new parser_1.Data((0, dom_1.define)(value, {
|
|
6361
6388
|
class: 'hashnum',
|
|
@@ -6381,8 +6408,8 @@ const source_1 = __webpack_require__(8745);
|
|
|
6381
6408
|
const dom_1 = __webpack_require__(394);
|
|
6382
6409
|
// https://example/hashtags/a must be a hashtag page or a redirect page going there.
|
|
6383
6410
|
// https://github.com/tc39/proposal-regexp-unicode-property-escapes#matching-emoji
|
|
6384
|
-
exports.emoji =
|
|
6385
|
-
exports.hashtag = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.verify)((0, combinator_1.surround)(new RegExp([/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/
|
|
6411
|
+
exports.emoji = /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F|\u200D/u;
|
|
6412
|
+
exports.hashtag = (0, combinator_1.lazy)(() => (0, combinator_1.rewrite)((0, combinator_1.verify)((0, combinator_1.surround)(new RegExp([/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/yu.source].join('|').replace(/emoji/g, exports.emoji.source), 'yu'), (0, source_1.str)(new RegExp([/(?!['_])(?:[^\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^\p{C}\p{S}\p{P}\s]|emoji))+/yu.source].join('|').replace(/emoji/g, exports.emoji.source), 'yu')), new RegExp([/(?![0-9a-z@#]|>>|:\S|[^\p{C}\p{S}\p{P}\s]|emoji)/yu.source].join('|').replace(/emoji/g, exports.emoji.source), 'yu'), false, undefined, undefined, [3 | 0 /* Backtrack.autolink */]), ([{
|
|
6386
6413
|
value
|
|
6387
6414
|
}]) => !/^[0-9]{1,4}$|^[0-9]{5}/.test(value)), (0, combinator_1.constraint)(1 /* State.autolink */, (0, combinator_1.state)(1 /* State.autolink */, (0, combinator_1.fmap)((0, combinator_1.convert)(source => `[${source}]{ ${`/hashtags/${source.slice(1)}`} }`, (0, combinator_1.union)([link_1.unsafelink]), false), ([{
|
|
6388
6415
|
value
|
|
@@ -7065,7 +7092,7 @@ Object.setPrototypeOf(attrspecs, null);
|
|
|
7065
7092
|
Object.values(attrspecs).forEach(o => Object.setPrototypeOf(o, null));
|
|
7066
7093
|
exports.html = (0, combinator_1.lazy)(() => (0, combinator_1.validate)(/<[a-z]+(?=[ >])/yi, (0, combinator_1.union)([(0, combinator_1.surround)(
|
|
7067
7094
|
// https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
|
7068
|
-
(0, source_1.str)(/<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[ >])/
|
|
7095
|
+
(0, source_1.str)(/<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[ >])/y), (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.Data(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, bs = new parser_1.List()], context) => new parser_1.List([new parser_1.Data(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.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)),
|
|
7069
7096
|
// 不可視のHTML構造が可視構造を変化させるべきでない。
|
|
7070
7097
|
// 可視のHTMLは優先度変更を検討する。
|
|
7071
7098
|
// このため<>は将来的に共通構造を変化させる可能性があり
|
|
@@ -7140,19 +7167,10 @@ Object.defineProperty(exports, "__esModule", ({
|
|
|
7140
7167
|
exports.htmlentity = exports.unsafehtmlentity = void 0;
|
|
7141
7168
|
const parser_1 = __webpack_require__(605);
|
|
7142
7169
|
const combinator_1 = __webpack_require__(3484);
|
|
7170
|
+
const source_1 = __webpack_require__(8745);
|
|
7143
7171
|
const util_1 = __webpack_require__(4992);
|
|
7144
7172
|
const dom_1 = __webpack_require__(394);
|
|
7145
|
-
exports.unsafehtmlentity = (0, combinator_1.
|
|
7146
|
-
//({ source }) => [[parser(source) ?? `${Command.Error}${source}`], '']));
|
|
7147
|
-
({
|
|
7148
|
-
context
|
|
7149
|
-
}) => {
|
|
7150
|
-
const {
|
|
7151
|
-
source
|
|
7152
|
-
} = context;
|
|
7153
|
-
context.position += source.length;
|
|
7154
|
-
return source.length > 1 && source.at(-1) === ';' ? new parser_1.List([new parser_1.Data(parser(source) ?? source)]) : new parser_1.List([new parser_1.Data(source)]);
|
|
7155
|
-
});
|
|
7173
|
+
exports.unsafehtmlentity = (0, combinator_1.surround)((0, source_1.str)('&'), (0, source_1.str)(/[0-9A-Za-z]+/y), (0, source_1.str)(';'), false, ([as, bs, cs]) => new parser_1.List([new parser_1.Data(parser(as.head.value + bs.head.value + cs.head.value))]), ([as, bs]) => new parser_1.List([new parser_1.Data(as.head.value + (bs?.head?.value ?? ''))]), [3 | 64 /* Backtrack.bracket */]);
|
|
7156
7174
|
exports.htmlentity = (0, combinator_1.fmap)((0, combinator_1.union)([exports.unsafehtmlentity]), ([{
|
|
7157
7175
|
value
|
|
7158
7176
|
}]) => new parser_1.List([length === 1 || value.at(-1) !== ';' ? new parser_1.Data(value) : new parser_1.Data((0, dom_1.html)('span', {
|
|
@@ -7162,8 +7180,7 @@ exports.htmlentity = (0, combinator_1.fmap)((0, combinator_1.union)([exports.uns
|
|
|
7162
7180
|
const parser = (el => entity => {
|
|
7163
7181
|
if (entity === '
') return ' ';
|
|
7164
7182
|
el.innerHTML = entity;
|
|
7165
|
-
|
|
7166
|
-
return entity === text ? undefined : text;
|
|
7183
|
+
return el.textContent;
|
|
7167
7184
|
})((0, dom_1.html)('span'));
|
|
7168
7185
|
|
|
7169
7186
|
/***/ },
|
|
@@ -7772,6 +7789,7 @@ exports.ruby = (0, combinator_1.lazy)(() => (0, combinator_1.bind)((0, combinato
|
|
|
7772
7789
|
}, acc) => value + ' ' + acc, '').trim())), new parser_1.Data((0, dom_1.html)('rp', ')'))])))))]);
|
|
7773
7790
|
}
|
|
7774
7791
|
}));
|
|
7792
|
+
const delimiter = /[$"`\[\](){}<>()[]{}]|\\?\n/y;
|
|
7775
7793
|
const text = input => {
|
|
7776
7794
|
const {
|
|
7777
7795
|
context
|
|
@@ -7785,11 +7803,12 @@ const text = input => {
|
|
|
7785
7803
|
for (let {
|
|
7786
7804
|
position
|
|
7787
7805
|
} = context; position < source.length; position = context.position) {
|
|
7788
|
-
|
|
7806
|
+
delimiter.lastIndex = position;
|
|
7807
|
+
if (delimiter.test(source)) break;
|
|
7789
7808
|
switch (source[position]) {
|
|
7790
7809
|
case '&':
|
|
7791
7810
|
{
|
|
7792
|
-
const result = (0, htmlentity_1.unsafehtmlentity)(input) ?? (0, source_1.txt)(input);
|
|
7811
|
+
const result = source[position + 1] !== ' ' ? (0, htmlentity_1.unsafehtmlentity)(input) ?? (0, source_1.txt)(input) : (0, source_1.txt)(input);
|
|
7793
7812
|
acc.last.value += result.head.value;
|
|
7794
7813
|
continue;
|
|
7795
7814
|
}
|
|
@@ -8517,7 +8536,7 @@ exports.strs = strs;
|
|
|
8517
8536
|
Object.defineProperty(exports, "__esModule", ({
|
|
8518
8537
|
value: true
|
|
8519
8538
|
}));
|
|
8520
|
-
exports.backToEmailHead = exports.backToUrlHead = exports.backToWhitespace = exports.next = exports.canSkip = exports.linebreak = exports.txt = exports.text = exports.nonWhitespace = void 0;
|
|
8539
|
+
exports.isAlphanumeric = exports.backToEmailHead = exports.backToUrlHead = exports.backToWhitespace = exports.next = exports.canSkip = exports.linebreak = exports.txt = exports.text = exports.nonWhitespace = void 0;
|
|
8521
8540
|
const parser_1 = __webpack_require__(605);
|
|
8522
8541
|
const combinator_1 = __webpack_require__(3484);
|
|
8523
8542
|
const dom_1 = __webpack_require__(394);
|
|
@@ -8678,8 +8697,9 @@ function backToEmailHead(source, position, index) {
|
|
|
8678
8697
|
exports.backToEmailHead = backToEmailHead;
|
|
8679
8698
|
function isAlphanumeric(char) {
|
|
8680
8699
|
if (char < '0' || '\x7F' < char) return false;
|
|
8681
|
-
return '0' <= char && char <= '9' || '
|
|
8700
|
+
return '0' <= char && char <= '9' || 'A' <= char && char <= 'Z' || 'a' <= char && char <= 'z';
|
|
8682
8701
|
}
|
|
8702
|
+
exports.isAlphanumeric = isAlphanumeric;
|
|
8683
8703
|
//const dict = new class {
|
|
8684
8704
|
// constructor() {
|
|
8685
8705
|
// [
|
|
@@ -8724,7 +8744,6 @@ function seek(source, position) {
|
|
|
8724
8744
|
case '@':
|
|
8725
8745
|
case '#':
|
|
8726
8746
|
case '$':
|
|
8727
|
-
case '&':
|
|
8728
8747
|
case '"':
|
|
8729
8748
|
case '`':
|
|
8730
8749
|
case '[':
|
|
@@ -8760,6 +8779,9 @@ function seek(source, position) {
|
|
|
8760
8779
|
case ':':
|
|
8761
8780
|
if (source[i + 1] === '/' && source[i + 2] === '/') return i;
|
|
8762
8781
|
continue;
|
|
8782
|
+
case '&':
|
|
8783
|
+
if (source[i + 1] !== ' ') return i;
|
|
8784
|
+
continue;
|
|
8763
8785
|
case ' ':
|
|
8764
8786
|
case '\t':
|
|
8765
8787
|
case ' ':
|
package/markdown.d.ts
CHANGED
|
@@ -1057,7 +1057,9 @@ export namespace MarkdownParser {
|
|
|
1057
1057
|
export interface UnsafeHTMLEntityParser extends
|
|
1058
1058
|
// ©
|
|
1059
1059
|
Inline<'unsafehtmlentity'>,
|
|
1060
|
-
Parser<string, Context, [
|
|
1060
|
+
Parser<string, Context, [
|
|
1061
|
+
SourceParser.StrParser,
|
|
1062
|
+
]> {
|
|
1061
1063
|
}
|
|
1062
1064
|
export interface ShortMediaParser extends
|
|
1063
1065
|
// !https://host
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
|
|
|
16
16
|
opener = / {1,4}|\t{1,2}/y;
|
|
17
17
|
}
|
|
18
18
|
assert(!opener.flags.match(/[gm]/) && opener.sticky && !opener.source.startsWith('^'));
|
|
19
|
-
assert(parser);
|
|
19
|
+
assert(parser = parser as Parser<N>);
|
|
20
20
|
return failsafe(bind(block(match(
|
|
21
21
|
opener,
|
|
22
22
|
memoize(
|
|
@@ -27,10 +27,8 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
|
|
|
27
27
|
return new List([new Data(source.slice(position))]);
|
|
28
28
|
}))),
|
|
29
29
|
([indent]) => indent.length * 2 + -(indent[0] === ' '), [], 2 ** 4 - 1)), separation),
|
|
30
|
-
(lines, context) =>
|
|
31
|
-
|
|
32
|
-
return parser(subinput(trimBlockEnd(lines.foldl((acc, node) => acc + node.value, '')), context));
|
|
33
|
-
}));
|
|
30
|
+
(lines, context) =>
|
|
31
|
+
parser(subinput(trimBlockEnd(lines.foldl((acc, node) => acc + node.value, '')), context))));
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
function trimBlockEnd(block: string): string {
|
|
@@ -29,8 +29,22 @@ export function surround<P extends Parser<unknown>, S = string>(
|
|
|
29
29
|
g?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
30
30
|
backtracks?: readonly number[],
|
|
31
31
|
): P;
|
|
32
|
+
export function surround<P extends Parser<string>, S = string>(
|
|
33
|
+
opener: string | RegExp | Parser<S, Context<P>>, parser: string | RegExp | P, closer: string | RegExp | Parser<S, Context<P>>,
|
|
34
|
+
optional?: false,
|
|
35
|
+
f?: (rss: [List<Data<S>>, List<Data<Node<P>>>, List<Data<S>>], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
36
|
+
g?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
37
|
+
backtracks?: readonly number[],
|
|
38
|
+
): P;
|
|
39
|
+
export function surround<P extends Parser<string>, S = string>(
|
|
40
|
+
opener: string | RegExp | Parser<S, Context<P>>, parser: string | RegExp | P, closer: string | RegExp | Parser<S, Context<P>>,
|
|
41
|
+
optional?: boolean,
|
|
42
|
+
f?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined, List<Data<S>>], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
43
|
+
g?: (rss: [List<Data<S>>, List<Data<Node<P>>> | undefined], context: Context<P>) => Result<Node<P>, Context<P>, SubParsers<P>>,
|
|
44
|
+
backtracks?: readonly number[],
|
|
45
|
+
): P;
|
|
32
46
|
export function surround<N>(
|
|
33
|
-
opener: string | RegExp | Parser<N>, parser: Parser<N>, closer: string | RegExp | Parser<N>,
|
|
47
|
+
opener: string | RegExp | Parser<N>, parser: string | RegExp | Parser<N>, closer: string | RegExp | Parser<N>,
|
|
34
48
|
optional: boolean = false,
|
|
35
49
|
f?: (rss: [List<Data<N>>, List<Data<N>>, List<Data<N>>], context: Ctx) => Result<N>,
|
|
36
50
|
g?: (rss: [List<Data<N>>, List<Data<N>> | undefined], context: Ctx) => Result<N>,
|
|
@@ -41,11 +55,19 @@ export function surround<N>(
|
|
|
41
55
|
case 'object':
|
|
42
56
|
opener = clear(matcher(opener, true));
|
|
43
57
|
}
|
|
58
|
+
assert(opener);
|
|
59
|
+
switch (typeof parser) {
|
|
60
|
+
case 'string':
|
|
61
|
+
case 'object':
|
|
62
|
+
parser = clear(matcher(parser, true));
|
|
63
|
+
}
|
|
64
|
+
assert(parser);
|
|
44
65
|
switch (typeof closer) {
|
|
45
66
|
case 'string':
|
|
46
67
|
case 'object':
|
|
47
68
|
closer = clear(matcher(closer, true));
|
|
48
69
|
}
|
|
70
|
+
assert(closer);
|
|
49
71
|
return failsafe(input => {
|
|
50
72
|
const { context } = input;
|
|
51
73
|
const { source, position } = context;
|
|
@@ -66,8 +88,7 @@ export function surround<N>(
|
|
|
66
88
|
if (!nodesM && !optional) {
|
|
67
89
|
setBacktrack(context, backtracks, position);
|
|
68
90
|
const result = g?.([nodesO, nodesM], context);
|
|
69
|
-
revert(context, linebreak);
|
|
70
|
-
return result;
|
|
91
|
+
return result || void revert(context, linebreak);
|
|
71
92
|
}
|
|
72
93
|
const nodesC = nodesM || optional ? closer(input) : undefined;
|
|
73
94
|
assert(context.position >= position);
|
|
@@ -75,8 +96,7 @@ export function surround<N>(
|
|
|
75
96
|
if (!nodesC) {
|
|
76
97
|
setBacktrack(context, backtracks, position);
|
|
77
98
|
const result = g?.([nodesO, nodesM], context);
|
|
78
|
-
revert(context, linebreak);
|
|
79
|
-
return result;
|
|
99
|
+
return result || void revert(context, linebreak);
|
|
80
100
|
}
|
|
81
101
|
if (context.position === position) {
|
|
82
102
|
return void revert(context, linebreak);
|
|
@@ -88,10 +108,7 @@ export function surround<N>(
|
|
|
88
108
|
if (result) {
|
|
89
109
|
context.linebreak ||= linebreak;
|
|
90
110
|
}
|
|
91
|
-
|
|
92
|
-
revert(context, linebreak);
|
|
93
|
-
}
|
|
94
|
-
return result;
|
|
111
|
+
return result || void revert(context, linebreak);
|
|
95
112
|
});
|
|
96
113
|
}
|
|
97
114
|
export function open<P extends Parser<unknown>>(
|
|
@@ -100,13 +117,19 @@ export function open<P extends Parser<unknown>>(
|
|
|
100
117
|
optional?: boolean,
|
|
101
118
|
backtracks?: readonly number[],
|
|
102
119
|
): P;
|
|
120
|
+
export function open<P extends Parser<string>>(
|
|
121
|
+
opener: string | RegExp | Parser<Node<P>, Context<P>>,
|
|
122
|
+
parser: string | RegExp | P,
|
|
123
|
+
optional?: boolean,
|
|
124
|
+
backtracks?: readonly number[],
|
|
125
|
+
): P;
|
|
103
126
|
export function open<N>(
|
|
104
127
|
opener: string | RegExp | Parser<N, Ctx>,
|
|
105
|
-
parser: Parser<N>,
|
|
128
|
+
parser: string | RegExp | Parser<N>,
|
|
106
129
|
optional?: boolean,
|
|
107
130
|
backtracks?: readonly number[],
|
|
108
131
|
): Parser<N> {
|
|
109
|
-
return surround(opener, parser
|
|
132
|
+
return surround(opener, parser as Parser<N>, '', optional, undefined, undefined, backtracks);
|
|
110
133
|
}
|
|
111
134
|
export function close<P extends Parser<unknown>>(
|
|
112
135
|
parser: P,
|
|
@@ -114,13 +137,19 @@ export function close<P extends Parser<unknown>>(
|
|
|
114
137
|
optional?: boolean,
|
|
115
138
|
backtracks?: readonly number[],
|
|
116
139
|
): P;
|
|
140
|
+
export function close<P extends Parser<string>>(
|
|
141
|
+
parser: string | RegExp | P,
|
|
142
|
+
closer: string | RegExp | Parser<Node<P>, Context<P>>,
|
|
143
|
+
optional?: boolean,
|
|
144
|
+
backtracks?: readonly number[],
|
|
145
|
+
): P;
|
|
117
146
|
export function close<N>(
|
|
118
|
-
parser: Parser<N>,
|
|
147
|
+
parser: string | RegExp | Parser<N>,
|
|
119
148
|
closer: string | RegExp | Parser<N, Ctx>,
|
|
120
149
|
optional?: boolean,
|
|
121
150
|
backtracks?: readonly number[],
|
|
122
151
|
): Parser<N> {
|
|
123
|
-
return surround('', parser
|
|
152
|
+
return surround('', parser as Parser<N>, closer, optional, undefined, undefined, backtracks);
|
|
124
153
|
}
|
|
125
154
|
|
|
126
155
|
const statesize = 2;
|
|
@@ -153,7 +182,7 @@ export function setBacktrack(
|
|
|
153
182
|
position: number,
|
|
154
183
|
length: number = 1,
|
|
155
184
|
): void {
|
|
156
|
-
//
|
|
185
|
+
// バックトラックの可能性がなく記録不要の場合もあるが判別が面倒なので省略
|
|
157
186
|
const { source } = context;
|
|
158
187
|
if (position === source.length) return;
|
|
159
188
|
if (length === 0) return;
|
|
@@ -39,8 +39,11 @@ export interface CtxOptions {
|
|
|
39
39
|
// 区間別テーブルは固定サイズであるためプールして再使用できる。
|
|
40
40
|
// 従って分割時のデータ構造は区間ごとに探索木を動的に生成しデータ数に応じてテーブルに移行するのが最も効率的である。
|
|
41
41
|
// これにより最悪時間計算量線形化に要する最悪空間計算量が+1nに局限される。
|
|
42
|
+
// またはテーブルの参照が高速なら変換せず併用してもよい。
|
|
42
43
|
// 木とテーブルいずれにおいてもバックトラックデータとオーバーヘッドを合わせた追加データサイズの最大値は
|
|
43
44
|
// セグメントサイズに制約されるため入力サイズに対する最大追加データサイズの平均比率はかなり小さくなる。
|
|
45
|
+
// 必要なテーブルの最大サイズは最大セグメントサイズであるため最大追加データサイズは入力サイズにかかわらず
|
|
46
|
+
// 10KB*並列数に留まり最大数百文字以下の短文ならば数百byte*並列数となる。
|
|
44
47
|
//
|
|
45
48
|
// 1. データ数が規定数を超えたら区間テーブルを生成しデータを振り分ける。
|
|
46
49
|
// - 子ノードのポインタだけ保持するとしても1ノード複数データ保持で圧縮できるかは微妙。
|
|
@@ -67,12 +67,20 @@ describe('Unit: parser/normalize', () => {
|
|
|
67
67
|
assert(normalize('\x01---\na: b\x01\n---\n\n!> \x01---\na: b\x01\n---') === '\uFFFD---\na: b\uFFFD\n---\n\n!> \uFFFD---\na: b\uFFFD\n---');
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
it('emoji', () => {
|
|
71
|
+
assert(normalize('😀') === '😀');
|
|
72
|
+
assert(normalize('🤚🏽') === '🤚🏽');
|
|
73
|
+
assert(normalize('👨👩👧') === '👨👩👧');
|
|
74
|
+
assert(normalize('🇺🇳') === '🇺🇳');
|
|
75
|
+
assert(normalize('#️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣') === '#️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣');
|
|
76
|
+
});
|
|
77
|
+
|
|
70
78
|
});
|
|
71
79
|
|
|
72
80
|
describe('escape', () => {
|
|
73
81
|
it('', () => {
|
|
74
82
|
assert(escape('\u200B') === '​');
|
|
75
|
-
assert(escape('\
|
|
83
|
+
assert(escape('\u200F') === '‏');
|
|
76
84
|
});
|
|
77
85
|
|
|
78
86
|
});
|
|
@@ -9,17 +9,16 @@ export function normalize(source: string): string {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function format(source: string): string {
|
|
12
|
-
return source
|
|
13
|
-
.replace(/\r\n?/g, '\n');
|
|
12
|
+
return source.replace(/\r\n?/g, '\n');
|
|
14
13
|
}
|
|
15
14
|
|
|
15
|
+
const invalid = new RegExp([
|
|
16
|
+
/(?![\t\r\n])[\x00-\x1F\x7F]/g.source,
|
|
17
|
+
/(?!\u200D)[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]|(?<![\u1820\u1821])\u180E/g.source,
|
|
18
|
+
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g.source,
|
|
19
|
+
].join('|'), 'g');
|
|
16
20
|
function sanitize(source: string): string {
|
|
17
|
-
return source
|
|
18
|
-
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|[\u2006\u200B-\u200F\u202A-\u202F\u2060\uFEFF]|(?<![\u1820\u1821])\u180E/g, UNICODE_REPLACEMENT_CHARACTER)
|
|
19
|
-
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/g, char =>
|
|
20
|
-
char.length === 1
|
|
21
|
-
? UNICODE_REPLACEMENT_CHARACTER
|
|
22
|
-
: char);
|
|
21
|
+
return source.replace(invalid, UNICODE_REPLACEMENT_CHARACTER);
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
// https://dev.w3.org/html5/html-author/charref
|
|
@@ -58,13 +57,20 @@ export const invisibleHTMLEntityNames = [
|
|
|
58
57
|
'InvisibleComma',
|
|
59
58
|
'ic',
|
|
60
59
|
] as const;
|
|
61
|
-
const
|
|
62
|
-
|
|
60
|
+
const unreadableEscapeHTMLEntityNames = invisibleHTMLEntityNames.filter(name => ![
|
|
61
|
+
'Tab',
|
|
62
|
+
'NewLine',
|
|
63
|
+
'NonBreakingSpace',
|
|
64
|
+
'nbsp',
|
|
65
|
+
'zwj',
|
|
66
|
+
'zwnj',
|
|
67
|
+
].includes(name));
|
|
68
|
+
const unreadableEscapeCharacters = unreadableEscapeHTMLEntityNames
|
|
63
69
|
.map(name => unsafehtmlentity(input(`&${name};`, {}))!.head!.value);
|
|
64
|
-
assert(
|
|
65
|
-
assert(
|
|
66
|
-
const
|
|
67
|
-
assert(!
|
|
70
|
+
assert(unreadableEscapeCharacters.length === unreadableEscapeHTMLEntityNames.length);
|
|
71
|
+
assert(unreadableEscapeCharacters.every(c => c.length === 1));
|
|
72
|
+
const unreadableEscapeCharacter = new RegExp(`[${unreadableEscapeCharacters.join('')}]`, 'g');
|
|
73
|
+
assert(!unreadableEscapeCharacter.source.includes('&'));
|
|
68
74
|
|
|
69
75
|
// https://www.pandanoir.info/entry/2018/03/11/193000
|
|
70
76
|
// http://anti.rosx.net/etc/memo/002_space.html
|
|
@@ -77,7 +83,7 @@ const unreadableSpecialCharacters = [
|
|
|
77
83
|
// ZERO WIDTH NON-JOINER
|
|
78
84
|
'\u200C',
|
|
79
85
|
// ZERO WIDTH JOINER
|
|
80
|
-
'\u200D',
|
|
86
|
+
//'\u200D',
|
|
81
87
|
// LEFT-TO-RIGHT MARK
|
|
82
88
|
'\u200E',
|
|
83
89
|
// RIGHT-TO-LEFT MARK
|
|
@@ -103,7 +109,6 @@ assert(unreadableSpecialCharacters.every(c => sanitize(c) === UNICODE_REPLACEMEN
|
|
|
103
109
|
|
|
104
110
|
// 特殊不可視文字はエディタおよびソースビューアでは等幅および強調表示により可視化する
|
|
105
111
|
export function escape(source: string): string {
|
|
106
|
-
return source
|
|
107
|
-
.
|
|
108
|
-
`&${unreadableHTMLEntityNames[unreadableEscapableCharacters.indexOf(char)]};`);
|
|
112
|
+
return source.replace(unreadableEscapeCharacter, char =>
|
|
113
|
+
`&${unreadableEscapeHTMLEntityNames[unreadableEscapeCharacters.indexOf(char)]};`);
|
|
109
114
|
}
|
|
@@ -361,9 +361,9 @@ describe('Unit: parser/api/parse', () => {
|
|
|
361
361
|
|
|
362
362
|
it('backtrack', function () {
|
|
363
363
|
this.timeout(5000);
|
|
364
|
-
// 最悪計算量での実行速度はCommonMarkの公式JS実装の32n
|
|
364
|
+
// 最悪計算量での実行速度はCommonMarkの公式JS実装の32nに対して3倍遅い程度。
|
|
365
365
|
// 5n = annotation/reference + link + url/math + ruby + text
|
|
366
|
-
const source = `((([[[[#$[${'.'.repeat(
|
|
366
|
+
const source = `((([[[[#$[${'.'.repeat(19998)}`;
|
|
367
367
|
assert.deepStrictEqual(
|
|
368
368
|
[...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
|
|
369
369
|
.map(el => el.tagName),
|
|
@@ -372,7 +372,7 @@ describe('Unit: parser/api/parse', () => {
|
|
|
372
372
|
|
|
373
373
|
it('backtrack error', function () {
|
|
374
374
|
this.timeout(5000);
|
|
375
|
-
const source = `((([[[[#$[${'.'.repeat(
|
|
375
|
+
const source = `((([[[[#$[${'.'.repeat(19998 + 1)}`;
|
|
376
376
|
assert.deepStrictEqual(
|
|
377
377
|
[...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
|
|
378
378
|
.map(el => el.tagName),
|
|
@@ -3,7 +3,6 @@ import { State, Backtrack } from '../../context';
|
|
|
3
3
|
import { List, Data } from '../../../combinator/data/parser';
|
|
4
4
|
import { union, state, constraint, rewrite, surround, convert, fmap, lazy } from '../../../combinator';
|
|
5
5
|
import { unsafelink } from '../link';
|
|
6
|
-
import { str } from '../../source';
|
|
7
6
|
import { define } from 'typed-dom/dom';
|
|
8
7
|
|
|
9
8
|
// https://example/@user must be a user page or a redirect page going there.
|
|
@@ -11,8 +10,8 @@ import { define } from 'typed-dom/dom';
|
|
|
11
10
|
export const account: AutolinkParser.AccountParser = lazy(() => rewrite(
|
|
12
11
|
surround(
|
|
13
12
|
/(?<![0-9a-z])@/yi,
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
/[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*\//yi,
|
|
14
|
+
/[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*(?![-.]?[0-9a-z@#]|>>|:\S)/yi,
|
|
16
15
|
true, undefined, undefined,
|
|
17
16
|
[3 | Backtrack.autolink]),
|
|
18
17
|
constraint(State.autolink, state(State.autolink, fmap(convert(
|
|
@@ -3,7 +3,6 @@ import { State, Backtrack } from '../../context';
|
|
|
3
3
|
import { List, Data } from '../../../combinator/data/parser';
|
|
4
4
|
import { union, state, constraint, rewrite, open, convert, fmap, lazy } from '../../../combinator';
|
|
5
5
|
import { unsafelink } from '../link';
|
|
6
|
-
import { str } from '../../source';
|
|
7
6
|
import { define } from 'typed-dom/dom';
|
|
8
7
|
|
|
9
8
|
// Timeline(pseudonym): user/tid
|
|
@@ -19,7 +18,7 @@ import { define } from 'typed-dom/dom';
|
|
|
19
18
|
export const anchor: AutolinkParser.AnchorParser = lazy(() => rewrite(
|
|
20
19
|
open(
|
|
21
20
|
/(?<![0-9a-z])>>/yi,
|
|
22
|
-
|
|
21
|
+
/(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*\/)?[0-9a-z]+(?:-[0-9a-z]+)*(?!-?[0-9a-z@#]|>>|:\S)/yi,
|
|
23
22
|
false,
|
|
24
23
|
[3 | Backtrack.autolink]),
|
|
25
24
|
constraint(State.autolink, state(State.autolink, fmap(convert(
|
|
@@ -13,18 +13,18 @@ export const channel: AutolinkParser.ChannelParser = lazy(() => rewrite(
|
|
|
13
13
|
sequence([
|
|
14
14
|
surround(
|
|
15
15
|
/(?<![0-9a-z])@/yi,
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*\//yi,
|
|
17
|
+
/[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*(?![-.]?[0-9a-z@]|>>|:\S)/yi,
|
|
18
18
|
true, undefined, undefined,
|
|
19
19
|
[3 | Backtrack.autolink]),
|
|
20
20
|
some(verify(surround(
|
|
21
21
|
'#',
|
|
22
22
|
str(new RegExp([
|
|
23
23
|
/(?!['_])(?:[^\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^\p{C}\p{S}\p{P}\s]|emoji))+/yu.source,
|
|
24
|
-
].join('').replace(/emoji/g, emoji), 'yu')),
|
|
25
|
-
|
|
24
|
+
].join('|').replace(/emoji/g, emoji.source), 'yu')),
|
|
25
|
+
new RegExp([
|
|
26
26
|
/(?![0-9a-z@]|>>|:\S|[^\p{C}\p{S}\p{P}\s]|emoji)/yu.source,
|
|
27
|
-
].join('').replace(/emoji/g, emoji), 'yu')
|
|
27
|
+
].join('|').replace(/emoji/g, emoji.source), 'yu'),
|
|
28
28
|
false, undefined, undefined,
|
|
29
29
|
[3 | Backtrack.autolink]),
|
|
30
30
|
([{ value }]) => !/^[0-9]{1,4}$|^[0-9]{5}/.test(value as string))),
|
|
@@ -4,17 +4,16 @@ import { List, Data } from '../../../combinator/data/parser';
|
|
|
4
4
|
import { union, state, constraint, rewrite, open, convert, fmap, lazy } from '../../../combinator';
|
|
5
5
|
import { unsafelink } from '../link';
|
|
6
6
|
import { emoji } from './hashtag';
|
|
7
|
-
import { str } from '../../source';
|
|
8
7
|
import { define } from 'typed-dom/dom';
|
|
9
8
|
|
|
10
9
|
export const hashnum: AutolinkParser.HashnumParser = lazy(() => rewrite(
|
|
11
10
|
open(
|
|
12
11
|
new RegExp([
|
|
13
|
-
/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/
|
|
14
|
-
].join('').replace(/emoji/g, emoji), 'yu'),
|
|
15
|
-
|
|
12
|
+
/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/yu.source,
|
|
13
|
+
].join('|').replace(/emoji/g, emoji.source), 'yu'),
|
|
14
|
+
new RegExp([
|
|
16
15
|
/[0-9]{1,9}(?![0-9a-z@#]|>>|:\S|[^\p{C}\p{S}\p{P}\s]|emoji)/yu.source,
|
|
17
|
-
].join('').replace(/emoji/g, emoji), 'yu')
|
|
16
|
+
].join('|').replace(/emoji/g, emoji.source), 'yu'),
|
|
18
17
|
false,
|
|
19
18
|
[1 | Backtrack.autolink]),
|
|
20
19
|
constraint(State.autolink, state(State.autolink, fmap(convert(
|
|
@@ -54,10 +54,14 @@ describe('Unit: parser/inline/autolink/hashtag', () => {
|
|
|
54
54
|
assert.deepStrictEqual(inspect(parser('#a_b'), ctx), [['<a class="hashtag" href="/hashtags/a_b">#a_b</a>'], '']);
|
|
55
55
|
assert.deepStrictEqual(inspect(parser('#a__b'), ctx), [['<a class="hashtag" href="/hashtags/a">#a</a>'], '__b']);
|
|
56
56
|
assert.deepStrictEqual(inspect(parser('#あ'), ctx), [['<a class="hashtag" href="/hashtags/あ">#あ</a>'], '']);
|
|
57
|
-
assert.deepStrictEqual(inspect(parser('
|
|
57
|
+
assert.deepStrictEqual(inspect(parser('#😀'), ctx), [['<a class="hashtag" href="/hashtags/😀">#😀</a>'], '']);
|
|
58
|
+
assert.deepStrictEqual(inspect(parser('#🤚🏽'), ctx), [['<a class="hashtag" href="/hashtags/🤚🏽">#🤚🏽</a>'], '']);
|
|
59
|
+
assert.deepStrictEqual(inspect(parser('#👨👩👧'), ctx), [['<a class="hashtag" href="/hashtags/👨👩👧">#👨👩👧</a>'], '']);
|
|
60
|
+
assert.deepStrictEqual(inspect(parser('#🇺🇳'), ctx), [['<a class="hashtag" href="/hashtags/🇺🇳">#🇺🇳</a>'], '']);
|
|
61
|
+
assert.deepStrictEqual(inspect(parser('##️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣'), ctx), [['<a class="hashtag" href="/hashtags/#️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣">##️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣</a>'], '']);
|
|
58
62
|
assert.deepStrictEqual(inspect(parser('#1a'), ctx), [['<a class="hashtag" href="/hashtags/1a">#1a</a>'], '']);
|
|
59
63
|
assert.deepStrictEqual(inspect(parser('#1あ'), ctx), [['<a class="hashtag" href="/hashtags/1あ">#1あ</a>'], '']);
|
|
60
|
-
assert.deepStrictEqual(inspect(parser('#1
|
|
64
|
+
assert.deepStrictEqual(inspect(parser('#1😀'), ctx), [['<a class="hashtag" href="/hashtags/1😀">#1😀</a>'], '']);
|
|
61
65
|
assert.deepStrictEqual(inspect(parser(`#a'`), ctx), [[`<a class="hashtag" href="/hashtags/a">#a</a>`], `'`]);
|
|
62
66
|
assert.deepStrictEqual(inspect(parser(`#a''`), ctx), [[`<a class="hashtag" href="/hashtags/a">#a</a>`], `''`]);
|
|
63
67
|
assert.deepStrictEqual(inspect(parser(`#a'b`), ctx), [[`<a class="hashtag" href="/hashtags/a'b">#a'b</a>`], '']);
|
|
@@ -9,19 +9,19 @@ import { define } from 'typed-dom/dom';
|
|
|
9
9
|
// https://example/hashtags/a must be a hashtag page or a redirect page going there.
|
|
10
10
|
|
|
11
11
|
// https://github.com/tc39/proposal-regexp-unicode-property-escapes#matching-emoji
|
|
12
|
-
export const emoji =
|
|
12
|
+
export const emoji = /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F|\u200D/u;
|
|
13
13
|
|
|
14
14
|
export const hashtag: AutolinkParser.HashtagParser = lazy(() => rewrite(
|
|
15
15
|
verify(surround(
|
|
16
16
|
new RegExp([
|
|
17
|
-
/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/
|
|
18
|
-
].join('').replace(/emoji/g, emoji), 'yu'),
|
|
17
|
+
/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/yu.source,
|
|
18
|
+
].join('|').replace(/emoji/g, emoji.source), 'yu'),
|
|
19
19
|
str(new RegExp([
|
|
20
20
|
/(?!['_])(?:[^\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^\p{C}\p{S}\p{P}\s]|emoji))+/yu.source,
|
|
21
|
-
].join('').replace(/emoji/g, emoji), 'yu')),
|
|
22
|
-
|
|
21
|
+
].join('|').replace(/emoji/g, emoji.source), 'yu')),
|
|
22
|
+
new RegExp([
|
|
23
23
|
/(?![0-9a-z@#]|>>|:\S|[^\p{C}\p{S}\p{P}\s]|emoji)/yu.source,
|
|
24
|
-
].join('').replace(/emoji/g, emoji), 'yu')
|
|
24
|
+
].join('|').replace(/emoji/g, emoji.source), 'yu'),
|
|
25
25
|
false, undefined, undefined,
|
|
26
26
|
[3 | Backtrack.autolink]),
|
|
27
27
|
([{ value }]) => !/^[0-9]{1,4}$|^[0-9]{5}/.test(value)),
|
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
import { AutolinkParser } from '../inline';
|
|
2
2
|
import { State } from '../context';
|
|
3
|
-
import {
|
|
3
|
+
import { state, lazy } from '../../combinator';
|
|
4
4
|
import { url, lineurl } from './autolink/url';
|
|
5
5
|
import { email } from './autolink/email';
|
|
6
6
|
import { channel } from './autolink/channel';
|
|
7
7
|
import { account } from './autolink/account';
|
|
8
|
-
import { hashtag
|
|
8
|
+
import { hashtag } from './autolink/hashtag';
|
|
9
9
|
import { hashnum } from './autolink/hashnum';
|
|
10
10
|
import { anchor } from './autolink/anchor';
|
|
11
|
+
import { isAlphanumeric } from '../source/text';
|
|
11
12
|
|
|
12
13
|
export const autolink: AutolinkParser = lazy(() =>
|
|
13
|
-
validate(new RegExp([
|
|
14
|
-
/(?<![0-9a-z])@/yi.source,
|
|
15
|
-
/(?<![^\p{C}\p{S}\p{P}\s]|emoji)#/yiu.source,
|
|
16
|
-
/(?<![0-9a-z])>>/yi.source,
|
|
17
|
-
/(?<![0-9a-z][.+-]?|[@#])!?[0-9a-z]/yi.source,
|
|
18
|
-
].join('|').replace(/emoji/g, emoji), 'yiu'),
|
|
19
14
|
state(~State.autolink,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
15
|
+
input => {
|
|
16
|
+
const { context: { source, position } } = input;
|
|
17
|
+
if (position === source.length) return;
|
|
18
|
+
const fst = source[position];
|
|
19
|
+
switch (fst) {
|
|
20
|
+
case '@':
|
|
21
|
+
return channel(input) || account(input);
|
|
22
|
+
case '#':
|
|
23
|
+
return hashtag(input) || hashnum(input);
|
|
24
|
+
case '>':
|
|
25
|
+
return anchor(input);
|
|
26
|
+
case '!':
|
|
27
|
+
if (!source.startsWith('http', position + 1)) break;
|
|
28
|
+
if (position === 0) return lineurl(input);
|
|
29
|
+
switch (source[position - 1]) {
|
|
30
|
+
case '\r':
|
|
31
|
+
case '\n':
|
|
32
|
+
return lineurl(input);
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
case 'h':
|
|
36
|
+
if (!source.startsWith('http', position)) return;
|
|
37
|
+
if (position === 0) return lineurl(input) || url(input) || email(input);
|
|
38
|
+
switch (source[position - 1]) {
|
|
39
|
+
case '\r':
|
|
40
|
+
case '\n':
|
|
41
|
+
return lineurl(input) || url(input) || email(input);
|
|
42
|
+
}
|
|
43
|
+
return url(input) || email(input);
|
|
44
|
+
default:
|
|
45
|
+
if (isAlphanumeric(fst)) return email(input);
|
|
46
|
+
}
|
|
47
|
+
}));
|
|
@@ -23,7 +23,7 @@ export const html: HTMLParser = lazy(() => validate(/<[a-z]+(?=[ >])/yi,
|
|
|
23
23
|
union([
|
|
24
24
|
surround(
|
|
25
25
|
// https://html.spec.whatwg.org/multipage/syntax.html#void-elements
|
|
26
|
-
str(/<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[ >])/
|
|
26
|
+
str(/<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[ >])/y),
|
|
27
27
|
some(union([attribute])),
|
|
28
28
|
open(str(/ ?/y), str('>'), true),
|
|
29
29
|
true,
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { HTMLEntityParser, UnsafeHTMLEntityParser } from '../inline';
|
|
2
|
+
import { Backtrack } from '../context';
|
|
2
3
|
import { List, Data } from '../../combinator/data/parser';
|
|
3
|
-
import { union,
|
|
4
|
+
import { union, surround, fmap } from '../../combinator';
|
|
5
|
+
import { str } from '../source';
|
|
4
6
|
import { invalid } from '../util';
|
|
5
7
|
import { html } from 'typed-dom/dom';
|
|
6
8
|
|
|
7
|
-
export const unsafehtmlentity: UnsafeHTMLEntityParser =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
: new List([new Data(source)]);
|
|
16
|
-
});
|
|
9
|
+
export const unsafehtmlentity: UnsafeHTMLEntityParser = surround(
|
|
10
|
+
str('&'), str(/[0-9A-Za-z]+/y), str(';'),
|
|
11
|
+
false,
|
|
12
|
+
([as, bs, cs]) =>
|
|
13
|
+
new List([new Data(parser(as.head!.value + bs.head!.value + cs.head!.value))]),
|
|
14
|
+
([as, bs]) =>
|
|
15
|
+
new List([new Data(as.head!.value + (bs?.head?.value ?? ''))]),
|
|
16
|
+
[3 | Backtrack.bracket]);
|
|
17
17
|
|
|
18
18
|
export const htmlentity: HTMLEntityParser = fmap(
|
|
19
19
|
union([unsafehtmlentity]),
|
|
@@ -23,14 +23,11 @@ export const htmlentity: HTMLEntityParser = fmap(
|
|
|
23
23
|
: new Data(html('span', {
|
|
24
24
|
class: 'invalid',
|
|
25
25
|
...invalid('htmlentity', 'syntax', 'Invalid HTML entity'),
|
|
26
|
-
|
|
26
|
+
}, value))
|
|
27
27
|
]));
|
|
28
28
|
|
|
29
|
-
const parser = (el => (entity: string): string
|
|
29
|
+
const parser = (el => (entity: string): string => {
|
|
30
30
|
if (entity === '
') return ' ';
|
|
31
31
|
el.innerHTML = entity;
|
|
32
|
-
|
|
33
|
-
return entity === text
|
|
34
|
-
? undefined
|
|
35
|
-
: text;
|
|
32
|
+
return el.textContent!;
|
|
36
33
|
})(html('span'));
|
|
@@ -63,6 +63,8 @@ export const ruby: RubyParser = lazy(() => bind(
|
|
|
63
63
|
}
|
|
64
64
|
}));
|
|
65
65
|
|
|
66
|
+
const delimiter = /[$"`\[\](){}<>()[]{}]|\\?\n/y;
|
|
67
|
+
|
|
66
68
|
const text: RubyParser.TextParser = input => {
|
|
67
69
|
const { context } = input;
|
|
68
70
|
const { source } = context;
|
|
@@ -70,11 +72,14 @@ const text: RubyParser.TextParser = input => {
|
|
|
70
72
|
let state = false;
|
|
71
73
|
context.sequential = true;
|
|
72
74
|
for (let { position } = context; position < source.length; position = context.position) {
|
|
73
|
-
|
|
75
|
+
delimiter.lastIndex = position;
|
|
76
|
+
if (delimiter.test(source)) break;
|
|
74
77
|
assert(source[position] !== '\n');
|
|
75
78
|
switch (source[position]) {
|
|
76
79
|
case '&': {
|
|
77
|
-
const result =
|
|
80
|
+
const result = source[position + 1] !== ' '
|
|
81
|
+
? unsafehtmlentity(input) ?? txt(input)!
|
|
82
|
+
: txt(input)!;
|
|
78
83
|
assert(result);
|
|
79
84
|
acc.last!.value += result.head!.value;
|
|
80
85
|
continue;
|
package/src/parser/inline.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { reference } from './inline/reference';
|
|
|
5
5
|
import { template } from './inline/template';
|
|
6
6
|
import { remark } from './inline/remark';
|
|
7
7
|
import { extension } from './inline/extension';
|
|
8
|
+
import { label } from './inline/extension/label';
|
|
8
9
|
import { textlink } from './inline/link';
|
|
9
10
|
import { ruby } from './inline/ruby';
|
|
10
11
|
import { html } from './inline/html';
|
|
@@ -94,7 +95,7 @@ export const inline: InlineParser = lazy(() => union([
|
|
|
94
95
|
return html(input);
|
|
95
96
|
case '$':
|
|
96
97
|
if (source[position + 1] === '{') return math(input);
|
|
97
|
-
return
|
|
98
|
+
return label(input)
|
|
98
99
|
|| math(input);
|
|
99
100
|
case '+':
|
|
100
101
|
if (source[position + 1] === '+') return insertion(input);
|
|
@@ -185,12 +185,12 @@ export function backToEmailHead(source: string, position: number, index: number)
|
|
|
185
185
|
? delim
|
|
186
186
|
: index;
|
|
187
187
|
}
|
|
188
|
-
function isAlphanumeric(char: string): boolean {
|
|
188
|
+
export function isAlphanumeric(char: string): boolean {
|
|
189
189
|
assert(char.length === 1);
|
|
190
190
|
if (char < '0' || '\x7F' < char) return false;
|
|
191
191
|
return '0' <= char && char <= '9'
|
|
192
|
-
|| '
|
|
193
|
-
|| '
|
|
192
|
+
|| 'A' <= char && char <= 'Z'
|
|
193
|
+
|| 'a' <= char && char <= 'z';
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
//const dict = new class {
|
|
@@ -238,7 +238,6 @@ function seek(source: string, position: number): number {
|
|
|
238
238
|
case '@':
|
|
239
239
|
case '#':
|
|
240
240
|
case '$':
|
|
241
|
-
case '&':
|
|
242
241
|
case '"':
|
|
243
242
|
case '`':
|
|
244
243
|
case '[':
|
|
@@ -274,6 +273,9 @@ function seek(source: string, position: number): number {
|
|
|
274
273
|
case ':':
|
|
275
274
|
if (source[i + 1] === '/' && source[i + 2] === '/') return i;
|
|
276
275
|
continue;
|
|
276
|
+
case '&':
|
|
277
|
+
if (source[i + 1] !== ' ') return i;
|
|
278
|
+
continue;
|
|
277
279
|
case ' ':
|
|
278
280
|
case '\t':
|
|
279
281
|
case ' ':
|