@incremark/core 0.2.7 → 0.3.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.
@@ -0,0 +1,1608 @@
1
+ import { Lexer, lexer } from 'marked';
2
+
3
+ // src/parser/ast/MarkedAstBuildter.ts
4
+
5
+ // src/extensions/html-extension/index.ts
6
+ var DEFAULT_TAG_BLACKLIST = [
7
+ "script",
8
+ "style",
9
+ "iframe",
10
+ "object",
11
+ "embed",
12
+ "form",
13
+ "input",
14
+ "button",
15
+ "textarea",
16
+ "select",
17
+ "meta",
18
+ "link",
19
+ "base",
20
+ "frame",
21
+ "frameset",
22
+ "applet",
23
+ "noscript",
24
+ "template"
25
+ ];
26
+ var DEFAULT_ATTR_BLACKLIST = [
27
+ // 事件属性通过正则匹配
28
+ "formaction",
29
+ "xlink:href",
30
+ "xmlns",
31
+ "srcdoc"
32
+ ];
33
+ var DEFAULT_PROTOCOL_BLACKLIST = [
34
+ "javascript:",
35
+ "vbscript:",
36
+ "data:"
37
+ // 注意:data:image/ 会被特殊处理允许
38
+ ];
39
+ var URL_ATTRS = ["href", "src", "action", "formaction", "poster", "background"];
40
+ var VOID_ELEMENTS = ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"];
41
+ function detectHtmlContentType(html) {
42
+ const trimmed = html.trim();
43
+ if (!trimmed) return "unknown";
44
+ if (!trimmed.startsWith("<")) return "unknown";
45
+ const closingMatch = trimmed.match(/^<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>$/);
46
+ if (closingMatch) {
47
+ return "closing";
48
+ }
49
+ const singleTagMatch = trimmed.match(/^<([a-zA-Z][a-zA-Z0-9-]*)(\s[^]*?)?(\/?)>$/);
50
+ if (singleTagMatch) {
51
+ const [fullMatch, tagName, attrsString, selfClosingSlash] = singleTagMatch;
52
+ if (attrsString) {
53
+ let inQuote = "";
54
+ let hasUnquotedBracket = false;
55
+ for (let i = 0; i < attrsString.length; i++) {
56
+ const char = attrsString[i];
57
+ if (inQuote) {
58
+ if (char === inQuote) inQuote = "";
59
+ } else {
60
+ if (char === '"' || char === "'") inQuote = char;
61
+ else if (char === "<") {
62
+ hasUnquotedBracket = true;
63
+ break;
64
+ }
65
+ }
66
+ }
67
+ if (hasUnquotedBracket) {
68
+ return "fragment";
69
+ }
70
+ }
71
+ const isSelfClosing = selfClosingSlash === "/" || VOID_ELEMENTS.includes(tagName.toLowerCase());
72
+ return isSelfClosing ? "self-closing" : "opening";
73
+ }
74
+ let bracketCount = 0;
75
+ for (const char of trimmed) {
76
+ if (char === "<") bracketCount++;
77
+ }
78
+ if (bracketCount > 1) {
79
+ return "fragment";
80
+ }
81
+ return "unknown";
82
+ }
83
+ function parseHtmlTag(html) {
84
+ const trimmed = html.trim();
85
+ const contentType = detectHtmlContentType(trimmed);
86
+ if (contentType !== "opening" && contentType !== "closing" && contentType !== "self-closing") {
87
+ return null;
88
+ }
89
+ if (contentType === "closing") {
90
+ const match2 = trimmed.match(/^<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>$/);
91
+ if (!match2) return null;
92
+ return {
93
+ tagName: match2[1].toLowerCase(),
94
+ attrs: {},
95
+ isClosing: true,
96
+ isSelfClosing: false,
97
+ rawHtml: html
98
+ };
99
+ }
100
+ const match = trimmed.match(/^<([a-zA-Z][a-zA-Z0-9-]*)(\s[^]*?)?(\/?)>$/);
101
+ if (!match) return null;
102
+ const [, tagName, attrsString, selfClosingSlash] = match;
103
+ const isSelfClosing = selfClosingSlash === "/" || VOID_ELEMENTS.includes(tagName.toLowerCase());
104
+ const attrs = {};
105
+ if (attrsString) {
106
+ const attrRegex = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
107
+ let attrMatch;
108
+ while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
109
+ const [, name, doubleQuoted, singleQuoted, unquoted] = attrMatch;
110
+ const value = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
111
+ attrs[name.toLowerCase()] = decodeHtmlEntities(value);
112
+ }
113
+ }
114
+ return {
115
+ tagName: tagName.toLowerCase(),
116
+ attrs,
117
+ isClosing: false,
118
+ isSelfClosing,
119
+ rawHtml: html
120
+ };
121
+ }
122
+ function decodeHtmlEntities(text) {
123
+ const entities = {
124
+ "&amp;": "&",
125
+ "&lt;": "<",
126
+ "&gt;": ">",
127
+ "&quot;": '"',
128
+ "&#39;": "'",
129
+ "&apos;": "'",
130
+ "&nbsp;": " "
131
+ };
132
+ return text.replace(/&(?:#(\d+)|#x([a-fA-F0-9]+)|([a-zA-Z]+));/g, (match, dec, hex, name) => {
133
+ if (dec) return String.fromCharCode(parseInt(dec, 10));
134
+ if (hex) return String.fromCharCode(parseInt(hex, 16));
135
+ return entities[`&${name};`] || match;
136
+ });
137
+ }
138
+ function parseTagDirect(tag) {
139
+ const trimmed = tag.trim();
140
+ const closingMatch = trimmed.match(/^<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>$/);
141
+ if (closingMatch) {
142
+ return {
143
+ tagName: closingMatch[1].toLowerCase(),
144
+ attrs: {},
145
+ isClosing: true,
146
+ isSelfClosing: false,
147
+ rawHtml: tag
148
+ };
149
+ }
150
+ const openMatch = trimmed.match(/^<([a-zA-Z][a-zA-Z0-9-]*)([\s\S]*?)(\/?)>$/);
151
+ if (!openMatch) return null;
152
+ const [, tagName, attrsString, selfClosingSlash] = openMatch;
153
+ const isSelfClosing = selfClosingSlash === "/" || VOID_ELEMENTS.includes(tagName.toLowerCase());
154
+ const attrs = {};
155
+ if (attrsString) {
156
+ const attrRegex = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
157
+ let attrMatch;
158
+ while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
159
+ const [, name, doubleQuoted, singleQuoted, unquoted] = attrMatch;
160
+ const value = doubleQuoted ?? singleQuoted ?? unquoted ?? "";
161
+ attrs[name.toLowerCase()] = decodeHtmlEntities(value);
162
+ }
163
+ }
164
+ return {
165
+ tagName: tagName.toLowerCase(),
166
+ attrs,
167
+ isClosing: false,
168
+ isSelfClosing,
169
+ rawHtml: tag
170
+ };
171
+ }
172
+ function parseHtmlFragment(html, options = {}) {
173
+ const result = [];
174
+ const stack = [];
175
+ const tokenRegex = /(<\/?[a-zA-Z][^>]*>)|([^<]+)/g;
176
+ let match;
177
+ while ((match = tokenRegex.exec(html)) !== null) {
178
+ const [, tag, text] = match;
179
+ if (tag) {
180
+ const parsed = parseTagDirect(tag);
181
+ if (!parsed) continue;
182
+ if (isTagBlacklisted(parsed.tagName, options)) {
183
+ continue;
184
+ }
185
+ if (parsed.isClosing) {
186
+ let found = false;
187
+ for (let i = stack.length - 1; i >= 0; i--) {
188
+ if (stack[i].tagName === parsed.tagName) {
189
+ const node = stack.pop();
190
+ if (stack.length > 0) {
191
+ stack[stack.length - 1].children.push(node);
192
+ } else {
193
+ result.push(node);
194
+ }
195
+ found = true;
196
+ break;
197
+ }
198
+ }
199
+ if (!found) continue;
200
+ } else {
201
+ const sanitizedAttrs = sanitizeAttrs(parsed.attrs, options);
202
+ const node = {
203
+ type: "htmlElement",
204
+ tagName: parsed.tagName,
205
+ attrs: sanitizedAttrs,
206
+ children: [],
207
+ data: options.preserveRawHtml !== false ? {
208
+ rawHtml: tag,
209
+ parsed: true
210
+ } : void 0
211
+ };
212
+ if (parsed.isSelfClosing) {
213
+ if (stack.length > 0) {
214
+ stack[stack.length - 1].children.push(node);
215
+ } else {
216
+ result.push(node);
217
+ }
218
+ } else {
219
+ stack.push(node);
220
+ }
221
+ }
222
+ } else if (text && text.trim()) {
223
+ const textNode = {
224
+ type: "text",
225
+ value: text
226
+ };
227
+ if (stack.length > 0) {
228
+ stack[stack.length - 1].children.push(textNode);
229
+ }
230
+ }
231
+ }
232
+ while (stack.length > 0) {
233
+ const node = stack.pop();
234
+ if (stack.length > 0) {
235
+ stack[stack.length - 1].children.push(node);
236
+ } else {
237
+ result.push(node);
238
+ }
239
+ }
240
+ return result;
241
+ }
242
+ function isTagBlacklisted(tagName, options) {
243
+ const blacklist = options.tagBlacklist ?? DEFAULT_TAG_BLACKLIST;
244
+ return blacklist.includes(tagName.toLowerCase());
245
+ }
246
+ function isAttrBlacklisted(attrName, options) {
247
+ const name = attrName.toLowerCase();
248
+ const blacklist = options.attrBlacklist ?? DEFAULT_ATTR_BLACKLIST;
249
+ if (name.startsWith("on")) return true;
250
+ return blacklist.includes(name);
251
+ }
252
+ function isProtocolDangerous(url, options) {
253
+ const protocolBlacklist = options.protocolBlacklist ?? DEFAULT_PROTOCOL_BLACKLIST;
254
+ const normalizedUrl = url.trim().toLowerCase();
255
+ for (const protocol of protocolBlacklist) {
256
+ if (normalizedUrl.startsWith(protocol)) {
257
+ if (protocol === "data:" && normalizedUrl.startsWith("data:image/")) {
258
+ return false;
259
+ }
260
+ return true;
261
+ }
262
+ }
263
+ return false;
264
+ }
265
+ function sanitizeAttrs(attrs, options) {
266
+ const result = {};
267
+ for (const [name, value] of Object.entries(attrs)) {
268
+ if (isAttrBlacklisted(name, options)) continue;
269
+ if (URL_ATTRS.includes(name.toLowerCase())) {
270
+ if (isProtocolDangerous(value, options)) continue;
271
+ }
272
+ result[name] = value;
273
+ }
274
+ return result;
275
+ }
276
+ function isHtmlNode(node) {
277
+ return node.type === "html";
278
+ }
279
+ function hasChildren(node) {
280
+ return "children" in node && Array.isArray(node.children);
281
+ }
282
+ function mergeFragmentedHtmlNodes(nodes) {
283
+ const result = [];
284
+ let i = 0;
285
+ while (i < nodes.length) {
286
+ const node = nodes[i];
287
+ if (!isHtmlNode(node)) {
288
+ result.push(node);
289
+ i++;
290
+ continue;
291
+ }
292
+ const unclosedTags = findUnclosedTags(node.value);
293
+ if (unclosedTags.length === 0) {
294
+ result.push(node);
295
+ i++;
296
+ continue;
297
+ }
298
+ const mergedParts = [node.value];
299
+ let j = i + 1;
300
+ let currentUnclosed = [...unclosedTags];
301
+ while (j < nodes.length && currentUnclosed.length > 0) {
302
+ const nextNode = nodes[j];
303
+ if (isHtmlNode(nextNode)) {
304
+ const closingInfo = checkClosingTags(nextNode.value, currentUnclosed);
305
+ if (closingInfo.hasRelevantClosing) {
306
+ mergedParts.push(nextNode.value);
307
+ currentUnclosed = closingInfo.remainingUnclosed;
308
+ if (currentUnclosed.length === 0) {
309
+ j++;
310
+ break;
311
+ }
312
+ } else {
313
+ mergedParts.push(nextNode.value);
314
+ }
315
+ } else {
316
+ break;
317
+ }
318
+ j++;
319
+ }
320
+ if (mergedParts.length > 1) {
321
+ const mergedValue = mergedParts.join("\n");
322
+ const mergedNode = {
323
+ type: "html",
324
+ value: mergedValue
325
+ };
326
+ result.push(mergedNode);
327
+ i = j;
328
+ } else {
329
+ result.push(node);
330
+ i++;
331
+ }
332
+ }
333
+ return result;
334
+ }
335
+ function findUnclosedTags(html) {
336
+ const tagStack = [];
337
+ const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9-]*)[^>]*\/?>/g;
338
+ let match;
339
+ while ((match = tagRegex.exec(html)) !== null) {
340
+ const fullTag = match[0];
341
+ const tagName = match[1].toLowerCase();
342
+ if (VOID_ELEMENTS.includes(tagName) || fullTag.endsWith("/>")) {
343
+ continue;
344
+ }
345
+ if (fullTag.startsWith("</")) {
346
+ const lastIndex = tagStack.lastIndexOf(tagName);
347
+ if (lastIndex !== -1) {
348
+ tagStack.splice(lastIndex, 1);
349
+ }
350
+ } else {
351
+ tagStack.push(tagName);
352
+ }
353
+ }
354
+ return tagStack;
355
+ }
356
+ function checkClosingTags(html, unclosedTags) {
357
+ const remaining = [...unclosedTags];
358
+ let hasRelevant = false;
359
+ const closeTagRegex = /<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>/g;
360
+ let match;
361
+ while ((match = closeTagRegex.exec(html)) !== null) {
362
+ const tagName = match[1].toLowerCase();
363
+ const index = remaining.lastIndexOf(tagName);
364
+ if (index !== -1) {
365
+ remaining.splice(index, 1);
366
+ hasRelevant = true;
367
+ }
368
+ }
369
+ return {
370
+ hasRelevantClosing: hasRelevant,
371
+ remainingUnclosed: remaining
372
+ };
373
+ }
374
+ function processHtmlNodesInArray(nodes, options) {
375
+ const mergedNodes = mergeFragmentedHtmlNodes(nodes);
376
+ const result = [];
377
+ let i = 0;
378
+ while (i < mergedNodes.length) {
379
+ const node = mergedNodes[i];
380
+ if (isHtmlNode(node)) {
381
+ const contentType = detectHtmlContentType(node.value);
382
+ if (contentType === "fragment") {
383
+ const fragmentNodes = parseHtmlFragment(node.value, options);
384
+ if (fragmentNodes.length > 0) {
385
+ result.push(...fragmentNodes);
386
+ } else {
387
+ result.push(node);
388
+ }
389
+ i++;
390
+ } else if (contentType === "self-closing") {
391
+ const parsed = parseHtmlTag(node.value);
392
+ if (parsed && !isTagBlacklisted(parsed.tagName, options)) {
393
+ const elementNode = {
394
+ type: "htmlElement",
395
+ tagName: parsed.tagName,
396
+ attrs: sanitizeAttrs(parsed.attrs, options),
397
+ children: [],
398
+ data: options.preserveRawHtml !== false ? {
399
+ rawHtml: node.value,
400
+ parsed: true,
401
+ originalType: "html"
402
+ } : void 0
403
+ };
404
+ result.push(elementNode);
405
+ }
406
+ i++;
407
+ } else if (contentType === "closing") {
408
+ i++;
409
+ } else if (contentType === "opening") {
410
+ const parsed = parseHtmlTag(node.value);
411
+ if (!parsed || isTagBlacklisted(parsed.tagName, options)) {
412
+ i++;
413
+ continue;
414
+ }
415
+ const tagName = parsed.tagName;
416
+ const contentNodes = [];
417
+ let depth = 1;
418
+ let j = i + 1;
419
+ let foundClosing = false;
420
+ while (j < mergedNodes.length && depth > 0) {
421
+ const nextNode = mergedNodes[j];
422
+ if (isHtmlNode(nextNode)) {
423
+ const nextType = detectHtmlContentType(nextNode.value);
424
+ if (nextType === "closing") {
425
+ const nextParsed = parseHtmlTag(nextNode.value);
426
+ if (nextParsed && nextParsed.tagName === tagName) {
427
+ depth--;
428
+ if (depth === 0) {
429
+ foundClosing = true;
430
+ break;
431
+ }
432
+ }
433
+ } else if (nextType === "opening") {
434
+ const nextParsed = parseHtmlTag(nextNode.value);
435
+ if (nextParsed && nextParsed.tagName === tagName) {
436
+ depth++;
437
+ }
438
+ }
439
+ }
440
+ contentNodes.push(nextNode);
441
+ j++;
442
+ }
443
+ const elementNode = {
444
+ type: "htmlElement",
445
+ tagName: parsed.tagName,
446
+ attrs: sanitizeAttrs(parsed.attrs, options),
447
+ children: processHtmlNodesInArray(contentNodes, options),
448
+ data: options.preserveRawHtml !== false ? {
449
+ rawHtml: node.value,
450
+ parsed: true,
451
+ originalType: "html"
452
+ } : void 0
453
+ };
454
+ result.push(elementNode);
455
+ i = foundClosing ? j + 1 : j;
456
+ } else {
457
+ result.push(node);
458
+ i++;
459
+ }
460
+ } else {
461
+ if (hasChildren(node)) {
462
+ const processed = processHtmlNodesInArray(
463
+ node.children,
464
+ options
465
+ );
466
+ result.push({
467
+ ...node,
468
+ children: processed
469
+ });
470
+ } else {
471
+ result.push(node);
472
+ }
473
+ i++;
474
+ }
475
+ }
476
+ return result;
477
+ }
478
+ function transformHtmlNodes(ast, options = {}) {
479
+ return {
480
+ ...ast,
481
+ children: processHtmlNodesInArray(ast.children, options)
482
+ };
483
+ }
484
+
485
+ // src/parser/ast/types.ts
486
+ function extractMarkedExtensions(plugins) {
487
+ const extensions = [];
488
+ for (const plugin of plugins) {
489
+ if ((plugin.type === "marked" || plugin.type === "both") && plugin.marked) {
490
+ extensions.push(...plugin.marked.extensions);
491
+ }
492
+ }
493
+ return extensions;
494
+ }
495
+
496
+ // src/extensions/marked-extensions/explicitDefinitionExtension.ts
497
+ function createExplicitDefinitionExtension() {
498
+ return {
499
+ name: "explicitDefinition",
500
+ level: "block",
501
+ // 🔑 关键修复:start 必须匹配完整的 definition 模式 [id]:,
502
+ // 而不能只匹配 [,否则会把 ![alt][id] 中的 [alt] 误认为是 definition 开头
503
+ // 同时排除脚注定义 [^id]:
504
+ start(src) {
505
+ const match = src.match(/^ {0,3}\[(?!\^)[^\]]+\]:/m);
506
+ return match?.index;
507
+ },
508
+ tokenizer(src) {
509
+ const rule = /^ {0,3}\[(?!\^)[^\]]+\]:.*?(?:\n+|$)/;
510
+ const match = rule.exec(src);
511
+ if (match) {
512
+ const raw = match[0];
513
+ const contentMatch = raw.match(
514
+ /^ {0,3}\[([^\]]+)\]:\s*(\S+)(?:\s+["'(](.*?)["')])?/
515
+ );
516
+ if (contentMatch) {
517
+ const identifier = contentMatch[1].toLowerCase();
518
+ const url = contentMatch[2];
519
+ const title = contentMatch[3];
520
+ if (this.lexer?.tokens?.links) {
521
+ this.lexer.tokens.links[identifier] = { href: url, title };
522
+ }
523
+ return {
524
+ type: "explicitDefinition",
525
+ raw,
526
+ identifier,
527
+ url,
528
+ title
529
+ };
530
+ }
531
+ return { type: "explicitDefinition", raw, identifier: "", url: "" };
532
+ }
533
+ return void 0;
534
+ },
535
+ renderer() {
536
+ return "";
537
+ }
538
+ };
539
+ }
540
+
541
+ // src/extensions/marked-extensions/optimisticReferenceExtension.ts
542
+ function createOptimisticReferenceExtension() {
543
+ return {
544
+ name: "optimisticReference",
545
+ level: "inline",
546
+ start(src) {
547
+ return src.match(/!?\[/)?.index;
548
+ },
549
+ tokenizer(src) {
550
+ const rule = /^(!?)\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\](?:\s*\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\])?/;
551
+ const match = rule.exec(src);
552
+ if (match) {
553
+ const fullMatch = match[0];
554
+ if (src.length > fullMatch.length && src[fullMatch.length] === "(") {
555
+ return void 0;
556
+ }
557
+ if (src.length > fullMatch.length && src[fullMatch.length] === ":") {
558
+ return void 0;
559
+ }
560
+ const isImage = match[1] === "!";
561
+ const text = match[2];
562
+ const refRaw = match[3];
563
+ if (text.startsWith("^")) {
564
+ return void 0;
565
+ }
566
+ let identifier = "";
567
+ let referenceType = "shortcut";
568
+ if (refRaw !== void 0) {
569
+ if (refRaw === "") {
570
+ referenceType = "collapsed";
571
+ identifier = text;
572
+ } else {
573
+ referenceType = "full";
574
+ identifier = refRaw;
575
+ }
576
+ } else {
577
+ referenceType = "shortcut";
578
+ identifier = text;
579
+ if (text.match(/^[ xX]$/)) {
580
+ return void 0;
581
+ }
582
+ }
583
+ return {
584
+ type: "optimisticReference",
585
+ raw: fullMatch,
586
+ isImage,
587
+ text,
588
+ identifier: identifier.toLowerCase(),
589
+ label: identifier,
590
+ referenceType
591
+ };
592
+ }
593
+ return void 0;
594
+ },
595
+ renderer() {
596
+ return "";
597
+ }
598
+ };
599
+ }
600
+
601
+ // src/extensions/marked-extensions/mathExtension.ts
602
+ function resolveOptions(options) {
603
+ return {
604
+ tex: options?.tex ?? false
605
+ };
606
+ }
607
+ function createBlockMathExtension(options) {
608
+ const resolved = resolveOptions(options);
609
+ return {
610
+ name: "blockMath",
611
+ level: "block",
612
+ start(src) {
613
+ const dollarMatch = src.match(/^ {0,3}\$\$/m);
614
+ let bracketMatch = null;
615
+ if (resolved.tex) {
616
+ bracketMatch = src.match(/^ {0,3}\\\[/m);
617
+ }
618
+ if (dollarMatch && bracketMatch) {
619
+ return Math.min(dollarMatch.index, bracketMatch.index);
620
+ }
621
+ return dollarMatch?.index ?? bracketMatch?.index;
622
+ },
623
+ tokenizer(src) {
624
+ const dollarRule = /^ {0,3}\$\$([\s\S]*?)\$\$ *(?:\n+|$)/;
625
+ const dollarMatch = dollarRule.exec(src);
626
+ if (dollarMatch) {
627
+ return {
628
+ type: "blockMath",
629
+ raw: dollarMatch[0],
630
+ text: dollarMatch[1].trim()
631
+ };
632
+ }
633
+ if (resolved.tex) {
634
+ const bracketRule = /^ {0,3}\\\[([\s\S]*?)\\\] *(?:\n+|$)/;
635
+ const bracketMatch = bracketRule.exec(src);
636
+ if (bracketMatch) {
637
+ return {
638
+ type: "blockMath",
639
+ raw: bracketMatch[0],
640
+ text: bracketMatch[1].trim()
641
+ };
642
+ }
643
+ }
644
+ return void 0;
645
+ },
646
+ renderer() {
647
+ return "";
648
+ }
649
+ };
650
+ }
651
+ function createInlineMathExtension(options) {
652
+ const resolved = resolveOptions(options);
653
+ return {
654
+ name: "inlineMath",
655
+ level: "inline",
656
+ start(src) {
657
+ const dollarIndex = src.indexOf("$");
658
+ const validDollarIndex = dollarIndex !== -1 && src[dollarIndex + 1] !== "$" ? dollarIndex : -1;
659
+ let parenIndex = -1;
660
+ if (resolved.tex) {
661
+ parenIndex = src.indexOf("\\(");
662
+ }
663
+ if (validDollarIndex !== -1 && parenIndex !== -1) {
664
+ return Math.min(validDollarIndex, parenIndex);
665
+ }
666
+ if (validDollarIndex !== -1) return validDollarIndex;
667
+ if (parenIndex !== -1) return parenIndex;
668
+ return void 0;
669
+ },
670
+ tokenizer(src) {
671
+ const dollarRule = /^\$(?!\$)((?:\\.|[^\\\n$])+?)\$(?!\d)/;
672
+ const dollarMatch = dollarRule.exec(src);
673
+ if (dollarMatch) {
674
+ return {
675
+ type: "inlineMath",
676
+ raw: dollarMatch[0],
677
+ text: dollarMatch[1].trim()
678
+ };
679
+ }
680
+ if (resolved.tex) {
681
+ const parenRule = /^\\\(([\s\S]*?)\\\)/;
682
+ const parenMatch = parenRule.exec(src);
683
+ if (parenMatch) {
684
+ return {
685
+ type: "inlineMath",
686
+ raw: parenMatch[0],
687
+ text: parenMatch[1].trim()
688
+ };
689
+ }
690
+ }
691
+ return void 0;
692
+ },
693
+ renderer() {
694
+ return "";
695
+ }
696
+ };
697
+ }
698
+
699
+ // src/extensions/marked-extensions/footnoteDefinitionExtension.ts
700
+ function createFootnoteDefinitionExtension() {
701
+ return {
702
+ name: "footnoteDefinitionBlock",
703
+ level: "block",
704
+ start(src) {
705
+ const match = src.match(/^ {0,3}\[\^[^\]]+\]:/m);
706
+ return match?.index;
707
+ },
708
+ tokenizer(src) {
709
+ const firstLineRule = /^ {0,3}\[\^([a-zA-Z0-9_-]+)\]:\s*(.*)/;
710
+ const firstLineMatch = firstLineRule.exec(src);
711
+ if (!firstLineMatch) return void 0;
712
+ const identifier = firstLineMatch[1];
713
+ let content = firstLineMatch[2];
714
+ let raw = firstLineMatch[0];
715
+ const remaining = src.slice(raw.length);
716
+ const lines = remaining.split("\n");
717
+ let lineIndex = 0;
718
+ if (lines[0] === "" && remaining.startsWith("\n")) {
719
+ lineIndex = 1;
720
+ raw += "\n";
721
+ content += "\n";
722
+ }
723
+ while (lineIndex < lines.length) {
724
+ const line = lines[lineIndex];
725
+ if (line.trim() === "") {
726
+ let hasIndentedLineAfter = false;
727
+ for (let j = lineIndex + 1; j < lines.length; j++) {
728
+ const nextLine = lines[j];
729
+ if (nextLine.trim() === "") continue;
730
+ if (nextLine.match(/^( |\t)/)) {
731
+ hasIndentedLineAfter = true;
732
+ }
733
+ break;
734
+ }
735
+ if (hasIndentedLineAfter) {
736
+ raw += line + (lineIndex < lines.length - 1 ? "\n" : "");
737
+ content += "\n" + line;
738
+ lineIndex++;
739
+ continue;
740
+ } else {
741
+ break;
742
+ }
743
+ }
744
+ if (line.match(/^( |\t)/)) {
745
+ raw += line + (lineIndex < lines.length - 1 ? "\n" : "");
746
+ content += "\n" + line;
747
+ lineIndex++;
748
+ continue;
749
+ }
750
+ if (line.match(/^ {0,3}\[\^[^\]]+\]:/)) {
751
+ break;
752
+ }
753
+ break;
754
+ }
755
+ const trimmedContent = content.replace(/\n+$/, "");
756
+ return {
757
+ type: "footnoteDefinitionBlock",
758
+ raw,
759
+ identifier,
760
+ content: trimmedContent
761
+ };
762
+ },
763
+ renderer() {
764
+ return "";
765
+ }
766
+ };
767
+ }
768
+
769
+ // src/extensions/marked-extensions/inlineHtmlExtension.ts
770
+ var SELF_CLOSING_TAGS = /* @__PURE__ */ new Set([
771
+ "area",
772
+ "base",
773
+ "br",
774
+ "col",
775
+ "embed",
776
+ "hr",
777
+ "img",
778
+ "input",
779
+ "link",
780
+ "meta",
781
+ "param",
782
+ "source",
783
+ "track",
784
+ "wbr"
785
+ ]);
786
+ function createInlineHtmlExtension() {
787
+ return {
788
+ name: "inlineHtml",
789
+ level: "inline",
790
+ start(src) {
791
+ const index = src.indexOf("<");
792
+ if (index === -1) return void 0;
793
+ const afterLt = src.slice(index + 1);
794
+ if (!/^[a-zA-Z\/]/.test(afterLt)) return void 0;
795
+ return index;
796
+ },
797
+ tokenizer(src) {
798
+ const completeTagMatch = matchCompleteHtmlElement(src);
799
+ if (completeTagMatch) {
800
+ return {
801
+ type: "inlineHtml",
802
+ raw: completeTagMatch,
803
+ text: completeTagMatch
804
+ };
805
+ }
806
+ const selfClosingMatch = matchSelfClosingTag(src);
807
+ if (selfClosingMatch) {
808
+ return {
809
+ type: "inlineHtml",
810
+ raw: selfClosingMatch,
811
+ text: selfClosingMatch
812
+ };
813
+ }
814
+ return void 0;
815
+ },
816
+ renderer() {
817
+ return "";
818
+ }
819
+ };
820
+ }
821
+ function matchCompleteHtmlElement(src) {
822
+ const openTagMatch = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*>/.exec(src);
823
+ if (!openTagMatch) return null;
824
+ const tagName = openTagMatch[1].toLowerCase();
825
+ const openTag = openTagMatch[0];
826
+ if (SELF_CLOSING_TAGS.has(tagName)) {
827
+ return openTag;
828
+ }
829
+ const afterOpenTag = src.slice(openTag.length);
830
+ let depth = 1;
831
+ let pos = 0;
832
+ const openPattern = new RegExp(`<${tagName}(?:\\s[^>]*)?>`, "gi");
833
+ const closePattern = new RegExp(`</${tagName}>`, "gi");
834
+ while (depth > 0 && pos < afterOpenTag.length) {
835
+ openPattern.lastIndex = pos;
836
+ closePattern.lastIndex = pos;
837
+ const nextOpen = openPattern.exec(afterOpenTag);
838
+ const nextClose = closePattern.exec(afterOpenTag);
839
+ if (!nextClose) {
840
+ return null;
841
+ }
842
+ if (nextOpen && nextOpen.index < nextClose.index) {
843
+ depth++;
844
+ pos = nextOpen.index + nextOpen[0].length;
845
+ } else {
846
+ depth--;
847
+ pos = nextClose.index + nextClose[0].length;
848
+ }
849
+ }
850
+ if (depth === 0) {
851
+ return src.slice(0, openTag.length + pos);
852
+ }
853
+ return null;
854
+ }
855
+ function matchSelfClosingTag(src) {
856
+ const explicitSelfClosing = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*\/>/.exec(src);
857
+ if (explicitSelfClosing) {
858
+ return explicitSelfClosing[0];
859
+ }
860
+ const implicitSelfClosing = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*>/.exec(src);
861
+ if (implicitSelfClosing && SELF_CLOSING_TAGS.has(implicitSelfClosing[1].toLowerCase())) {
862
+ return implicitSelfClosing[0];
863
+ }
864
+ return null;
865
+ }
866
+ function transformBlockMath(token) {
867
+ return {
868
+ type: "math",
869
+ value: token.text,
870
+ meta: null
871
+ };
872
+ }
873
+ function transformFootnoteDefinitionBlock(token, ctx) {
874
+ const children = ctx.parseFootnoteContent(token.content);
875
+ return {
876
+ type: "footnoteDefinition",
877
+ identifier: token.identifier,
878
+ label: token.identifier,
879
+ children
880
+ };
881
+ }
882
+ function transformExplicitDefinition(token) {
883
+ if (!token.identifier || !token.url) return null;
884
+ return {
885
+ type: "definition",
886
+ identifier: token.identifier,
887
+ label: token.identifier,
888
+ url: token.url,
889
+ title: token.title ?? null
890
+ };
891
+ }
892
+ function transformDef(token) {
893
+ if (token.tag.startsWith("^")) {
894
+ const footnoteId = token.tag.slice(1);
895
+ return {
896
+ type: "footnoteDefinition",
897
+ identifier: footnoteId,
898
+ label: footnoteId,
899
+ children: [
900
+ {
901
+ type: "paragraph",
902
+ children: [{ type: "text", value: token.href }]
903
+ }
904
+ ]
905
+ };
906
+ }
907
+ return {
908
+ type: "definition",
909
+ identifier: token.tag,
910
+ label: token.tag,
911
+ url: token.href,
912
+ title: token.title ?? null
913
+ };
914
+ }
915
+ function transformContainer(token, ctx) {
916
+ const attributes = {};
917
+ const attrRegex = /([a-zA-Z0-9_-]+)=?("([^"]*)"|'([^']*)'|([^ ]*))?/g;
918
+ let match;
919
+ while ((match = attrRegex.exec(token.attrs)) !== null) {
920
+ attributes[match[1]] = match[3] || match[4] || match[5] || "";
921
+ }
922
+ const children = ctx.transformTokensWithPosition(token.tokens);
923
+ return {
924
+ type: "containerDirective",
925
+ name: token.name,
926
+ attributes,
927
+ children
928
+ };
929
+ }
930
+ function transformFootnoteDefToken(token, ctx) {
931
+ return {
932
+ type: "footnoteDefinition",
933
+ identifier: token.identifier,
934
+ label: token.identifier,
935
+ children: [
936
+ {
937
+ type: "paragraph",
938
+ children: ctx.transformInline(token.tokens)
939
+ }
940
+ ]
941
+ };
942
+ }
943
+ function transformHeading(token, ctx) {
944
+ return {
945
+ type: "heading",
946
+ depth: token.depth,
947
+ children: ctx.transformInline(token.tokens)
948
+ };
949
+ }
950
+ function transformParagraph(token, ctx) {
951
+ return {
952
+ type: "paragraph",
953
+ children: ctx.transformInline(token.tokens)
954
+ };
955
+ }
956
+ function transformCode(token) {
957
+ return {
958
+ type: "code",
959
+ lang: token.lang || null,
960
+ meta: null,
961
+ // 对齐 micromark 输出
962
+ value: token.text
963
+ };
964
+ }
965
+ function transformBlockquote(token, ctx) {
966
+ const children = ctx.transformTokens(token.tokens);
967
+ return {
968
+ type: "blockquote",
969
+ children
970
+ };
971
+ }
972
+ function transformList(token, ctx) {
973
+ const children = token.items.map((item) => ({
974
+ type: "listItem",
975
+ spread: item.loose,
976
+ checked: item.checked ?? null,
977
+ // 对齐 micromark 输出(GFM 任务列表)
978
+ children: ctx.transformTokens(item.tokens)
979
+ }));
980
+ return {
981
+ type: "list",
982
+ ordered: token.ordered,
983
+ start: token.ordered ? token.start || 1 : null,
984
+ // 对齐 micromark:有序列表有 start,无序列表为 null
985
+ spread: token.loose,
986
+ children
987
+ };
988
+ }
989
+ function transformTable(token, ctx) {
990
+ const headerCells = token.header.map((cell) => ({
991
+ type: "tableCell",
992
+ children: ctx.transformInline(cell.tokens)
993
+ }));
994
+ const bodyRows = token.rows.map((row) => ({
995
+ type: "tableRow",
996
+ children: row.map((cell) => ({
997
+ type: "tableCell",
998
+ children: ctx.transformInline(cell.tokens)
999
+ }))
1000
+ }));
1001
+ return {
1002
+ type: "table",
1003
+ align: token.align,
1004
+ children: [{ type: "tableRow", children: headerCells }, ...bodyRows]
1005
+ };
1006
+ }
1007
+ function transformHr() {
1008
+ return { type: "thematicBreak" };
1009
+ }
1010
+ function transformHtml(token) {
1011
+ return {
1012
+ type: "html",
1013
+ value: token.text
1014
+ };
1015
+ }
1016
+ function transformTextBlock(token, ctx) {
1017
+ if (token.tokens) {
1018
+ return {
1019
+ type: "paragraph",
1020
+ children: ctx.transformInline(token.tokens)
1021
+ };
1022
+ }
1023
+ return {
1024
+ type: "paragraph",
1025
+ children: [{ type: "text", value: token.text }]
1026
+ };
1027
+ }
1028
+ function transformInlineMath(token) {
1029
+ return {
1030
+ type: "inlineMath",
1031
+ value: token.text
1032
+ };
1033
+ }
1034
+ function transformOptimisticReference(token, ctx) {
1035
+ if (token.isImage) {
1036
+ return {
1037
+ type: "imageReference",
1038
+ identifier: token.identifier,
1039
+ label: token.label,
1040
+ referenceType: token.referenceType,
1041
+ alt: token.text
1042
+ };
1043
+ }
1044
+ const labelChildren = ctx.transformInline(new Lexer().inlineTokens(token.text));
1045
+ return {
1046
+ type: "linkReference",
1047
+ identifier: token.identifier,
1048
+ label: token.label,
1049
+ referenceType: token.referenceType,
1050
+ children: labelChildren.length ? labelChildren : [{ type: "text", value: token.text }]
1051
+ };
1052
+ }
1053
+ function transformLink(token, ctx) {
1054
+ if (token.text.startsWith("^") && token.text.length > 1) {
1055
+ const footnoteId = token.text.slice(1);
1056
+ return {
1057
+ type: "footnoteReference",
1058
+ identifier: footnoteId,
1059
+ label: footnoteId
1060
+ };
1061
+ }
1062
+ return {
1063
+ type: "link",
1064
+ url: token.href,
1065
+ title: token.title || null,
1066
+ // 对齐 micromark 输出
1067
+ children: ctx.transformInline(token.tokens)
1068
+ };
1069
+ }
1070
+ function transformImage(token) {
1071
+ return {
1072
+ type: "image",
1073
+ url: token.href,
1074
+ title: token.title || null,
1075
+ // 对齐 micromark 输出
1076
+ alt: token.text
1077
+ };
1078
+ }
1079
+ function transformText(token) {
1080
+ const results = [];
1081
+ const text = token.text;
1082
+ const footnoteRegex = /\[\^([a-zA-Z0-9_-]+)\]/g;
1083
+ let lastIndex = 0;
1084
+ let match;
1085
+ while ((match = footnoteRegex.exec(text)) !== null) {
1086
+ if (match.index > lastIndex) {
1087
+ results.push({
1088
+ type: "text",
1089
+ value: text.substring(lastIndex, match.index)
1090
+ });
1091
+ }
1092
+ results.push({
1093
+ type: "footnoteReference",
1094
+ identifier: match[1],
1095
+ label: match[1]
1096
+ });
1097
+ lastIndex = match.index + match[0].length;
1098
+ }
1099
+ if (lastIndex < text.length) {
1100
+ results.push({
1101
+ type: "text",
1102
+ value: text.substring(lastIndex)
1103
+ });
1104
+ }
1105
+ return results;
1106
+ }
1107
+ function transformStrong(token, ctx) {
1108
+ return {
1109
+ type: "strong",
1110
+ children: ctx.transformInline(token.tokens)
1111
+ };
1112
+ }
1113
+ function transformEmphasis(token, ctx) {
1114
+ return {
1115
+ type: "emphasis",
1116
+ children: ctx.transformInline(token.tokens)
1117
+ };
1118
+ }
1119
+ function transformCodespan(token) {
1120
+ return {
1121
+ type: "inlineCode",
1122
+ value: token.text
1123
+ };
1124
+ }
1125
+ function transformBreak() {
1126
+ return { type: "break" };
1127
+ }
1128
+ function transformDelete(token, ctx) {
1129
+ return {
1130
+ type: "delete",
1131
+ children: ctx.transformInline(token.tokens)
1132
+ };
1133
+ }
1134
+ function transformInlineHtml(token) {
1135
+ const parsed = parseHtmlFragment(token.text);
1136
+ if (parsed.length > 0) {
1137
+ return parsed;
1138
+ }
1139
+ return { type: "text", value: token.text };
1140
+ }
1141
+ function isTokenType(token, type) {
1142
+ return token.type === type;
1143
+ }
1144
+ var builtinBlockTransformers = {
1145
+ blockMath: (token) => {
1146
+ if (isTokenType(token, "blockMath")) return transformBlockMath(token);
1147
+ return null;
1148
+ },
1149
+ footnoteDefinitionBlock: (token, ctx) => {
1150
+ if (isTokenType(token, "footnoteDefinitionBlock"))
1151
+ return transformFootnoteDefinitionBlock(token, ctx);
1152
+ return null;
1153
+ },
1154
+ explicitDefinition: (token) => {
1155
+ if (isTokenType(token, "explicitDefinition"))
1156
+ return transformExplicitDefinition(token);
1157
+ return null;
1158
+ },
1159
+ def: (token) => {
1160
+ if (isTokenType(token, "def")) return transformDef(token);
1161
+ return null;
1162
+ },
1163
+ container: (token, ctx) => {
1164
+ if (isTokenType(token, "container")) return transformContainer(token, ctx);
1165
+ return null;
1166
+ },
1167
+ footnoteDefinition: (token, ctx) => {
1168
+ if (isTokenType(token, "footnoteDefinition"))
1169
+ return transformFootnoteDefToken(token, ctx);
1170
+ return null;
1171
+ },
1172
+ heading: (token, ctx) => {
1173
+ if (isTokenType(token, "heading")) return transformHeading(token, ctx);
1174
+ return null;
1175
+ },
1176
+ paragraph: (token, ctx) => {
1177
+ if (isTokenType(token, "paragraph")) return transformParagraph(token, ctx);
1178
+ return null;
1179
+ },
1180
+ code: (token) => {
1181
+ if (isTokenType(token, "code")) return transformCode(token);
1182
+ return null;
1183
+ },
1184
+ blockquote: (token, ctx) => {
1185
+ if (isTokenType(token, "blockquote")) return transformBlockquote(token, ctx);
1186
+ return null;
1187
+ },
1188
+ list: (token, ctx) => {
1189
+ if (isTokenType(token, "list")) return transformList(token, ctx);
1190
+ return null;
1191
+ },
1192
+ table: (token, ctx) => {
1193
+ if (isTokenType(token, "table")) return transformTable(token, ctx);
1194
+ return null;
1195
+ },
1196
+ hr: () => transformHr(),
1197
+ html: (token) => {
1198
+ if (isTokenType(token, "html")) return transformHtml(token);
1199
+ return null;
1200
+ },
1201
+ space: () => null,
1202
+ text: (token, ctx) => {
1203
+ if (isTokenType(token, "text")) return transformTextBlock(token, ctx);
1204
+ return null;
1205
+ }
1206
+ };
1207
+ var builtinInlineTransformers = {
1208
+ inlineMath: (token) => {
1209
+ if (isTokenType(token, "inlineMath")) return transformInlineMath(token);
1210
+ return null;
1211
+ },
1212
+ optimisticReference: (token, ctx) => {
1213
+ if (isTokenType(token, "optimisticReference"))
1214
+ return transformOptimisticReference(token, ctx);
1215
+ return null;
1216
+ },
1217
+ link: (token, ctx) => {
1218
+ if (isTokenType(token, "link")) return transformLink(token, ctx);
1219
+ return null;
1220
+ },
1221
+ image: (token) => {
1222
+ if (isTokenType(token, "image")) return transformImage(token);
1223
+ return null;
1224
+ },
1225
+ text: (token) => {
1226
+ if (isTokenType(token, "text")) return transformText(token);
1227
+ return null;
1228
+ },
1229
+ escape: (token) => {
1230
+ if (isTokenType(token, "escape")) return transformText(token);
1231
+ return null;
1232
+ },
1233
+ strong: (token, ctx) => {
1234
+ if (isTokenType(token, "strong")) return transformStrong(token, ctx);
1235
+ return null;
1236
+ },
1237
+ em: (token, ctx) => {
1238
+ if (isTokenType(token, "em")) return transformEmphasis(token, ctx);
1239
+ return null;
1240
+ },
1241
+ codespan: (token) => {
1242
+ if (isTokenType(token, "codespan")) return transformCodespan(token);
1243
+ return null;
1244
+ },
1245
+ br: () => transformBreak(),
1246
+ del: (token, ctx) => {
1247
+ if (isTokenType(token, "del")) return transformDelete(token, ctx);
1248
+ return null;
1249
+ },
1250
+ inlineHtml: (token) => {
1251
+ if (isTokenType(token, "inlineHtml")) return transformInlineHtml(token);
1252
+ return null;
1253
+ }
1254
+ };
1255
+ function transformBlockToken(token, ctx) {
1256
+ const tokenType = token.type;
1257
+ if (ctx.customBlockTransformers?.[tokenType]) {
1258
+ const result = ctx.customBlockTransformers[tokenType](token, ctx);
1259
+ if (result !== void 0) return result;
1260
+ }
1261
+ if (builtinBlockTransformers[tokenType]) {
1262
+ const result = builtinBlockTransformers[tokenType](token, ctx);
1263
+ if (result !== void 0) return result;
1264
+ }
1265
+ if ("text" in token && typeof token.text === "string") {
1266
+ const paragraph = {
1267
+ type: "paragraph",
1268
+ children: [{ type: "text", value: token.text }]
1269
+ };
1270
+ return paragraph;
1271
+ }
1272
+ return null;
1273
+ }
1274
+ function transformInlineToken(token, ctx) {
1275
+ const tokenType = token.type;
1276
+ if (ctx.customInlineTransformers?.[tokenType]) {
1277
+ const result = ctx.customInlineTransformers[tokenType](token, ctx);
1278
+ if (result !== void 0) return result;
1279
+ }
1280
+ if (builtinInlineTransformers[tokenType]) {
1281
+ const result = builtinInlineTransformers[tokenType](token, ctx);
1282
+ if (result !== void 0) return result;
1283
+ }
1284
+ if ("text" in token && typeof token.text === "string") {
1285
+ const text = { type: "text", value: token.text };
1286
+ return text;
1287
+ }
1288
+ return null;
1289
+ }
1290
+
1291
+ // src/parser/ast/MarkedAstBuildter.ts
1292
+ var MarkedAstBuilder = class {
1293
+ constructor(options = {}) {
1294
+ this.options = options;
1295
+ this.containerConfig = typeof options.containers === "object" ? options.containers : options.containers === true ? {} : void 0;
1296
+ this.htmlTreeOptions = typeof options.htmlTree === "object" ? options.htmlTree : options.htmlTree === true ? {} : void 0;
1297
+ if (options.plugins) {
1298
+ this.userExtensions.push(...extractMarkedExtensions(options.plugins));
1299
+ }
1300
+ if (options.markedExtensions) {
1301
+ this.userExtensions.push(...options.markedExtensions);
1302
+ }
1303
+ this.transformContext = {
1304
+ transformTokens: this.transformTokens.bind(this),
1305
+ transformTokensWithPosition: this.transformTokensWithPosition.bind(this),
1306
+ transformInline: this.transformInline.bind(this),
1307
+ parseFootnoteContent: this.parseFootnoteContent.bind(this)
1308
+ };
1309
+ }
1310
+ containerConfig;
1311
+ htmlTreeOptions;
1312
+ globalLinks = {};
1313
+ /** 用户传入的 marked 扩展 */
1314
+ userExtensions = [];
1315
+ /** 转换上下文(用于递归转换) */
1316
+ transformContext;
1317
+ parse(text) {
1318
+ const normalizedText = text.replace(/[\u00A0\u200b\u202f]/g, " ");
1319
+ const optimisticRefExt = createOptimisticReferenceExtension();
1320
+ const explicitDefExt = createExplicitDefinitionExtension();
1321
+ const footnoteDefExt = createFootnoteDefinitionExtension();
1322
+ const userBlockExts = [];
1323
+ const userBlockStartExts = [];
1324
+ const userInlineExts = [];
1325
+ const userInlineStartExts = [];
1326
+ for (const ext of this.userExtensions) {
1327
+ if (ext.level === "block") {
1328
+ if (ext.tokenizer) userBlockExts.push(ext.tokenizer);
1329
+ if (ext.start) userBlockStartExts.push(ext.start);
1330
+ } else if (ext.level === "inline") {
1331
+ if (ext.tokenizer) userInlineExts.push(ext.tokenizer);
1332
+ if (ext.start) userInlineStartExts.push(ext.start);
1333
+ }
1334
+ }
1335
+ const blockExts = [
1336
+ footnoteDefExt.tokenizer,
1337
+ explicitDefExt.tokenizer,
1338
+ ...userBlockExts
1339
+ ];
1340
+ const blockStartExts = [
1341
+ footnoteDefExt.start,
1342
+ explicitDefExt.start,
1343
+ ...userBlockStartExts
1344
+ ];
1345
+ const inlineExts = [optimisticRefExt.tokenizer, ...userInlineExts];
1346
+ const inlineStartExts = [optimisticRefExt.start, ...userInlineStartExts];
1347
+ if (this.options.math) {
1348
+ const mathOptions = typeof this.options.math === "object" ? this.options.math : {};
1349
+ const blockMathExt = createBlockMathExtension(mathOptions);
1350
+ const inlineMathExt = createInlineMathExtension(mathOptions);
1351
+ blockExts.unshift(blockMathExt.tokenizer);
1352
+ blockStartExts.unshift(blockMathExt.start);
1353
+ inlineExts.unshift(inlineMathExt.tokenizer);
1354
+ inlineStartExts.unshift(inlineMathExt.start);
1355
+ }
1356
+ if (this.htmlTreeOptions) {
1357
+ const inlineHtmlExt = createInlineHtmlExtension();
1358
+ inlineExts.unshift(inlineHtmlExt.tokenizer);
1359
+ inlineStartExts.unshift(inlineHtmlExt.start);
1360
+ }
1361
+ const lexerOptions = {
1362
+ gfm: true,
1363
+ breaks: false,
1364
+ // 关闭软换行转 break,与 Micromark 保持一致
1365
+ ...this.options,
1366
+ extensions: {
1367
+ inline: inlineExts,
1368
+ startInline: inlineStartExts,
1369
+ block: blockExts,
1370
+ startBlock: blockStartExts
1371
+ }
1372
+ };
1373
+ const lexerInstance = new Lexer(lexerOptions);
1374
+ if (lexerInstance.tokens && lexerInstance.tokens.links) {
1375
+ Object.assign(lexerInstance.tokens.links, this.globalLinks);
1376
+ }
1377
+ let tokens = lexerInstance.lex(normalizedText);
1378
+ if (lexerInstance.tokens && lexerInstance.tokens.links) {
1379
+ Object.assign(this.globalLinks, lexerInstance.tokens.links);
1380
+ }
1381
+ tokens = this.preprocessTokens(tokens);
1382
+ let children = this.transformTokensWithPosition(tokens);
1383
+ if (this.htmlTreeOptions) {
1384
+ children = this.processHtmlNodes(children);
1385
+ }
1386
+ return {
1387
+ type: "root",
1388
+ children
1389
+ };
1390
+ }
1391
+ /**
1392
+ * 预处理 tokens
1393
+ *
1394
+ * 处理容器指令和遗留的脚注定义(从 paragraph 中提取)
1395
+ */
1396
+ preprocessTokens(tokens) {
1397
+ const result = [];
1398
+ let i = 0;
1399
+ while (i < tokens.length) {
1400
+ const token = tokens[i];
1401
+ if (token.type === "paragraph") {
1402
+ const text = token.text;
1403
+ const footnoteMatch = text.match(/^\[\^([a-zA-Z0-9_-]+)\]:\s+([\s\S]*)$/);
1404
+ if (footnoteMatch) {
1405
+ const defToken = {
1406
+ type: "footnoteDefinition",
1407
+ identifier: footnoteMatch[1],
1408
+ text: footnoteMatch[2],
1409
+ tokens: new Lexer().inlineTokens(footnoteMatch[2]),
1410
+ raw: token.raw
1411
+ };
1412
+ result.push(defToken);
1413
+ i++;
1414
+ continue;
1415
+ }
1416
+ const containerStartMatch = text.match(/^:::(\s*)([a-zA-Z0-9_-]+)(.*?)(\n|$)/);
1417
+ if (containerStartMatch) {
1418
+ const name = containerStartMatch[2];
1419
+ const attrs = containerStartMatch[3].trim();
1420
+ let rawAccumulator = "";
1421
+ let j = i;
1422
+ let depth = 0;
1423
+ let foundEnd = false;
1424
+ let contentRaw = "";
1425
+ while (j < tokens.length) {
1426
+ const currentToken = tokens[j];
1427
+ rawAccumulator += currentToken.raw;
1428
+ const lines = rawAccumulator.split("\n");
1429
+ depth = 0;
1430
+ let startLineIndex = -1;
1431
+ let endLineIndex = -1;
1432
+ for (let k = 0; k < lines.length; k++) {
1433
+ const line = lines[k];
1434
+ if (line.match(/^:::(\s*)([a-zA-Z0-9_-]+)/)) {
1435
+ if (depth === 0 && startLineIndex === -1) startLineIndex = k;
1436
+ depth++;
1437
+ } else if (line.trim() === ":::") {
1438
+ depth--;
1439
+ if (depth === 0) {
1440
+ endLineIndex = k;
1441
+ foundEnd = true;
1442
+ break;
1443
+ }
1444
+ }
1445
+ }
1446
+ if (foundEnd) {
1447
+ const contentLines = lines.slice(startLineIndex + 1, endLineIndex);
1448
+ contentRaw = contentLines.join("\n");
1449
+ const remainingLines = lines.slice(endLineIndex + 1);
1450
+ const remainingText = remainingLines.join("\n");
1451
+ const containerToken = {
1452
+ type: "container",
1453
+ name,
1454
+ attrs,
1455
+ tokens: this.preprocessTokens(lexer(contentRaw)),
1456
+ raw: rawAccumulator
1457
+ };
1458
+ result.push(containerToken);
1459
+ if (remainingText.trim()) {
1460
+ const remainingTokens = this.preprocessTokens(lexer(remainingText));
1461
+ result.push(...remainingTokens);
1462
+ }
1463
+ i = j + 1;
1464
+ break;
1465
+ }
1466
+ j++;
1467
+ }
1468
+ if (foundEnd) continue;
1469
+ }
1470
+ }
1471
+ result.push(token);
1472
+ i++;
1473
+ }
1474
+ return result;
1475
+ }
1476
+ /**
1477
+ * 转换 tokens 为 MDAST 节点(带位置信息)
1478
+ */
1479
+ transformTokensWithPosition(tokens) {
1480
+ if (!tokens) return [];
1481
+ const results = [];
1482
+ let currentOffset = 0;
1483
+ for (const token of tokens) {
1484
+ const rawLength = token.raw?.length ?? 0;
1485
+ const node = transformBlockToken(token, this.transformContext);
1486
+ if (node) {
1487
+ node.position = {
1488
+ start: { line: 0, column: 0, offset: currentOffset },
1489
+ end: { line: 0, column: 0, offset: currentOffset + rawLength }
1490
+ };
1491
+ results.push(node);
1492
+ }
1493
+ currentOffset += rawLength;
1494
+ }
1495
+ return results;
1496
+ }
1497
+ /**
1498
+ * 转换 tokens 为 MDAST 节点(不带位置信息)
1499
+ */
1500
+ transformTokens(tokens) {
1501
+ if (!tokens) return [];
1502
+ return tokens.map((t) => transformBlockToken(t, this.transformContext)).filter(Boolean);
1503
+ }
1504
+ /**
1505
+ * 转换行内 tokens
1506
+ */
1507
+ transformInline(tokens) {
1508
+ if (!tokens) return [];
1509
+ const results = [];
1510
+ for (const token of tokens) {
1511
+ const result = transformInlineToken(token, this.transformContext);
1512
+ if (result) {
1513
+ if (Array.isArray(result)) {
1514
+ results.push(...result);
1515
+ } else {
1516
+ results.push(result);
1517
+ }
1518
+ }
1519
+ }
1520
+ return results;
1521
+ }
1522
+ /**
1523
+ * 解析脚注内容为 AST 节点
1524
+ */
1525
+ parseFootnoteContent(content) {
1526
+ if (!content.trim()) {
1527
+ return [];
1528
+ }
1529
+ const normalizedContent = content.split("\n").map((line, index) => {
1530
+ if (index === 0) return line;
1531
+ if (line.startsWith(" ")) return line.slice(4);
1532
+ if (line.startsWith(" ")) return line.slice(1);
1533
+ return line;
1534
+ }).join("\n");
1535
+ const contentLexer = new Lexer({ gfm: true, breaks: true });
1536
+ const tokens = contentLexer.lex(normalizedContent);
1537
+ return this.transformTokens(tokens);
1538
+ }
1539
+ /**
1540
+ * 处理 HTML 节点
1541
+ *
1542
+ * 使用 html-extension 的 transformHtmlNodes 来处理:
1543
+ * - 合并被空行分割的 HTML 节点
1544
+ * - 将 HTML 解析为 HtmlElementNode 树结构
1545
+ */
1546
+ processHtmlNodes(nodes) {
1547
+ const tempRoot = {
1548
+ type: "root",
1549
+ children: nodes
1550
+ };
1551
+ const transformed = transformHtmlNodes(tempRoot, this.htmlTreeOptions);
1552
+ return transformed.children;
1553
+ }
1554
+ /**
1555
+ * 将 AST 节点转换为 ParsedBlock
1556
+ */
1557
+ nodesToBlocks(nodes, startOffset, rawText, status, generateBlockId) {
1558
+ const blocks = [];
1559
+ for (const node of nodes) {
1560
+ const relativeStart = node.position?.start?.offset ?? 0;
1561
+ const relativeEnd = node.position?.end?.offset ?? rawText.length;
1562
+ const nodeText = rawText.substring(relativeStart, relativeEnd);
1563
+ const absoluteStart = startOffset + relativeStart;
1564
+ const absoluteEnd = startOffset + relativeEnd;
1565
+ blocks.push({
1566
+ id: generateBlockId(),
1567
+ status,
1568
+ node,
1569
+ startOffset: absoluteStart,
1570
+ endOffset: absoluteEnd,
1571
+ rawText: nodeText
1572
+ });
1573
+ }
1574
+ return blocks;
1575
+ }
1576
+ /**
1577
+ * 更新配置选项
1578
+ * @param options 部分配置选项
1579
+ */
1580
+ updateOptions(options) {
1581
+ Object.assign(this.options, options);
1582
+ if ("containers" in options) {
1583
+ this.containerConfig = typeof options.containers === "object" ? options.containers : options.containers === true ? {} : void 0;
1584
+ }
1585
+ if ("htmlTree" in options) {
1586
+ this.htmlTreeOptions = typeof options.htmlTree === "object" ? options.htmlTree : options.htmlTree === true ? {} : void 0;
1587
+ }
1588
+ if (options.plugins || options.markedExtensions) {
1589
+ this.userExtensions.length = 0;
1590
+ if (options.plugins) {
1591
+ this.userExtensions.push(...extractMarkedExtensions(options.plugins));
1592
+ }
1593
+ if (options.markedExtensions) {
1594
+ this.userExtensions.push(...options.markedExtensions);
1595
+ }
1596
+ }
1597
+ }
1598
+ };
1599
+ var AstBuilder = MarkedAstBuilder;
1600
+
1601
+ // src/engines/marked/index.ts
1602
+ function createMarkedBuilder(options = {}) {
1603
+ return new MarkedAstBuilder(options);
1604
+ }
1605
+
1606
+ export { AstBuilder, MarkedAstBuilder, createMarkedBuilder };
1607
+ //# sourceMappingURL=index.js.map
1608
+ //# sourceMappingURL=index.js.map