@incremark/core 0.2.6 → 0.3.0

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/dist/index.js CHANGED
@@ -1,21 +1,758 @@
1
- import { fromMarkdown } from 'mdast-util-from-markdown';
2
- import { gfmFromMarkdown } from 'mdast-util-gfm';
3
- import { gfm } from 'micromark-extension-gfm';
4
- import { gfmFootnoteFromMarkdown } from 'mdast-util-gfm-footnote';
5
- import { math } from 'micromark-extension-math';
6
- import { mathFromMarkdown } from 'mdast-util-math';
7
- import { codes, constants, types } from 'micromark-util-symbol';
8
- import { markdownLineEndingOrSpace } from 'micromark-util-character';
9
- import { factoryDestination } from 'micromark-factory-destination';
10
- import { factoryTitle } from 'micromark-factory-title';
11
- import { factoryLabel } from 'micromark-factory-label';
12
- import { factoryWhitespace } from 'micromark-factory-whitespace';
13
- import { gfmFootnote } from 'micromark-extension-gfm-footnote';
14
- import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
15
- import { directive } from 'micromark-extension-directive';
16
- import { directiveFromMarkdown } from 'mdast-util-directive';
1
+ import { Lexer, lexer } from 'marked';
17
2
 
18
- // src/parser/IncremarkParser.ts
3
+ // src/detector/index.ts
4
+ var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
5
+ var RE_EMPTY_LINE = /^\s*$/;
6
+ var RE_HEADING = /^#{1,6}\s/;
7
+ var RE_THEMATIC_BREAK = /^(\*{3,}|-{3,}|_{3,})\s*$/;
8
+ var RE_BLOCKQUOTE = /^\s{0,3}>/;
9
+ var RE_ESCAPE_SPECIAL = /[.*+?^${}()|[\]\\]/g;
10
+ var RE_FOOTNOTE_DEFINITION = /^\[\^([^\]]+)\]:\s/;
11
+ var RE_FOOTNOTE_CONTINUATION = /^(?: |\t)/;
12
+ var fenceEndPatternCache = /* @__PURE__ */ new Map();
13
+ var containerPatternCache = /* @__PURE__ */ new Map();
14
+ function detectFenceStart(line) {
15
+ const match = line.match(RE_FENCE_START);
16
+ if (match) {
17
+ const fence = match[2];
18
+ const char = fence[0];
19
+ return { char, length: fence.length };
20
+ }
21
+ return null;
22
+ }
23
+ function detectFenceEnd(line, context) {
24
+ if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
25
+ return false;
26
+ }
27
+ const cacheKey = `${context.fenceChar}-${context.fenceLength}`;
28
+ let pattern = fenceEndPatternCache.get(cacheKey);
29
+ if (!pattern) {
30
+ pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
31
+ fenceEndPatternCache.set(cacheKey, pattern);
32
+ }
33
+ return pattern.test(line);
34
+ }
35
+ function isEmptyLine(line) {
36
+ return RE_EMPTY_LINE.test(line);
37
+ }
38
+ function isSetextHeadingUnderline(line, prevLine) {
39
+ const trimmed = line.trim();
40
+ if (!/^={3,}$|^-{3,}$/.test(trimmed)) {
41
+ return false;
42
+ }
43
+ if (!prevLine) {
44
+ return false;
45
+ }
46
+ const trimmedPrev = prevLine.trim();
47
+ if (trimmedPrev === "") {
48
+ return false;
49
+ }
50
+ if (/^#{1,6}\s/.test(trimmedPrev) || /^(\*{3,}|-{3,}|_{3,})\s*$/.test(trimmedPrev)) {
51
+ return false;
52
+ }
53
+ if (/^(\s*)([-*+])\s/.test(trimmedPrev) || /^(\s*)(\d{1,9})[.)]\s/.test(trimmedPrev)) {
54
+ return false;
55
+ }
56
+ if (/^\s{0,3}>/.test(trimmedPrev)) {
57
+ return false;
58
+ }
59
+ if (/^(\s*)(`{3,}|~{3,})/.test(trimmedPrev)) {
60
+ return false;
61
+ }
62
+ const underlineIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
63
+ if (underlineIndent > 3) {
64
+ return false;
65
+ }
66
+ const contentIndent = prevLine.match(/^(\s*)/)?.[1].length ?? 0;
67
+ if (contentIndent > 3) {
68
+ return false;
69
+ }
70
+ return true;
71
+ }
72
+ function isHeading(line) {
73
+ return RE_HEADING.test(line);
74
+ }
75
+ function isThematicBreak(line) {
76
+ return RE_THEMATIC_BREAK.test(line.trim());
77
+ }
78
+ function isListItemStart(line) {
79
+ const hasListMarker = /^(\s*)([-*+]|\d{1,9}[.)])/.test(line);
80
+ if (!hasListMarker) {
81
+ return null;
82
+ }
83
+ const match = line.match(/^(\s*)([-*+]|\d{1,9}[.)])(.*)/);
84
+ if (match) {
85
+ const indent = match[1].length;
86
+ const marker = match[2];
87
+ const rest = match[3];
88
+ if (rest.trim()) {
89
+ const isOrdered = /^\d{1,9}[.)]/.test(marker);
90
+ return { ordered: isOrdered, indent };
91
+ }
92
+ if (/^\s+$/.test(rest)) {
93
+ const isOrdered = /^\d{1,9}[.)]/.test(marker);
94
+ return { ordered: isOrdered, indent };
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+ function isBlockquoteStart(line) {
100
+ return RE_BLOCKQUOTE.test(line);
101
+ }
102
+ function isFootnoteDefinitionStart(line) {
103
+ return RE_FOOTNOTE_DEFINITION.test(line);
104
+ }
105
+ function isFootnoteContinuation(line) {
106
+ return RE_FOOTNOTE_CONTINUATION.test(line);
107
+ }
108
+ function detectContainer(line, config) {
109
+ const marker = config?.marker || ":";
110
+ const minLength = config?.minMarkerLength || 3;
111
+ const cacheKey = `${marker}-${minLength}`;
112
+ let pattern = containerPatternCache.get(cacheKey);
113
+ if (!pattern) {
114
+ const escapedMarker = marker.replace(RE_ESCAPE_SPECIAL, "\\$&");
115
+ pattern = new RegExp(
116
+ `^(\\s*)(${escapedMarker}{${minLength},})(?:\\s*(\\w[\\w-]*))?(?:\\{[^}]*\\})?(?:\\s+(.*))?\\s*$`
117
+ );
118
+ containerPatternCache.set(cacheKey, pattern);
119
+ }
120
+ const match = line.match(pattern);
121
+ if (!match) {
122
+ return null;
123
+ }
124
+ const markerLength = match[2].length;
125
+ const name = match[3] || "";
126
+ const isEnd = !name && !match[4];
127
+ if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {
128
+ if (!config.allowedNames.includes(name)) {
129
+ return null;
130
+ }
131
+ }
132
+ return { name, markerLength, isEnd };
133
+ }
134
+ function detectContainerEnd(line, context, config) {
135
+ if (!context.inContainer || !context.containerMarkerLength) {
136
+ return false;
137
+ }
138
+ const result = detectContainer(line, config);
139
+ if (!result) {
140
+ return false;
141
+ }
142
+ return result.isEnd && result.markerLength >= context.containerMarkerLength;
143
+ }
144
+ var CodeContextUpdater = class {
145
+ update(line, context) {
146
+ const newContext = { ...context };
147
+ if (context.inFencedCode) {
148
+ if (detectFenceEnd(line, context)) {
149
+ newContext.inFencedCode = false;
150
+ newContext.fenceChar = void 0;
151
+ newContext.fenceLength = void 0;
152
+ return newContext;
153
+ }
154
+ return null;
155
+ }
156
+ const fence = detectFenceStart(line);
157
+ if (fence) {
158
+ newContext.inFencedCode = true;
159
+ newContext.fenceChar = fence.char;
160
+ newContext.fenceLength = fence.length;
161
+ return newContext;
162
+ }
163
+ return null;
164
+ }
165
+ };
166
+ var ContainerContextUpdater = class {
167
+ update(line, context, config) {
168
+ if (config === void 0) {
169
+ return null;
170
+ }
171
+ const newContext = { ...context };
172
+ if (context.inContainer) {
173
+ if (detectContainerEnd(line, context, config)) {
174
+ newContext.containerDepth = context.containerDepth - 1;
175
+ if (newContext.containerDepth === 0) {
176
+ newContext.inContainer = false;
177
+ newContext.containerMarkerLength = void 0;
178
+ newContext.containerName = void 0;
179
+ }
180
+ return newContext;
181
+ }
182
+ const nested = detectContainer(line, config);
183
+ if (nested && !nested.isEnd) {
184
+ newContext.containerDepth = context.containerDepth + 1;
185
+ return newContext;
186
+ }
187
+ return newContext;
188
+ } else {
189
+ const container = detectContainer(line, config);
190
+ if (container && !container.isEnd) {
191
+ newContext.inContainer = true;
192
+ newContext.containerMarkerLength = container.markerLength;
193
+ newContext.containerName = container.name;
194
+ newContext.containerDepth = 1;
195
+ return newContext;
196
+ }
197
+ }
198
+ return null;
199
+ }
200
+ };
201
+ var FootnoteContextUpdater = class {
202
+ update(line, context) {
203
+ const newContext = { ...context };
204
+ if (!context.inFootnote && isFootnoteDefinitionStart(line)) {
205
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
206
+ newContext.inFootnote = true;
207
+ newContext.footnoteIdentifier = identifier;
208
+ return newContext;
209
+ }
210
+ if (context.inFootnote) {
211
+ if (isFootnoteDefinitionStart(line)) {
212
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
213
+ newContext.footnoteIdentifier = identifier;
214
+ return newContext;
215
+ }
216
+ if (isEmptyLine(line)) {
217
+ return { ...context };
218
+ }
219
+ const listItem = isListItemStart(line);
220
+ if (listItem) {
221
+ if (listItem.indent === 0) {
222
+ newContext.inFootnote = false;
223
+ newContext.footnoteIdentifier = void 0;
224
+ } else {
225
+ return { ...context };
226
+ }
227
+ return null;
228
+ }
229
+ if (isHeading(line) || detectFenceStart(line) || isBlockquoteStart(line)) {
230
+ newContext.inFootnote = false;
231
+ newContext.footnoteIdentifier = void 0;
232
+ return newContext;
233
+ }
234
+ if (isFootnoteContinuation(line)) {
235
+ return { ...context };
236
+ }
237
+ newContext.inFootnote = false;
238
+ newContext.footnoteIdentifier = void 0;
239
+ return newContext;
240
+ }
241
+ return null;
242
+ }
243
+ };
244
+ var ListContextUpdater = class {
245
+ /**
246
+ * 检测是否是列表项的延续内容(缩进内容或空行)
247
+ */
248
+ isListContinuation(line, listIndent) {
249
+ if (isEmptyLine(line)) {
250
+ return true;
251
+ }
252
+ const contentIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
253
+ return contentIndent > listIndent;
254
+ }
255
+ update(line, context) {
256
+ const newContext = { ...context };
257
+ const listItem = isListItemStart(line);
258
+ if (context.inList) {
259
+ if (context.listMayEnd) {
260
+ if (listItem) {
261
+ if (listItem.ordered === context.listOrdered && listItem.indent === context.listIndent) {
262
+ newContext.listMayEnd = false;
263
+ return newContext;
264
+ }
265
+ newContext.listOrdered = listItem.ordered;
266
+ newContext.listIndent = listItem.indent;
267
+ newContext.listMayEnd = false;
268
+ return newContext;
269
+ } else if (this.isListContinuation(line, context.listIndent ?? 0)) {
270
+ newContext.listMayEnd = isEmptyLine(line);
271
+ return newContext;
272
+ } else {
273
+ newContext.inList = false;
274
+ newContext.listOrdered = void 0;
275
+ newContext.listIndent = void 0;
276
+ newContext.listMayEnd = false;
277
+ return newContext;
278
+ }
279
+ } else {
280
+ if (listItem) {
281
+ return null;
282
+ } else if (isEmptyLine(line)) {
283
+ newContext.listMayEnd = true;
284
+ return newContext;
285
+ } else if (this.isListContinuation(line, context.listIndent ?? 0)) {
286
+ return null;
287
+ } else {
288
+ newContext.inList = false;
289
+ newContext.listOrdered = void 0;
290
+ newContext.listIndent = void 0;
291
+ newContext.listMayEnd = false;
292
+ return newContext;
293
+ }
294
+ }
295
+ } else {
296
+ if (listItem) {
297
+ newContext.inList = true;
298
+ newContext.listOrdered = listItem.ordered;
299
+ newContext.listIndent = listItem.indent;
300
+ newContext.listMayEnd = false;
301
+ return newContext;
302
+ }
303
+ }
304
+ return null;
305
+ }
306
+ };
307
+ function createInitialContext() {
308
+ return {
309
+ inFencedCode: false,
310
+ listDepth: 0,
311
+ blockquoteDepth: 0,
312
+ inContainer: false,
313
+ containerDepth: 0,
314
+ inList: false,
315
+ inFootnote: false,
316
+ footnoteIdentifier: void 0
317
+ };
318
+ }
319
+ var ContextManager = class {
320
+ updaters = [
321
+ new CodeContextUpdater(),
322
+ new ContainerContextUpdater(),
323
+ new FootnoteContextUpdater(),
324
+ new ListContextUpdater()
325
+ ];
326
+ /**
327
+ * 更新上下文(处理一行后)
328
+ *
329
+ * @param line 当前行
330
+ * @param context 当前上下文
331
+ * @param containerConfig 容器配置
332
+ * @returns 更新后的上下文
333
+ */
334
+ update(line, context, containerConfig) {
335
+ const config = containerConfig === true ? {} : containerConfig === false ? void 0 : containerConfig;
336
+ for (const updater of this.updaters) {
337
+ const result = updater.update(line, context, config);
338
+ if (result !== null) {
339
+ return result;
340
+ }
341
+ }
342
+ return { ...context };
343
+ }
344
+ };
345
+ var contextManager = new ContextManager();
346
+ function updateContext(line, context, containerConfig) {
347
+ return contextManager.update(line, context, containerConfig);
348
+ }
349
+
350
+ // src/parser/boundary/BoundaryDetector.ts
351
+ var ContainerBoundaryChecker = class {
352
+ constructor(containerConfig) {
353
+ this.containerConfig = containerConfig;
354
+ }
355
+ check(lineIndex, context, lines) {
356
+ const line = lines[lineIndex];
357
+ if (!context.inContainer) {
358
+ return -1;
359
+ }
360
+ if (this.containerConfig !== void 0) {
361
+ const containerEnd = detectContainerEnd(line, context, this.containerConfig);
362
+ if (containerEnd) {
363
+ return lineIndex - 1;
364
+ }
365
+ }
366
+ return -1;
367
+ }
368
+ };
369
+ var ListBoundaryChecker = class {
370
+ check(lineIndex, context, lines) {
371
+ if (!context.inList) {
372
+ return -1;
373
+ }
374
+ if (!context.listMayEnd) {
375
+ return -1;
376
+ }
377
+ const line = lines[lineIndex];
378
+ const listItem = isListItemStart(line);
379
+ const contentIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
380
+ const isListContent = contentIndent > (context.listIndent ?? 0);
381
+ if (!listItem && !isListContent && !isEmptyLine(line)) {
382
+ return lineIndex - 1;
383
+ }
384
+ return -1;
385
+ }
386
+ };
387
+ var FootnoteBoundaryChecker = class {
388
+ check(lineIndex, context, lines) {
389
+ const line = lines[lineIndex];
390
+ const prevLine = lines[lineIndex - 1];
391
+ if (isFootnoteDefinitionStart(prevLine)) {
392
+ if (isEmptyLine(line) || isFootnoteContinuation(line)) {
393
+ return -1;
394
+ }
395
+ if (isFootnoteDefinitionStart(line)) {
396
+ return lineIndex - 1;
397
+ }
398
+ }
399
+ if (!isEmptyLine(prevLine) && isFootnoteContinuation(prevLine)) {
400
+ return -1;
401
+ }
402
+ if (!isEmptyLine(prevLine) && isFootnoteDefinitionStart(line) && !isFootnoteDefinitionStart(prevLine)) {
403
+ return lineIndex - 1;
404
+ }
405
+ return -1;
406
+ }
407
+ };
408
+ var NewBlockBoundaryChecker = class {
409
+ check(lineIndex, context, lines) {
410
+ const line = lines[lineIndex];
411
+ const prevLine = lines[lineIndex - 1];
412
+ if (isEmptyLine(prevLine)) {
413
+ return -1;
414
+ }
415
+ if (isSetextHeadingUnderline(line, prevLine)) {
416
+ return lineIndex - 1;
417
+ }
418
+ if (isHeading(line)) {
419
+ return lineIndex - 1;
420
+ }
421
+ if (detectFenceStart(line)) {
422
+ return lineIndex - 1;
423
+ }
424
+ if (isBlockquoteStart(line) && !isBlockquoteStart(prevLine)) {
425
+ return lineIndex - 1;
426
+ }
427
+ if (!context.inList && isListItemStart(line) && !isListItemStart(prevLine)) {
428
+ return lineIndex - 1;
429
+ }
430
+ return -1;
431
+ }
432
+ };
433
+ var EmptyLineBoundaryChecker = class {
434
+ check(lineIndex, context, lines) {
435
+ const line = lines[lineIndex];
436
+ const prevLine = lines[lineIndex - 1];
437
+ if (isEmptyLine(line) && !isEmptyLine(prevLine) && !context.inList) {
438
+ return lineIndex;
439
+ }
440
+ return -1;
441
+ }
442
+ };
443
+ var BoundaryDetector = class {
444
+ containerConfig;
445
+ checkers;
446
+ /** 缓存每一行结束时对应的 Context,避免重复计算 */
447
+ contextCache = /* @__PURE__ */ new Map();
448
+ constructor(config = {}) {
449
+ this.containerConfig = config.containers;
450
+ this.checkers = [
451
+ new ContainerBoundaryChecker(this.containerConfig),
452
+ new ListBoundaryChecker(),
453
+ new FootnoteBoundaryChecker(),
454
+ new NewBlockBoundaryChecker(),
455
+ new EmptyLineBoundaryChecker()
456
+ ];
457
+ }
458
+ /**
459
+ * 清空上下文缓存
460
+ * 当 pendingStartLine 推进后调用,释放不再需要的缓存
461
+ */
462
+ clearContextCache(beforeLine) {
463
+ for (const key of this.contextCache.keys()) {
464
+ if (key < beforeLine) {
465
+ this.contextCache.delete(key);
466
+ }
467
+ }
468
+ }
469
+ /**
470
+ * 查找稳定边界
471
+ * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
472
+ *
473
+ * @param lines 所有行
474
+ * @param startLine 起始行
475
+ * @param context 当前上下文
476
+ * @returns 稳定边界结果
477
+ */
478
+ findStableBoundary(lines, startLine, context) {
479
+ let stableLine = -1;
480
+ let stableContext = context;
481
+ let tempContext = startLine > 0 && this.contextCache.has(startLine - 1) ? { ...this.contextCache.get(startLine - 1) } : { ...context };
482
+ for (let i = startLine; i < lines.length; i++) {
483
+ const line = lines[i];
484
+ const wasInFencedCode = tempContext.inFencedCode;
485
+ const wasInContainer = tempContext.inContainer;
486
+ const wasContainerDepth = tempContext.containerDepth;
487
+ tempContext = updateContext(line, tempContext, this.containerConfig);
488
+ this.contextCache.set(i, { ...tempContext });
489
+ if (wasInFencedCode && !tempContext.inFencedCode) {
490
+ if (i < lines.length - 1) {
491
+ stableLine = i;
492
+ stableContext = { ...tempContext };
493
+ }
494
+ continue;
495
+ }
496
+ if (tempContext.inFencedCode) {
497
+ continue;
498
+ }
499
+ if (wasInContainer && wasContainerDepth === 1 && !tempContext.inContainer) {
500
+ if (i < lines.length - 1) {
501
+ stableLine = i;
502
+ stableContext = { ...tempContext };
503
+ }
504
+ continue;
505
+ }
506
+ if (tempContext.inContainer) {
507
+ continue;
508
+ }
509
+ const stablePoint = this.checkStability(i, tempContext, lines);
510
+ if (stablePoint >= 0) {
511
+ stableLine = stablePoint;
512
+ stableContext = { ...tempContext };
513
+ }
514
+ }
515
+ return { line: stableLine, context: stableContext };
516
+ }
517
+ /**
518
+ * 检查指定行是否是稳定边界
519
+ * 使用责任链模式,依次调用各个检查器
520
+ *
521
+ * @param lineIndex 行索引
522
+ * @param context 当前上下文
523
+ * @param lines 所有行
524
+ * @returns 稳定边界行号,如果不是稳定边界返回 -1
525
+ */
526
+ checkStability(lineIndex, context, lines) {
527
+ if (lineIndex === 0) {
528
+ return -1;
529
+ }
530
+ const line = lines[lineIndex];
531
+ const prevLine = lines[lineIndex - 1];
532
+ if (context.inFootnote) {
533
+ if (isFootnoteDefinitionStart(prevLine) && !isEmptyLine(line)) {
534
+ if (isFootnoteContinuation(line)) {
535
+ return -1;
536
+ }
537
+ return lineIndex - 1;
538
+ }
539
+ if (isEmptyLine(prevLine) && (isEmptyLine(line) || isFootnoteContinuation(line))) {
540
+ return -1;
541
+ }
542
+ return -1;
543
+ }
544
+ if (isHeading(prevLine) || isThematicBreak(prevLine)) {
545
+ return lineIndex - 1;
546
+ }
547
+ if (isSetextHeadingUnderline(prevLine, lines[lineIndex - 2])) {
548
+ return lineIndex - 1;
549
+ }
550
+ if (lineIndex >= lines.length - 1) {
551
+ return -1;
552
+ }
553
+ for (const checker of this.checkers) {
554
+ const stablePoint = checker.check(lineIndex, context, lines);
555
+ if (stablePoint >= 0) {
556
+ return stablePoint;
557
+ }
558
+ }
559
+ return -1;
560
+ }
561
+ };
562
+
563
+ // src/utils/index.ts
564
+ function isDefinitionNode(node) {
565
+ return node.type === "definition";
566
+ }
567
+ function isFootnoteDefinitionNode(node) {
568
+ return node.type === "footnoteDefinition";
569
+ }
570
+ function traverseAst(node, visitor) {
571
+ const stopEarly = visitor(node);
572
+ if (stopEarly === true) {
573
+ return;
574
+ }
575
+ if ("children" in node && Array.isArray(node.children)) {
576
+ for (const child of node.children) {
577
+ traverseAst(child, visitor);
578
+ }
579
+ }
580
+ }
581
+ function collectAstNodes(node, predicate) {
582
+ const results = [];
583
+ traverseAst(node, (node2) => {
584
+ if (predicate(node2)) {
585
+ results.push(node2);
586
+ }
587
+ });
588
+ return results;
589
+ }
590
+
591
+ // src/parser/manager/DefinitionManager.ts
592
+ var DefinitionManager = class {
593
+ definitions = {};
594
+ /**
595
+ * 从已完成的 blocks 中提取 definitions
596
+ *
597
+ * @param blocks 已完成的块
598
+ */
599
+ extractFromBlocks(blocks) {
600
+ for (const block of blocks) {
601
+ const newDefinitions = this.findDefinitions(block);
602
+ this.definitions = {
603
+ ...this.definitions,
604
+ ...newDefinitions
605
+ };
606
+ }
607
+ }
608
+ /**
609
+ * 从 block 中提取 definitions
610
+ *
611
+ * @param block 解析块
612
+ * @returns Definition 映射表
613
+ */
614
+ findDefinitions(block) {
615
+ const definitions = collectAstNodes(block.node, isDefinitionNode);
616
+ return definitions.reduce((acc, node) => {
617
+ acc[node.identifier] = node;
618
+ return acc;
619
+ }, {});
620
+ }
621
+ /**
622
+ * 获取所有 definitions
623
+ *
624
+ * @returns Definition 映射表
625
+ */
626
+ getAll() {
627
+ return this.definitions;
628
+ }
629
+ /**
630
+ * 清空所有 definitions
631
+ */
632
+ clear() {
633
+ this.definitions = {};
634
+ }
635
+ };
636
+
637
+ // src/parser/manager/FootnoteManager.ts
638
+ var FootnoteManager = class {
639
+ definitions = {};
640
+ /** 已完成部分的脚注引用顺序(缓存) */
641
+ completedReferenceOrder = [];
642
+ /** 所有脚注引用顺序(包括 pending 部分) */
643
+ referenceOrder = [];
644
+ /**
645
+ * 从已完成的 blocks 中提取 footnote definitions
646
+ *
647
+ * @param blocks 已完成的块
648
+ */
649
+ extractDefinitionsFromBlocks(blocks) {
650
+ for (const block of blocks) {
651
+ const newDefinitions = this.findFootnoteDefinitions(block);
652
+ this.definitions = {
653
+ ...this.definitions,
654
+ ...newDefinitions
655
+ };
656
+ }
657
+ }
658
+ /**
659
+ * 从 block 中提取 footnote definitions
660
+ *
661
+ * @param block 解析块
662
+ * @returns Footnote Definition 映射表
663
+ */
664
+ findFootnoteDefinitions(block) {
665
+ const definitions = collectAstNodes(block.node, isFootnoteDefinitionNode);
666
+ return definitions.reduce((acc, node) => {
667
+ acc[node.identifier] = node;
668
+ return acc;
669
+ }, {});
670
+ }
671
+ /**
672
+ * 从已完成的 blocks 中收集脚注引用(增量更新)
673
+ * 只收集新完成的 blocks 中的引用,并缓存结果
674
+ *
675
+ * @param blocks 新完成的 blocks
676
+ */
677
+ collectReferencesFromCompletedBlocks(blocks) {
678
+ const newReferences = /* @__PURE__ */ new Set();
679
+ blocks.forEach((block) => {
680
+ traverseAst(block.node, (n) => {
681
+ if (n.type === "footnoteReference") {
682
+ const identifier = n.identifier;
683
+ if (!this.completedReferenceOrder.includes(identifier)) {
684
+ newReferences.add(identifier);
685
+ }
686
+ }
687
+ });
688
+ });
689
+ this.completedReferenceOrder.push(...Array.from(newReferences));
690
+ }
691
+ /**
692
+ * 收集 pending blocks 中的脚注引用
693
+ * 返回完整的引用顺序(已完成 + pending)
694
+ *
695
+ * @param pendingBlocks pending blocks
696
+ * @returns 完整的脚注引用顺序
697
+ */
698
+ collectReferencesFromPending(pendingBlocks) {
699
+ const pendingReferences = /* @__PURE__ */ new Set();
700
+ pendingBlocks.forEach((block) => {
701
+ traverseAst(block.node, (n) => {
702
+ if (n.type === "footnoteReference") {
703
+ const identifier = n.identifier;
704
+ if (!this.completedReferenceOrder.includes(identifier)) {
705
+ pendingReferences.add(identifier);
706
+ }
707
+ }
708
+ });
709
+ });
710
+ this.referenceOrder = [...this.completedReferenceOrder, ...Array.from(pendingReferences)];
711
+ return this.referenceOrder;
712
+ }
713
+ /**
714
+ * 收集 AST 中的脚注引用(按出现顺序)
715
+ *
716
+ * @deprecated 使用 collectReferencesFromCompletedBlocks 和 collectReferencesFromPending 代替
717
+ * @param nodes AST 节点列表
718
+ */
719
+ collectReferences(nodes) {
720
+ nodes.forEach((node) => {
721
+ traverseAst(node, (n) => {
722
+ if (n.type === "footnoteReference") {
723
+ const identifier = n.identifier;
724
+ if (!this.referenceOrder.includes(identifier)) {
725
+ this.referenceOrder.push(identifier);
726
+ }
727
+ }
728
+ });
729
+ });
730
+ }
731
+ /**
732
+ * 获取所有 footnote definitions
733
+ *
734
+ * @returns Footnote Definition 映射表
735
+ */
736
+ getDefinitions() {
737
+ return this.definitions;
738
+ }
739
+ /**
740
+ * 获取脚注引用顺序
741
+ *
742
+ * @returns 脚注引用顺序
743
+ */
744
+ getReferenceOrder() {
745
+ return this.referenceOrder;
746
+ }
747
+ /**
748
+ * 清空所有 footnote definitions 和引用顺序
749
+ */
750
+ clear() {
751
+ this.definitions = {};
752
+ this.completedReferenceOrder = [];
753
+ this.referenceOrder = [];
754
+ }
755
+ };
19
756
 
20
757
  // src/extensions/html-extension/index.ts
21
758
  var DEFAULT_TAG_BLACKLIST = [
@@ -294,11 +1031,104 @@ function isHtmlNode(node) {
294
1031
  function hasChildren(node) {
295
1032
  return "children" in node && Array.isArray(node.children);
296
1033
  }
297
- function processHtmlNodesInArray(nodes, options) {
1034
+ function mergeFragmentedHtmlNodes(nodes) {
298
1035
  const result = [];
299
1036
  let i = 0;
300
1037
  while (i < nodes.length) {
301
1038
  const node = nodes[i];
1039
+ if (!isHtmlNode(node)) {
1040
+ result.push(node);
1041
+ i++;
1042
+ continue;
1043
+ }
1044
+ const unclosedTags = findUnclosedTags(node.value);
1045
+ if (unclosedTags.length === 0) {
1046
+ result.push(node);
1047
+ i++;
1048
+ continue;
1049
+ }
1050
+ const mergedParts = [node.value];
1051
+ let j = i + 1;
1052
+ let currentUnclosed = [...unclosedTags];
1053
+ while (j < nodes.length && currentUnclosed.length > 0) {
1054
+ const nextNode = nodes[j];
1055
+ if (isHtmlNode(nextNode)) {
1056
+ const closingInfo = checkClosingTags(nextNode.value, currentUnclosed);
1057
+ if (closingInfo.hasRelevantClosing) {
1058
+ mergedParts.push(nextNode.value);
1059
+ currentUnclosed = closingInfo.remainingUnclosed;
1060
+ if (currentUnclosed.length === 0) {
1061
+ j++;
1062
+ break;
1063
+ }
1064
+ } else {
1065
+ mergedParts.push(nextNode.value);
1066
+ }
1067
+ } else {
1068
+ break;
1069
+ }
1070
+ j++;
1071
+ }
1072
+ if (mergedParts.length > 1) {
1073
+ const mergedValue = mergedParts.join("\n");
1074
+ const mergedNode = {
1075
+ type: "html",
1076
+ value: mergedValue
1077
+ };
1078
+ result.push(mergedNode);
1079
+ i = j;
1080
+ } else {
1081
+ result.push(node);
1082
+ i++;
1083
+ }
1084
+ }
1085
+ return result;
1086
+ }
1087
+ function findUnclosedTags(html) {
1088
+ const tagStack = [];
1089
+ const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9-]*)[^>]*\/?>/g;
1090
+ let match;
1091
+ while ((match = tagRegex.exec(html)) !== null) {
1092
+ const fullTag = match[0];
1093
+ const tagName = match[1].toLowerCase();
1094
+ if (VOID_ELEMENTS.includes(tagName) || fullTag.endsWith("/>")) {
1095
+ continue;
1096
+ }
1097
+ if (fullTag.startsWith("</")) {
1098
+ const lastIndex = tagStack.lastIndexOf(tagName);
1099
+ if (lastIndex !== -1) {
1100
+ tagStack.splice(lastIndex, 1);
1101
+ }
1102
+ } else {
1103
+ tagStack.push(tagName);
1104
+ }
1105
+ }
1106
+ return tagStack;
1107
+ }
1108
+ function checkClosingTags(html, unclosedTags) {
1109
+ const remaining = [...unclosedTags];
1110
+ let hasRelevant = false;
1111
+ const closeTagRegex = /<\/([a-zA-Z][a-zA-Z0-9-]*)\s*>/g;
1112
+ let match;
1113
+ while ((match = closeTagRegex.exec(html)) !== null) {
1114
+ const tagName = match[1].toLowerCase();
1115
+ const index = remaining.lastIndexOf(tagName);
1116
+ if (index !== -1) {
1117
+ remaining.splice(index, 1);
1118
+ hasRelevant = true;
1119
+ }
1120
+ }
1121
+ return {
1122
+ hasRelevantClosing: hasRelevant,
1123
+ remainingUnclosed: remaining
1124
+ };
1125
+ }
1126
+ function processHtmlNodesInArray(nodes, options) {
1127
+ const mergedNodes = mergeFragmentedHtmlNodes(nodes);
1128
+ const result = [];
1129
+ let i = 0;
1130
+ while (i < mergedNodes.length) {
1131
+ const node = mergedNodes[i];
302
1132
  if (isHtmlNode(node)) {
303
1133
  const contentType = detectHtmlContentType(node.value);
304
1134
  if (contentType === "fragment") {
@@ -339,8 +1169,8 @@ function processHtmlNodesInArray(nodes, options) {
339
1169
  let depth = 1;
340
1170
  let j = i + 1;
341
1171
  let foundClosing = false;
342
- while (j < nodes.length && depth > 0) {
343
- const nextNode = nodes[j];
1172
+ while (j < mergedNodes.length && depth > 0) {
1173
+ const nextNode = mergedNodes[j];
344
1174
  if (isHtmlNode(nextNode)) {
345
1175
  const nextType = detectHtmlContentType(nextNode.value);
346
1176
  if (nextType === "closing") {
@@ -403,792 +1233,1057 @@ function transformHtmlNodes(ast, options = {}) {
403
1233
  children: processHtmlNodesInArray(ast.children, options)
404
1234
  };
405
1235
  }
406
- function createHtmlTreeTransformer(options = {}) {
407
- return function transformer(tree) {
408
- return transformHtmlNodes(tree, options);
409
- };
410
- }
411
- var htmlTreeExtension = {
412
- enter: {},
413
- exit: {}
414
- };
415
- function isHtmlElementNode(node) {
416
- return node.type === "htmlElement";
417
- }
418
- function walkHtmlElements(node, callback, parent = null) {
419
- if (isHtmlElementNode(node)) {
420
- callback(node, parent);
421
- }
422
- if (hasChildren(node) || node.type === "root") {
423
- const children = node.children;
424
- for (const child of children) {
425
- walkHtmlElements(child, callback, node);
1236
+
1237
+ // src/parser/ast/types.ts
1238
+ function extractMarkedExtensions(plugins) {
1239
+ const extensions = [];
1240
+ for (const plugin of plugins) {
1241
+ if ((plugin.type === "marked" || plugin.type === "both") && plugin.marked) {
1242
+ extensions.push(...plugin.marked.extensions);
426
1243
  }
427
1244
  }
1245
+ return extensions;
428
1246
  }
429
- function findHtmlElementsByTag(root, tagName) {
430
- const result = [];
431
- walkHtmlElements(root, (node) => {
432
- if (node.tagName === tagName.toLowerCase()) {
433
- result.push(node);
1247
+
1248
+ // src/extensions/marked-extensions/explicitDefinitionExtension.ts
1249
+ function createExplicitDefinitionExtension() {
1250
+ return {
1251
+ name: "explicitDefinition",
1252
+ level: "block",
1253
+ // 🔑 关键修复:start 必须匹配完整的 definition 模式 [id]:,
1254
+ // 而不能只匹配 [,否则会把 ![alt][id] 中的 [alt] 误认为是 definition 开头
1255
+ // 同时排除脚注定义 [^id]:
1256
+ start(src) {
1257
+ const match = src.match(/^ {0,3}\[(?!\^)[^\]]+\]:/m);
1258
+ return match?.index;
1259
+ },
1260
+ tokenizer(src) {
1261
+ const rule = /^ {0,3}\[(?!\^)[^\]]+\]:.*?(?:\n+|$)/;
1262
+ const match = rule.exec(src);
1263
+ if (match) {
1264
+ const raw = match[0];
1265
+ const contentMatch = raw.match(
1266
+ /^ {0,3}\[([^\]]+)\]:\s*(\S+)(?:\s+["'(](.*?)["')])?/
1267
+ );
1268
+ if (contentMatch) {
1269
+ const identifier = contentMatch[1].toLowerCase();
1270
+ const url = contentMatch[2];
1271
+ const title = contentMatch[3];
1272
+ if (this.lexer?.tokens?.links) {
1273
+ this.lexer.tokens.links[identifier] = { href: url, title };
1274
+ }
1275
+ return {
1276
+ type: "explicitDefinition",
1277
+ raw,
1278
+ identifier,
1279
+ url,
1280
+ title
1281
+ };
1282
+ }
1283
+ return { type: "explicitDefinition", raw, identifier: "", url: "" };
1284
+ }
1285
+ return void 0;
1286
+ },
1287
+ renderer() {
1288
+ return "";
434
1289
  }
435
- });
436
- return result;
1290
+ };
437
1291
  }
438
- function htmlElementToString(node) {
439
- const { tagName, attrs, children } = node;
440
- const attrsStr = Object.entries(attrs).map(([name, value]) => {
441
- if (value === "") return name;
442
- return `${name}="${escapeHtml(value)}"`;
443
- }).join(" ");
444
- const openTag = attrsStr ? `<${tagName} ${attrsStr}>` : `<${tagName}>`;
445
- if (children.length === 0 && isSelfClosingTag(tagName)) {
446
- return attrsStr ? `<${tagName} ${attrsStr} />` : `<${tagName} />`;
447
- }
448
- const childrenStr = children.map((child) => {
449
- if (child.type === "text") {
450
- return child.value;
451
- }
452
- if (isHtmlElementNode(child)) {
453
- return htmlElementToString(child);
1292
+
1293
+ // src/extensions/marked-extensions/optimisticReferenceExtension.ts
1294
+ function createOptimisticReferenceExtension() {
1295
+ return {
1296
+ name: "optimisticReference",
1297
+ level: "inline",
1298
+ start(src) {
1299
+ return src.match(/!?\[/)?.index;
1300
+ },
1301
+ tokenizer(src) {
1302
+ const rule = /^(!?)\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\](?:\s*\[((?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*)\])?/;
1303
+ const match = rule.exec(src);
1304
+ if (match) {
1305
+ const fullMatch = match[0];
1306
+ if (src.length > fullMatch.length && src[fullMatch.length] === "(") {
1307
+ return void 0;
1308
+ }
1309
+ if (src.length > fullMatch.length && src[fullMatch.length] === ":") {
1310
+ return void 0;
1311
+ }
1312
+ const isImage = match[1] === "!";
1313
+ const text = match[2];
1314
+ const refRaw = match[3];
1315
+ if (text.startsWith("^")) {
1316
+ return void 0;
1317
+ }
1318
+ let identifier = "";
1319
+ let referenceType = "shortcut";
1320
+ if (refRaw !== void 0) {
1321
+ if (refRaw === "") {
1322
+ referenceType = "collapsed";
1323
+ identifier = text;
1324
+ } else {
1325
+ referenceType = "full";
1326
+ identifier = refRaw;
1327
+ }
1328
+ } else {
1329
+ referenceType = "shortcut";
1330
+ identifier = text;
1331
+ if (text.match(/^[ xX]$/)) {
1332
+ return void 0;
1333
+ }
1334
+ }
1335
+ return {
1336
+ type: "optimisticReference",
1337
+ raw: fullMatch,
1338
+ isImage,
1339
+ text,
1340
+ identifier: identifier.toLowerCase(),
1341
+ label: identifier,
1342
+ referenceType
1343
+ };
1344
+ }
1345
+ return void 0;
1346
+ },
1347
+ renderer() {
1348
+ return "";
454
1349
  }
455
- return "";
456
- }).join("");
457
- return `${openTag}${childrenStr}</${tagName}>`;
458
- }
459
- function isSelfClosingTag(tagName) {
460
- return ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"].includes(tagName.toLowerCase());
1350
+ };
461
1351
  }
462
- function escapeHtml(text) {
463
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1352
+
1353
+ // src/extensions/marked-extensions/mathExtension.ts
1354
+ function createBlockMathExtension() {
1355
+ return {
1356
+ name: "blockMath",
1357
+ level: "block",
1358
+ start(src) {
1359
+ const match = src.match(/^ {0,3}\$\$/m);
1360
+ return match?.index;
1361
+ },
1362
+ tokenizer(src) {
1363
+ const rule = /^ {0,3}\$\$([\s\S]*?)\$\$ *(?:\n+|$)/;
1364
+ const match = rule.exec(src);
1365
+ if (match) {
1366
+ return {
1367
+ type: "blockMath",
1368
+ raw: match[0],
1369
+ text: match[1].trim()
1370
+ };
1371
+ }
1372
+ return void 0;
1373
+ },
1374
+ renderer() {
1375
+ return "";
1376
+ }
1377
+ };
464
1378
  }
465
- function micromarkReferenceExtension() {
1379
+ function createInlineMathExtension() {
466
1380
  return {
467
- // 在 text 中使用 codes.rightSquareBracket 键覆盖 labelEnd
468
- text: {
469
- [codes.rightSquareBracket]: {
470
- name: "labelEnd",
471
- resolveAll: resolveAllLabelEnd,
472
- resolveTo: resolveToLabelEnd,
473
- tokenize: tokenizeLabelEnd,
474
- // 添加 add: 'before' 确保先被尝试
475
- add: "before"
1381
+ name: "inlineMath",
1382
+ level: "inline",
1383
+ start(src) {
1384
+ const index = src.indexOf("$");
1385
+ if (index === -1) return void 0;
1386
+ if (src[index + 1] === "$") return void 0;
1387
+ return index;
1388
+ },
1389
+ tokenizer(src) {
1390
+ const rule = /^\$(?!\$)((?:\\.|[^\\\n$])+?)\$(?!\d)/;
1391
+ const match = rule.exec(src);
1392
+ if (match) {
1393
+ return {
1394
+ type: "inlineMath",
1395
+ raw: match[0],
1396
+ text: match[1].trim()
1397
+ };
476
1398
  }
1399
+ return void 0;
1400
+ },
1401
+ renderer() {
1402
+ return "";
477
1403
  }
478
1404
  };
479
1405
  }
480
- function resolveAllLabelEnd(events) {
481
- let index = -1;
482
- const newEvents = [];
483
- while (++index < events.length) {
484
- const token = events[index][1];
485
- newEvents.push(events[index]);
486
- if (token.type === types.labelImage || token.type === types.labelLink || token.type === types.labelEnd) {
487
- const offset = token.type === types.labelImage ? 4 : 2;
488
- token.type = types.data;
489
- index += offset;
490
- }
491
- }
492
- if (events.length !== newEvents.length) {
493
- events.length = 0;
494
- events.push(...newEvents);
495
- }
496
- return events;
497
- }
498
- function resolveToLabelEnd(events, context) {
499
- let index = events.length;
500
- let offset = 0;
501
- let token;
502
- let open;
503
- let close;
504
- let media;
505
- while (index--) {
506
- token = events[index][1];
507
- if (open !== void 0) {
508
- if (token.type === types.link || token.type === types.labelLink && token._inactive) {
1406
+
1407
+ // src/extensions/marked-extensions/footnoteDefinitionExtension.ts
1408
+ function createFootnoteDefinitionExtension() {
1409
+ return {
1410
+ name: "footnoteDefinitionBlock",
1411
+ level: "block",
1412
+ start(src) {
1413
+ const match = src.match(/^ {0,3}\[\^[^\]]+\]:/m);
1414
+ return match?.index;
1415
+ },
1416
+ tokenizer(src) {
1417
+ const firstLineRule = /^ {0,3}\[\^([a-zA-Z0-9_-]+)\]:\s*(.*)/;
1418
+ const firstLineMatch = firstLineRule.exec(src);
1419
+ if (!firstLineMatch) return void 0;
1420
+ const identifier = firstLineMatch[1];
1421
+ let content = firstLineMatch[2];
1422
+ let raw = firstLineMatch[0];
1423
+ const remaining = src.slice(raw.length);
1424
+ const lines = remaining.split("\n");
1425
+ let lineIndex = 0;
1426
+ if (lines[0] === "" && remaining.startsWith("\n")) {
1427
+ lineIndex = 1;
1428
+ raw += "\n";
1429
+ content += "\n";
1430
+ }
1431
+ while (lineIndex < lines.length) {
1432
+ const line = lines[lineIndex];
1433
+ if (line.trim() === "") {
1434
+ let hasIndentedLineAfter = false;
1435
+ for (let j = lineIndex + 1; j < lines.length; j++) {
1436
+ const nextLine = lines[j];
1437
+ if (nextLine.trim() === "") continue;
1438
+ if (nextLine.match(/^( |\t)/)) {
1439
+ hasIndentedLineAfter = true;
1440
+ }
1441
+ break;
1442
+ }
1443
+ if (hasIndentedLineAfter) {
1444
+ raw += line + (lineIndex < lines.length - 1 ? "\n" : "");
1445
+ content += "\n" + line;
1446
+ lineIndex++;
1447
+ continue;
1448
+ } else {
1449
+ break;
1450
+ }
1451
+ }
1452
+ if (line.match(/^( |\t)/)) {
1453
+ raw += line + (lineIndex < lines.length - 1 ? "\n" : "");
1454
+ content += "\n" + line;
1455
+ lineIndex++;
1456
+ continue;
1457
+ }
1458
+ if (line.match(/^ {0,3}\[\^[^\]]+\]:/)) {
1459
+ break;
1460
+ }
509
1461
  break;
510
1462
  }
511
- if (events[index][0] === "enter" && token.type === types.labelLink) {
512
- token._inactive = true;
1463
+ const trimmedContent = content.replace(/\n+$/, "");
1464
+ return {
1465
+ type: "footnoteDefinitionBlock",
1466
+ raw,
1467
+ identifier,
1468
+ content: trimmedContent
1469
+ };
1470
+ },
1471
+ renderer() {
1472
+ return "";
1473
+ }
1474
+ };
1475
+ }
1476
+
1477
+ // src/extensions/marked-extensions/inlineHtmlExtension.ts
1478
+ var SELF_CLOSING_TAGS = /* @__PURE__ */ new Set([
1479
+ "area",
1480
+ "base",
1481
+ "br",
1482
+ "col",
1483
+ "embed",
1484
+ "hr",
1485
+ "img",
1486
+ "input",
1487
+ "link",
1488
+ "meta",
1489
+ "param",
1490
+ "source",
1491
+ "track",
1492
+ "wbr"
1493
+ ]);
1494
+ function createInlineHtmlExtension() {
1495
+ return {
1496
+ name: "inlineHtml",
1497
+ level: "inline",
1498
+ start(src) {
1499
+ const index = src.indexOf("<");
1500
+ if (index === -1) return void 0;
1501
+ const afterLt = src.slice(index + 1);
1502
+ if (!/^[a-zA-Z\/]/.test(afterLt)) return void 0;
1503
+ return index;
1504
+ },
1505
+ tokenizer(src) {
1506
+ const completeTagMatch = matchCompleteHtmlElement(src);
1507
+ if (completeTagMatch) {
1508
+ return {
1509
+ type: "inlineHtml",
1510
+ raw: completeTagMatch,
1511
+ text: completeTagMatch
1512
+ };
513
1513
  }
514
- } else if (close !== void 0) {
515
- if (events[index][0] === "enter" && (token.type === types.labelImage || token.type === types.labelLink) && !token._balanced) {
516
- open = index;
517
- if (token.type !== types.labelLink) {
518
- offset = 2;
519
- break;
520
- }
1514
+ const selfClosingMatch = matchSelfClosingTag(src);
1515
+ if (selfClosingMatch) {
1516
+ return {
1517
+ type: "inlineHtml",
1518
+ raw: selfClosingMatch,
1519
+ text: selfClosingMatch
1520
+ };
521
1521
  }
522
- } else if (token.type === types.labelEnd) {
523
- close = index;
1522
+ return void 0;
1523
+ },
1524
+ renderer() {
1525
+ return "";
1526
+ }
1527
+ };
1528
+ }
1529
+ function matchCompleteHtmlElement(src) {
1530
+ const openTagMatch = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*>/.exec(src);
1531
+ if (!openTagMatch) return null;
1532
+ const tagName = openTagMatch[1].toLowerCase();
1533
+ const openTag = openTagMatch[0];
1534
+ if (SELF_CLOSING_TAGS.has(tagName)) {
1535
+ return openTag;
1536
+ }
1537
+ const afterOpenTag = src.slice(openTag.length);
1538
+ let depth = 1;
1539
+ let pos = 0;
1540
+ const openPattern = new RegExp(`<${tagName}(?:\\s[^>]*)?>`, "gi");
1541
+ const closePattern = new RegExp(`</${tagName}>`, "gi");
1542
+ while (depth > 0 && pos < afterOpenTag.length) {
1543
+ openPattern.lastIndex = pos;
1544
+ closePattern.lastIndex = pos;
1545
+ const nextOpen = openPattern.exec(afterOpenTag);
1546
+ const nextClose = closePattern.exec(afterOpenTag);
1547
+ if (!nextClose) {
1548
+ return null;
524
1549
  }
1550
+ if (nextOpen && nextOpen.index < nextClose.index) {
1551
+ depth++;
1552
+ pos = nextOpen.index + nextOpen[0].length;
1553
+ } else {
1554
+ depth--;
1555
+ pos = nextClose.index + nextClose[0].length;
1556
+ }
1557
+ }
1558
+ if (depth === 0) {
1559
+ return src.slice(0, openTag.length + pos);
1560
+ }
1561
+ return null;
1562
+ }
1563
+ function matchSelfClosingTag(src) {
1564
+ const explicitSelfClosing = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*\/>/.exec(src);
1565
+ if (explicitSelfClosing) {
1566
+ return explicitSelfClosing[0];
525
1567
  }
526
- if (open === void 0 || close === void 0) {
527
- return events;
1568
+ const implicitSelfClosing = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*>/.exec(src);
1569
+ if (implicitSelfClosing && SELF_CLOSING_TAGS.has(implicitSelfClosing[1].toLowerCase())) {
1570
+ return implicitSelfClosing[0];
528
1571
  }
529
- const group = {
530
- type: events[open][1].type === types.labelLink ? types.link : types.image,
531
- start: { ...events[open][1].start },
532
- end: { ...events[events.length - 1][1].end }
1572
+ return null;
1573
+ }
1574
+ function transformBlockMath(token) {
1575
+ return {
1576
+ type: "math",
1577
+ value: token.text,
1578
+ meta: null
533
1579
  };
534
- const label = {
535
- type: types.label,
536
- start: { ...events[open][1].start },
537
- end: { ...events[close][1].end }
1580
+ }
1581
+ function transformFootnoteDefinitionBlock(token, ctx) {
1582
+ const children = ctx.parseFootnoteContent(token.content);
1583
+ return {
1584
+ type: "footnoteDefinition",
1585
+ identifier: token.identifier,
1586
+ label: token.identifier,
1587
+ children
538
1588
  };
539
- const text = {
540
- type: types.labelText,
541
- start: { ...events[open + offset + 2][1].end },
542
- end: { ...events[close - 2][1].start }
1589
+ }
1590
+ function transformExplicitDefinition(token) {
1591
+ if (!token.identifier || !token.url) return null;
1592
+ return {
1593
+ type: "definition",
1594
+ identifier: token.identifier,
1595
+ label: token.identifier,
1596
+ url: token.url,
1597
+ title: token.title ?? null
543
1598
  };
544
- media = [
545
- ["enter", group, context],
546
- ["enter", label, context]
547
- ];
548
- media.push(...events.slice(open + 1, open + offset + 3));
549
- media.push(["enter", text, context]);
550
- media.push(...events.slice(open + offset + 4, close - 3));
551
- media.push(
552
- ["exit", text, context],
553
- events[close - 2],
554
- events[close - 1],
555
- ["exit", label, context]
556
- );
557
- media.push(...events.slice(close + 1));
558
- media.push(["exit", group, context]);
559
- events.splice(open, events.length - open, ...media);
560
- return events;
561
- }
562
- function tokenizeLabelEnd(effects, ok, nok) {
563
- const self = this;
564
- let index = self.events.length;
565
- let labelStart;
566
- while (index--) {
567
- if ((self.events[index][1].type === types.labelImage || self.events[index][1].type === types.labelLink) && !self.events[index][1]._balanced) {
568
- labelStart = self.events[index][1];
569
- break;
570
- }
571
- }
572
- return start;
573
- function start(code) {
574
- if (!labelStart) {
575
- return nok(code);
576
- }
577
- if (labelStart._inactive) {
578
- return labelEndNok(code);
579
- }
580
- if (labelStart.type === types.labelLink) {
581
- const labelText = self.sliceSerialize({ start: labelStart.end, end: self.now() });
582
- if (labelText.startsWith("^")) {
583
- return nok(code);
584
- }
585
- }
586
- effects.enter(types.labelEnd);
587
- effects.enter(types.labelMarker);
588
- effects.consume(code);
589
- effects.exit(types.labelMarker);
590
- effects.exit(types.labelEnd);
591
- return after;
592
- }
593
- function after(code) {
594
- if (code === codes.leftParenthesis) {
595
- return effects.attempt(
596
- {
597
- tokenize: tokenizeResource,
598
- partial: false
599
- },
600
- labelEndOk,
601
- labelEndNok
602
- // 修复:resource 解析失败时返回 nok
603
- )(code);
604
- }
605
- if (code === codes.leftSquareBracket) {
606
- return effects.attempt(
1599
+ }
1600
+ function transformDef(token) {
1601
+ if (token.tag.startsWith("^")) {
1602
+ const footnoteId = token.tag.slice(1);
1603
+ return {
1604
+ type: "footnoteDefinition",
1605
+ identifier: footnoteId,
1606
+ label: footnoteId,
1607
+ children: [
607
1608
  {
608
- tokenize: tokenizeReferenceFull,
609
- partial: false
610
- },
611
- labelEndOk,
612
- referenceNotFull
613
- // 修改:即使不是 full reference,也尝试 collapsed
614
- )(code);
615
- }
616
- return labelEndOk(code);
1609
+ type: "paragraph",
1610
+ children: [{ type: "text", value: token.href }]
1611
+ }
1612
+ ]
1613
+ };
1614
+ }
1615
+ return {
1616
+ type: "definition",
1617
+ identifier: token.tag,
1618
+ label: token.tag,
1619
+ url: token.href,
1620
+ title: token.title ?? null
1621
+ };
1622
+ }
1623
+ function transformContainer(token, ctx) {
1624
+ const attributes = {};
1625
+ const attrRegex = /([a-zA-Z0-9_-]+)=?("([^"]*)"|'([^']*)'|([^ ]*))?/g;
1626
+ let match;
1627
+ while ((match = attrRegex.exec(token.attrs)) !== null) {
1628
+ attributes[match[1]] = match[3] || match[4] || match[5] || "";
1629
+ }
1630
+ const children = ctx.transformTokensWithPosition(token.tokens);
1631
+ return {
1632
+ type: "containerDirective",
1633
+ name: token.name,
1634
+ attributes,
1635
+ children
1636
+ };
1637
+ }
1638
+ function transformFootnoteDefToken(token, ctx) {
1639
+ return {
1640
+ type: "footnoteDefinition",
1641
+ identifier: token.identifier,
1642
+ label: token.identifier,
1643
+ children: [
1644
+ {
1645
+ type: "paragraph",
1646
+ children: ctx.transformInline(token.tokens)
1647
+ }
1648
+ ]
1649
+ };
1650
+ }
1651
+ function transformHeading(token, ctx) {
1652
+ return {
1653
+ type: "heading",
1654
+ depth: token.depth,
1655
+ children: ctx.transformInline(token.tokens)
1656
+ };
1657
+ }
1658
+ function transformParagraph(token, ctx) {
1659
+ return {
1660
+ type: "paragraph",
1661
+ children: ctx.transformInline(token.tokens)
1662
+ };
1663
+ }
1664
+ function transformCode(token) {
1665
+ return {
1666
+ type: "code",
1667
+ lang: token.lang || null,
1668
+ meta: null,
1669
+ // 对齐 micromark 输出
1670
+ value: token.text
1671
+ };
1672
+ }
1673
+ function transformBlockquote(token, ctx) {
1674
+ const children = ctx.transformTokens(token.tokens);
1675
+ return {
1676
+ type: "blockquote",
1677
+ children
1678
+ };
1679
+ }
1680
+ function transformList(token, ctx) {
1681
+ const children = token.items.map((item) => ({
1682
+ type: "listItem",
1683
+ spread: item.loose,
1684
+ checked: item.checked ?? null,
1685
+ // 对齐 micromark 输出(GFM 任务列表)
1686
+ children: ctx.transformTokens(item.tokens)
1687
+ }));
1688
+ return {
1689
+ type: "list",
1690
+ ordered: token.ordered,
1691
+ start: token.ordered ? token.start || 1 : null,
1692
+ // 对齐 micromark:有序列表有 start,无序列表为 null
1693
+ spread: token.loose,
1694
+ children
1695
+ };
1696
+ }
1697
+ function transformTable(token, ctx) {
1698
+ const headerCells = token.header.map((cell) => ({
1699
+ type: "tableCell",
1700
+ children: ctx.transformInline(cell.tokens)
1701
+ }));
1702
+ const bodyRows = token.rows.map((row) => ({
1703
+ type: "tableRow",
1704
+ children: row.map((cell) => ({
1705
+ type: "tableCell",
1706
+ children: ctx.transformInline(cell.tokens)
1707
+ }))
1708
+ }));
1709
+ return {
1710
+ type: "table",
1711
+ align: token.align,
1712
+ children: [{ type: "tableRow", children: headerCells }, ...bodyRows]
1713
+ };
1714
+ }
1715
+ function transformHr() {
1716
+ return { type: "thematicBreak" };
1717
+ }
1718
+ function transformHtml(token) {
1719
+ return {
1720
+ type: "html",
1721
+ value: token.text
1722
+ };
1723
+ }
1724
+ function transformTextBlock(token, ctx) {
1725
+ if (token.tokens) {
1726
+ return {
1727
+ type: "paragraph",
1728
+ children: ctx.transformInline(token.tokens)
1729
+ };
1730
+ }
1731
+ return {
1732
+ type: "paragraph",
1733
+ children: [{ type: "text", value: token.text }]
1734
+ };
1735
+ }
1736
+ function transformInlineMath(token) {
1737
+ return {
1738
+ type: "inlineMath",
1739
+ value: token.text
1740
+ };
1741
+ }
1742
+ function transformOptimisticReference(token, ctx) {
1743
+ if (token.isImage) {
1744
+ return {
1745
+ type: "imageReference",
1746
+ identifier: token.identifier,
1747
+ label: token.label,
1748
+ referenceType: token.referenceType,
1749
+ alt: token.text
1750
+ };
617
1751
  }
618
- function referenceNotFull(code) {
619
- return effects.attempt(
620
- {
621
- tokenize: tokenizeReferenceCollapsed,
622
- partial: false
623
- },
624
- labelEndOk,
625
- labelEndOk
626
- // 修改:即使失败也返回 ok
627
- )(code);
628
- }
629
- function labelEndOk(code) {
630
- return ok(code);
631
- }
632
- function labelEndNok(code) {
633
- labelStart._balanced = true;
634
- return nok(code);
635
- }
636
- }
637
- function tokenizeResource(effects, ok, nok) {
638
- return resourceStart;
639
- function resourceStart(code) {
640
- if (code !== codes.leftParenthesis) {
641
- return nok(code);
642
- }
643
- effects.enter(types.resource);
644
- effects.enter(types.resourceMarker);
645
- effects.consume(code);
646
- effects.exit(types.resourceMarker);
647
- return resourceBefore;
648
- }
649
- function resourceBefore(code) {
650
- return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceOpen)(code) : resourceOpen(code);
651
- }
652
- function resourceOpen(code) {
653
- if (code === codes.rightParenthesis) {
654
- return resourceEnd(code);
655
- }
656
- return factoryDestination(
657
- effects,
658
- resourceDestinationAfter,
659
- resourceDestinationMissing,
660
- types.resourceDestination,
661
- types.resourceDestinationLiteral,
662
- types.resourceDestinationLiteralMarker,
663
- types.resourceDestinationRaw,
664
- types.resourceDestinationString,
665
- constants.linkResourceDestinationBalanceMax
666
- )(code);
667
- }
668
- function resourceDestinationAfter(code) {
669
- return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceBetween)(code) : resourceEnd(code);
670
- }
671
- function resourceDestinationMissing(code) {
672
- return nok(code);
673
- }
674
- function resourceBetween(code) {
675
- if (code === codes.quotationMark || code === codes.apostrophe || code === codes.leftParenthesis) {
676
- return factoryTitle(
677
- effects,
678
- resourceTitleAfter,
679
- nok,
680
- types.resourceTitle,
681
- types.resourceTitleMarker,
682
- types.resourceTitleString
683
- )(code);
684
- }
685
- return resourceEnd(code);
686
- }
687
- function resourceTitleAfter(code) {
688
- return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceEnd)(code) : resourceEnd(code);
689
- }
690
- function resourceEnd(code) {
691
- if (code === codes.rightParenthesis) {
692
- effects.enter(types.resourceMarker);
693
- effects.consume(code);
694
- effects.exit(types.resourceMarker);
695
- effects.exit(types.resource);
696
- return ok;
697
- }
698
- return nok(code);
699
- }
700
- }
701
- function tokenizeReferenceFull(effects, ok, nok) {
702
- const self = this;
703
- return referenceFull;
704
- function referenceFull(code) {
705
- if (code !== codes.leftSquareBracket) {
706
- return nok(code);
707
- }
708
- return factoryLabel.call(
709
- self,
710
- effects,
711
- referenceFullAfter,
712
- referenceFullMissing,
713
- types.reference,
714
- types.referenceMarker,
715
- types.referenceString
716
- )(code);
717
- }
718
- function referenceFullAfter(code) {
719
- return ok(code);
720
- }
721
- function referenceFullMissing(code) {
722
- return nok(code);
723
- }
724
- }
725
- function tokenizeReferenceCollapsed(effects, ok, nok) {
726
- return referenceCollapsedStart;
727
- function referenceCollapsedStart(code) {
728
- if (code !== codes.leftSquareBracket) {
729
- return nok(code);
730
- }
731
- effects.enter(types.reference);
732
- effects.enter(types.referenceMarker);
733
- effects.consume(code);
734
- effects.exit(types.referenceMarker);
735
- return referenceCollapsedOpen;
736
- }
737
- function referenceCollapsedOpen(code) {
738
- if (code === codes.rightSquareBracket) {
739
- effects.enter(types.referenceMarker);
740
- effects.consume(code);
741
- effects.exit(types.referenceMarker);
742
- effects.exit(types.reference);
743
- return ok;
744
- }
745
- return nok(code);
746
- }
747
- }
748
- function gfmFootnoteIncremental() {
749
- const original = gfmFootnote();
1752
+ const labelChildren = ctx.transformInline(new Lexer().inlineTokens(token.text));
750
1753
  return {
751
- ...original,
752
- text: {
753
- ...original.text,
754
- // 覆盖 text[91] (`[` 的处理) - 这是脚注引用解析的起点
755
- [codes.leftSquareBracket]: {
756
- ...original.text[codes.leftSquareBracket],
757
- tokenize: tokenizeGfmFootnoteCallIncremental
758
- },
759
- // 覆盖 text[93] (`]` 的处理) - 用于处理 ![^1] 这样的情况
760
- [codes.rightSquareBracket]: {
761
- ...original.text[codes.rightSquareBracket],
762
- tokenize: tokenizePotentialGfmFootnoteCallIncremental
763
- }
764
- }
1754
+ type: "linkReference",
1755
+ identifier: token.identifier,
1756
+ label: token.label,
1757
+ referenceType: token.referenceType,
1758
+ children: labelChildren.length ? labelChildren : [{ type: "text", value: token.text }]
765
1759
  };
766
1760
  }
767
- function tokenizeGfmFootnoteCallIncremental(effects, ok, nok) {
768
- let size = 0;
769
- let data = false;
770
- return start;
771
- function start(code) {
772
- if (code !== codes.leftSquareBracket) {
773
- return nok(code);
774
- }
775
- effects.enter("gfmFootnoteCall");
776
- effects.enter("gfmFootnoteCallLabelMarker");
777
- effects.consume(code);
778
- effects.exit("gfmFootnoteCallLabelMarker");
779
- return callStart;
780
- }
781
- function callStart(code) {
782
- if (code !== codes.caret) {
783
- return nok(code);
784
- }
785
- effects.enter("gfmFootnoteCallMarker");
786
- effects.consume(code);
787
- effects.exit("gfmFootnoteCallMarker");
788
- effects.enter("gfmFootnoteCallString");
789
- const token = effects.enter("chunkString");
790
- token.contentType = "string";
791
- return callData;
792
- }
793
- function callData(code) {
794
- if (
795
- // 太长
796
- size > constants.linkReferenceSizeMax || // 右括号但没有数据
797
- code === codes.rightSquareBracket && !data || // EOF、换行、空格、制表符、左括号不支持
798
- code === codes.eof || code === codes.leftSquareBracket || markdownLineEndingOrSpace(code)
799
- ) {
800
- return nok(code);
801
- }
802
- if (code === codes.rightSquareBracket) {
803
- effects.exit("chunkString");
804
- effects.exit("gfmFootnoteCallString");
805
- effects.enter("gfmFootnoteCallLabelMarker");
806
- effects.consume(code);
807
- effects.exit("gfmFootnoteCallLabelMarker");
808
- effects.exit("gfmFootnoteCall");
809
- return ok;
810
- }
811
- if (!markdownLineEndingOrSpace(code)) {
812
- data = true;
813
- }
814
- size++;
815
- effects.consume(code);
816
- return code === codes.backslash ? callEscape : callData;
817
- }
818
- function callEscape(code) {
819
- if (code === codes.leftSquareBracket || code === codes.backslash || code === codes.rightSquareBracket) {
820
- effects.consume(code);
821
- size++;
822
- return callData;
823
- }
824
- return callData(code);
825
- }
826
- }
827
- function tokenizePotentialGfmFootnoteCallIncremental(effects, ok, nok) {
828
- const self = this;
829
- let index = self.events.length;
830
- let labelStart;
831
- while (index--) {
832
- const token = self.events[index][1];
833
- if (token.type === "labelImage") {
834
- labelStart = token;
835
- break;
836
- }
837
- if (token.type === "gfmFootnoteCall" || token.type === "labelLink" || token.type === "label" || token.type === "image" || token.type === "link") {
838
- break;
839
- }
840
- }
841
- return start;
842
- function start(code) {
843
- if (code !== codes.rightSquareBracket) {
844
- return nok(code);
845
- }
846
- if (!labelStart || !labelStart._balanced) {
847
- return nok(code);
848
- }
849
- const id = normalizeIdentifier(
850
- self.sliceSerialize({
851
- start: labelStart.end,
852
- end: self.now()
853
- })
854
- );
855
- if (id.codePointAt(0) !== codes.caret) {
856
- return nok(code);
857
- }
858
- effects.enter("gfmFootnoteCallLabelMarker");
859
- effects.consume(code);
860
- effects.exit("gfmFootnoteCallLabelMarker");
861
- return ok(code);
1761
+ function transformLink(token, ctx) {
1762
+ if (token.text.startsWith("^") && token.text.length > 1) {
1763
+ const footnoteId = token.text.slice(1);
1764
+ return {
1765
+ type: "footnoteReference",
1766
+ identifier: footnoteId,
1767
+ label: footnoteId
1768
+ };
862
1769
  }
1770
+ return {
1771
+ type: "link",
1772
+ url: token.href,
1773
+ title: token.title || null,
1774
+ // 对齐 micromark 输出
1775
+ children: ctx.transformInline(token.tokens)
1776
+ };
863
1777
  }
864
-
865
- // src/detector/index.ts
866
- var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
867
- var RE_EMPTY_LINE = /^\s*$/;
868
- var RE_HEADING = /^#{1,6}\s/;
869
- var RE_THEMATIC_BREAK = /^(\*{3,}|-{3,}|_{3,})\s*$/;
870
- var RE_BLOCKQUOTE = /^\s{0,3}>/;
871
- var RE_HTML_BLOCK_1 = /^\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\?|!\[CDATA\[)/i;
872
- var RE_HTML_BLOCK_2 = /^\s{0,3}<\/?[a-zA-Z][a-zA-Z0-9-]*(\s|>|$)/;
873
- var RE_TABLE_DELIMITER = /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/;
874
- var RE_ESCAPE_SPECIAL = /[.*+?^${}()|[\]\\]/g;
875
- var RE_FOOTNOTE_DEFINITION = /^\[\^([^\]]+)\]:\s/;
876
- var RE_FOOTNOTE_CONTINUATION = /^(?: |\t)/;
877
- var fenceEndPatternCache = /* @__PURE__ */ new Map();
878
- var containerPatternCache = /* @__PURE__ */ new Map();
879
- function detectFenceStart(line) {
880
- const match = line.match(RE_FENCE_START);
881
- if (match) {
882
- const fence = match[2];
883
- const char = fence[0];
884
- return { char, length: fence.length };
885
- }
886
- return null;
1778
+ function transformImage(token) {
1779
+ return {
1780
+ type: "image",
1781
+ url: token.href,
1782
+ title: token.title || null,
1783
+ // 对齐 micromark 输出
1784
+ alt: token.text
1785
+ };
887
1786
  }
888
- function detectFenceEnd(line, context) {
889
- if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
890
- return false;
1787
+ function transformText(token) {
1788
+ const results = [];
1789
+ const text = token.text;
1790
+ const footnoteRegex = /\[\^([a-zA-Z0-9_-]+)\]/g;
1791
+ let lastIndex = 0;
1792
+ let match;
1793
+ while ((match = footnoteRegex.exec(text)) !== null) {
1794
+ if (match.index > lastIndex) {
1795
+ results.push({
1796
+ type: "text",
1797
+ value: text.substring(lastIndex, match.index)
1798
+ });
1799
+ }
1800
+ results.push({
1801
+ type: "footnoteReference",
1802
+ identifier: match[1],
1803
+ label: match[1]
1804
+ });
1805
+ lastIndex = match.index + match[0].length;
891
1806
  }
892
- const cacheKey = `${context.fenceChar}-${context.fenceLength}`;
893
- let pattern = fenceEndPatternCache.get(cacheKey);
894
- if (!pattern) {
895
- pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
896
- fenceEndPatternCache.set(cacheKey, pattern);
1807
+ if (lastIndex < text.length) {
1808
+ results.push({
1809
+ type: "text",
1810
+ value: text.substring(lastIndex)
1811
+ });
897
1812
  }
898
- return pattern.test(line);
899
- }
900
- function isEmptyLine(line) {
901
- return RE_EMPTY_LINE.test(line);
902
- }
903
- function isHeading(line) {
904
- return RE_HEADING.test(line);
1813
+ return results;
905
1814
  }
906
- function isThematicBreak(line) {
907
- return RE_THEMATIC_BREAK.test(line.trim());
1815
+ function transformStrong(token, ctx) {
1816
+ return {
1817
+ type: "strong",
1818
+ children: ctx.transformInline(token.tokens)
1819
+ };
908
1820
  }
909
- function isListItemStart(line) {
910
- const hasListMarker = /^(\s*)([-*+]|\d{1,9}[.)])/.test(line);
911
- if (!hasListMarker) {
912
- return null;
913
- }
914
- const match = line.match(/^(\s*)([-*+]|\d{1,9}[.)])(.*)/);
915
- if (match) {
916
- const indent = match[1].length;
917
- const marker = match[2];
918
- const rest = match[3];
919
- if (rest.trim()) {
920
- const isOrdered = /^\d{1,9}[.)]/.test(marker);
921
- return { ordered: isOrdered, indent };
922
- }
923
- if (/^\s+$/.test(rest)) {
924
- const isOrdered = /^\d{1,9}[.)]/.test(marker);
925
- return { ordered: isOrdered, indent };
926
- }
927
- }
928
- return null;
1821
+ function transformEmphasis(token, ctx) {
1822
+ return {
1823
+ type: "emphasis",
1824
+ children: ctx.transformInline(token.tokens)
1825
+ };
929
1826
  }
930
- function isBlockquoteStart(line) {
931
- return RE_BLOCKQUOTE.test(line);
1827
+ function transformCodespan(token) {
1828
+ return {
1829
+ type: "inlineCode",
1830
+ value: token.text
1831
+ };
932
1832
  }
933
- function isHtmlBlock(line) {
934
- return RE_HTML_BLOCK_1.test(line) || RE_HTML_BLOCK_2.test(line);
1833
+ function transformBreak() {
1834
+ return { type: "break" };
935
1835
  }
936
- function isTableDelimiter(line) {
937
- return RE_TABLE_DELIMITER.test(line.trim());
1836
+ function transformDelete(token, ctx) {
1837
+ return {
1838
+ type: "delete",
1839
+ children: ctx.transformInline(token.tokens)
1840
+ };
938
1841
  }
939
- function isFootnoteDefinitionStart(line) {
940
- return RE_FOOTNOTE_DEFINITION.test(line);
1842
+ function transformInlineHtml(token) {
1843
+ const parsed = parseHtmlFragment(token.text);
1844
+ if (parsed.length > 0) {
1845
+ return parsed;
1846
+ }
1847
+ return { type: "text", value: token.text };
941
1848
  }
942
- function isFootnoteContinuation(line) {
943
- return RE_FOOTNOTE_CONTINUATION.test(line);
1849
+ function isTokenType(token, type) {
1850
+ return token.type === type;
944
1851
  }
945
- function detectContainer(line, config) {
946
- const marker = config?.marker || ":";
947
- const minLength = config?.minMarkerLength || 3;
948
- const cacheKey = `${marker}-${minLength}`;
949
- let pattern = containerPatternCache.get(cacheKey);
950
- if (!pattern) {
951
- const escapedMarker = marker.replace(RE_ESCAPE_SPECIAL, "\\$&");
952
- pattern = new RegExp(
953
- `^(\\s*)(${escapedMarker}{${minLength},})(?:\\s*(\\w[\\w-]*))?(?:\\{[^}]*\\})?(?:\\s+(.*))?\\s*$`
954
- );
955
- containerPatternCache.set(cacheKey, pattern);
956
- }
957
- const match = line.match(pattern);
958
- if (!match) {
1852
+ var builtinBlockTransformers = {
1853
+ blockMath: (token) => {
1854
+ if (isTokenType(token, "blockMath")) return transformBlockMath(token);
1855
+ return null;
1856
+ },
1857
+ footnoteDefinitionBlock: (token, ctx) => {
1858
+ if (isTokenType(token, "footnoteDefinitionBlock"))
1859
+ return transformFootnoteDefinitionBlock(token, ctx);
1860
+ return null;
1861
+ },
1862
+ explicitDefinition: (token) => {
1863
+ if (isTokenType(token, "explicitDefinition"))
1864
+ return transformExplicitDefinition(token);
1865
+ return null;
1866
+ },
1867
+ def: (token) => {
1868
+ if (isTokenType(token, "def")) return transformDef(token);
1869
+ return null;
1870
+ },
1871
+ container: (token, ctx) => {
1872
+ if (isTokenType(token, "container")) return transformContainer(token, ctx);
1873
+ return null;
1874
+ },
1875
+ footnoteDefinition: (token, ctx) => {
1876
+ if (isTokenType(token, "footnoteDefinition"))
1877
+ return transformFootnoteDefToken(token, ctx);
1878
+ return null;
1879
+ },
1880
+ heading: (token, ctx) => {
1881
+ if (isTokenType(token, "heading")) return transformHeading(token, ctx);
1882
+ return null;
1883
+ },
1884
+ paragraph: (token, ctx) => {
1885
+ if (isTokenType(token, "paragraph")) return transformParagraph(token, ctx);
1886
+ return null;
1887
+ },
1888
+ code: (token) => {
1889
+ if (isTokenType(token, "code")) return transformCode(token);
1890
+ return null;
1891
+ },
1892
+ blockquote: (token, ctx) => {
1893
+ if (isTokenType(token, "blockquote")) return transformBlockquote(token, ctx);
1894
+ return null;
1895
+ },
1896
+ list: (token, ctx) => {
1897
+ if (isTokenType(token, "list")) return transformList(token, ctx);
1898
+ return null;
1899
+ },
1900
+ table: (token, ctx) => {
1901
+ if (isTokenType(token, "table")) return transformTable(token, ctx);
1902
+ return null;
1903
+ },
1904
+ hr: () => transformHr(),
1905
+ html: (token) => {
1906
+ if (isTokenType(token, "html")) return transformHtml(token);
1907
+ return null;
1908
+ },
1909
+ space: () => null,
1910
+ text: (token, ctx) => {
1911
+ if (isTokenType(token, "text")) return transformTextBlock(token, ctx);
959
1912
  return null;
960
1913
  }
961
- const markerLength = match[2].length;
962
- const name = match[3] || "";
963
- const isEnd = !name && !match[4];
964
- if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {
965
- if (!config.allowedNames.includes(name)) {
966
- return null;
967
- }
968
- }
969
- return { name, markerLength, isEnd };
970
- }
971
- function detectContainerEnd(line, context, config) {
972
- if (!context.inContainer || !context.containerMarkerLength) {
973
- return false;
1914
+ };
1915
+ var builtinInlineTransformers = {
1916
+ inlineMath: (token) => {
1917
+ if (isTokenType(token, "inlineMath")) return transformInlineMath(token);
1918
+ return null;
1919
+ },
1920
+ optimisticReference: (token, ctx) => {
1921
+ if (isTokenType(token, "optimisticReference"))
1922
+ return transformOptimisticReference(token, ctx);
1923
+ return null;
1924
+ },
1925
+ link: (token, ctx) => {
1926
+ if (isTokenType(token, "link")) return transformLink(token, ctx);
1927
+ return null;
1928
+ },
1929
+ image: (token) => {
1930
+ if (isTokenType(token, "image")) return transformImage(token);
1931
+ return null;
1932
+ },
1933
+ text: (token) => {
1934
+ if (isTokenType(token, "text")) return transformText(token);
1935
+ return null;
1936
+ },
1937
+ escape: (token) => {
1938
+ if (isTokenType(token, "escape")) return transformText(token);
1939
+ return null;
1940
+ },
1941
+ strong: (token, ctx) => {
1942
+ if (isTokenType(token, "strong")) return transformStrong(token, ctx);
1943
+ return null;
1944
+ },
1945
+ em: (token, ctx) => {
1946
+ if (isTokenType(token, "em")) return transformEmphasis(token, ctx);
1947
+ return null;
1948
+ },
1949
+ codespan: (token) => {
1950
+ if (isTokenType(token, "codespan")) return transformCodespan(token);
1951
+ return null;
1952
+ },
1953
+ br: () => transformBreak(),
1954
+ del: (token, ctx) => {
1955
+ if (isTokenType(token, "del")) return transformDelete(token, ctx);
1956
+ return null;
1957
+ },
1958
+ inlineHtml: (token) => {
1959
+ if (isTokenType(token, "inlineHtml")) return transformInlineHtml(token);
1960
+ return null;
974
1961
  }
975
- const result = detectContainer(line, config);
976
- if (!result) {
977
- return false;
1962
+ };
1963
+ function transformBlockToken(token, ctx) {
1964
+ const tokenType = token.type;
1965
+ if (ctx.customBlockTransformers?.[tokenType]) {
1966
+ const result = ctx.customBlockTransformers[tokenType](token, ctx);
1967
+ if (result !== void 0) return result;
1968
+ }
1969
+ if (builtinBlockTransformers[tokenType]) {
1970
+ const result = builtinBlockTransformers[tokenType](token, ctx);
1971
+ if (result !== void 0) return result;
1972
+ }
1973
+ if ("text" in token && typeof token.text === "string") {
1974
+ const paragraph = {
1975
+ type: "paragraph",
1976
+ children: [{ type: "text", value: token.text }]
1977
+ };
1978
+ return paragraph;
978
1979
  }
979
- return result.isEnd && result.markerLength >= context.containerMarkerLength;
1980
+ return null;
980
1981
  }
981
- function isBlockBoundary(prevLine, currentLine, context) {
982
- if (context.inFencedCode) {
983
- return detectFenceEnd(currentLine, context);
984
- }
985
- if (isEmptyLine(prevLine) && !isEmptyLine(currentLine)) {
986
- return true;
1982
+ function transformInlineToken(token, ctx) {
1983
+ const tokenType = token.type;
1984
+ if (ctx.customInlineTransformers?.[tokenType]) {
1985
+ const result = ctx.customInlineTransformers[tokenType](token, ctx);
1986
+ if (result !== void 0) return result;
987
1987
  }
988
- if (isHeading(currentLine) && !isEmptyLine(prevLine)) {
989
- return true;
1988
+ if (builtinInlineTransformers[tokenType]) {
1989
+ const result = builtinInlineTransformers[tokenType](token, ctx);
1990
+ if (result !== void 0) return result;
990
1991
  }
991
- if (isThematicBreak(currentLine)) {
992
- return true;
1992
+ if ("text" in token && typeof token.text === "string") {
1993
+ const text = { type: "text", value: token.text };
1994
+ return text;
993
1995
  }
994
- if (detectFenceStart(currentLine)) {
995
- return true;
996
- }
997
- return false;
998
- }
999
- function createInitialContext() {
1000
- return {
1001
- inFencedCode: false,
1002
- listDepth: 0,
1003
- blockquoteDepth: 0,
1004
- inContainer: false,
1005
- containerDepth: 0,
1006
- inList: false,
1007
- inFootnote: false,
1008
- footnoteIdentifier: void 0
1009
- };
1996
+ return null;
1010
1997
  }
1011
- function isListContinuation(line, listIndent) {
1012
- if (isEmptyLine(line)) {
1013
- return true;
1998
+
1999
+ // src/parser/ast/MarkedAstBuildter.ts
2000
+ var MarkedAstBuilder = class {
2001
+ constructor(options = {}) {
2002
+ this.options = options;
2003
+ this.containerConfig = typeof options.containers === "object" ? options.containers : options.containers === true ? {} : void 0;
2004
+ this.htmlTreeOptions = typeof options.htmlTree === "object" ? options.htmlTree : options.htmlTree === true ? {} : void 0;
2005
+ if (options.plugins) {
2006
+ this.userExtensions.push(...extractMarkedExtensions(options.plugins));
2007
+ }
2008
+ if (options.markedExtensions) {
2009
+ this.userExtensions.push(...options.markedExtensions);
2010
+ }
2011
+ this.transformContext = {
2012
+ transformTokens: this.transformTokens.bind(this),
2013
+ transformTokensWithPosition: this.transformTokensWithPosition.bind(this),
2014
+ transformInline: this.transformInline.bind(this),
2015
+ parseFootnoteContent: this.parseFootnoteContent.bind(this)
2016
+ };
1014
2017
  }
1015
- const contentIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
1016
- return contentIndent > listIndent;
1017
- }
1018
- function updateContext(line, context, containerConfig) {
1019
- const newContext = { ...context };
1020
- const containerCfg = containerConfig === true ? {} : containerConfig === false ? void 0 : containerConfig;
1021
- if (context.inFencedCode) {
1022
- if (detectFenceEnd(line, context)) {
1023
- newContext.inFencedCode = false;
1024
- newContext.fenceChar = void 0;
1025
- newContext.fenceLength = void 0;
1026
- }
1027
- return newContext;
1028
- }
1029
- const fence = detectFenceStart(line);
1030
- if (fence) {
1031
- newContext.inFencedCode = true;
1032
- newContext.fenceChar = fence.char;
1033
- newContext.fenceLength = fence.length;
1034
- return newContext;
1035
- }
1036
- if (containerCfg !== void 0) {
1037
- if (context.inContainer) {
1038
- if (detectContainerEnd(line, context, containerCfg)) {
1039
- newContext.containerDepth = context.containerDepth - 1;
1040
- if (newContext.containerDepth === 0) {
1041
- newContext.inContainer = false;
1042
- newContext.containerMarkerLength = void 0;
1043
- newContext.containerName = void 0;
1044
- }
1045
- return newContext;
1046
- }
1047
- const nested = detectContainer(line, containerCfg);
1048
- if (nested && !nested.isEnd) {
1049
- newContext.containerDepth = context.containerDepth + 1;
1050
- return newContext;
1051
- }
1052
- return newContext;
1053
- } else {
1054
- const container = detectContainer(line, containerCfg);
1055
- if (container && !container.isEnd) {
1056
- newContext.inContainer = true;
1057
- newContext.containerMarkerLength = container.markerLength;
1058
- newContext.containerName = container.name;
1059
- newContext.containerDepth = 1;
1060
- return newContext;
2018
+ containerConfig;
2019
+ htmlTreeOptions;
2020
+ globalLinks = {};
2021
+ /** 用户传入的 marked 扩展 */
2022
+ userExtensions = [];
2023
+ /** 转换上下文(用于递归转换) */
2024
+ transformContext;
2025
+ parse(text) {
2026
+ const normalizedText = text.replace(/[\u00A0\u200b\u202f]/g, " ");
2027
+ const optimisticRefExt = createOptimisticReferenceExtension();
2028
+ const explicitDefExt = createExplicitDefinitionExtension();
2029
+ const footnoteDefExt = createFootnoteDefinitionExtension();
2030
+ const userBlockExts = [];
2031
+ const userBlockStartExts = [];
2032
+ const userInlineExts = [];
2033
+ const userInlineStartExts = [];
2034
+ for (const ext of this.userExtensions) {
2035
+ if (ext.level === "block") {
2036
+ if (ext.tokenizer) userBlockExts.push(ext.tokenizer);
2037
+ if (ext.start) userBlockStartExts.push(ext.start);
2038
+ } else if (ext.level === "inline") {
2039
+ if (ext.tokenizer) userInlineExts.push(ext.tokenizer);
2040
+ if (ext.start) userInlineStartExts.push(ext.start);
2041
+ }
2042
+ }
2043
+ const blockExts = [
2044
+ footnoteDefExt.tokenizer,
2045
+ explicitDefExt.tokenizer,
2046
+ ...userBlockExts
2047
+ ];
2048
+ const blockStartExts = [
2049
+ footnoteDefExt.start,
2050
+ explicitDefExt.start,
2051
+ ...userBlockStartExts
2052
+ ];
2053
+ const inlineExts = [optimisticRefExt.tokenizer, ...userInlineExts];
2054
+ const inlineStartExts = [optimisticRefExt.start, ...userInlineStartExts];
2055
+ if (this.options.math) {
2056
+ const blockMathExt = createBlockMathExtension();
2057
+ const inlineMathExt = createInlineMathExtension();
2058
+ blockExts.unshift(blockMathExt.tokenizer);
2059
+ blockStartExts.unshift(blockMathExt.start);
2060
+ inlineExts.unshift(inlineMathExt.tokenizer);
2061
+ inlineStartExts.unshift(inlineMathExt.start);
2062
+ }
2063
+ if (this.htmlTreeOptions) {
2064
+ const inlineHtmlExt = createInlineHtmlExtension();
2065
+ inlineExts.unshift(inlineHtmlExt.tokenizer);
2066
+ inlineStartExts.unshift(inlineHtmlExt.start);
2067
+ }
2068
+ const lexerOptions = {
2069
+ gfm: true,
2070
+ breaks: false,
2071
+ // 关闭软换行转 break,与 Micromark 保持一致
2072
+ ...this.options,
2073
+ extensions: {
2074
+ inline: inlineExts,
2075
+ startInline: inlineStartExts,
2076
+ block: blockExts,
2077
+ startBlock: blockStartExts
1061
2078
  }
2079
+ };
2080
+ const lexerInstance = new Lexer(lexerOptions);
2081
+ if (lexerInstance.tokens && lexerInstance.tokens.links) {
2082
+ Object.assign(lexerInstance.tokens.links, this.globalLinks);
1062
2083
  }
2084
+ let tokens = lexerInstance.lex(normalizedText);
2085
+ if (lexerInstance.tokens && lexerInstance.tokens.links) {
2086
+ Object.assign(this.globalLinks, lexerInstance.tokens.links);
2087
+ }
2088
+ tokens = this.preprocessTokens(tokens);
2089
+ let children = this.transformTokensWithPosition(tokens);
2090
+ if (this.htmlTreeOptions) {
2091
+ children = this.processHtmlNodes(children);
2092
+ }
2093
+ return {
2094
+ type: "root",
2095
+ children
2096
+ };
1063
2097
  }
1064
- if (context.inFootnote && isFootnoteDefinitionStart(line)) {
1065
- const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
1066
- newContext.footnoteIdentifier = identifier;
1067
- return newContext;
1068
- }
1069
- if (!context.inFootnote && isFootnoteDefinitionStart(line)) {
1070
- const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
1071
- newContext.inFootnote = true;
1072
- newContext.footnoteIdentifier = identifier;
1073
- return newContext;
1074
- }
1075
- const listItem = isListItemStart(line);
1076
- if (context.inList) {
1077
- if (context.listMayEnd) {
1078
- if (listItem) {
1079
- if (listItem.ordered === context.listOrdered && listItem.indent === context.listIndent) {
1080
- newContext.listMayEnd = false;
1081
- return newContext;
2098
+ /**
2099
+ * 预处理 tokens
2100
+ *
2101
+ * 处理容器指令和遗留的脚注定义(从 paragraph 中提取)
2102
+ */
2103
+ preprocessTokens(tokens) {
2104
+ const result = [];
2105
+ let i = 0;
2106
+ while (i < tokens.length) {
2107
+ const token = tokens[i];
2108
+ if (token.type === "paragraph") {
2109
+ const text = token.text;
2110
+ const footnoteMatch = text.match(/^\[\^([a-zA-Z0-9_-]+)\]:\s+([\s\S]*)$/);
2111
+ if (footnoteMatch) {
2112
+ const defToken = {
2113
+ type: "footnoteDefinition",
2114
+ identifier: footnoteMatch[1],
2115
+ text: footnoteMatch[2],
2116
+ tokens: new Lexer().inlineTokens(footnoteMatch[2]),
2117
+ raw: token.raw
2118
+ };
2119
+ result.push(defToken);
2120
+ i++;
2121
+ continue;
2122
+ }
2123
+ const containerStartMatch = text.match(/^:::(\s*)([a-zA-Z0-9_-]+)(.*?)(\n|$)/);
2124
+ if (containerStartMatch) {
2125
+ const name = containerStartMatch[2];
2126
+ const attrs = containerStartMatch[3].trim();
2127
+ let rawAccumulator = "";
2128
+ let j = i;
2129
+ let depth = 0;
2130
+ let foundEnd = false;
2131
+ let contentRaw = "";
2132
+ while (j < tokens.length) {
2133
+ const currentToken = tokens[j];
2134
+ rawAccumulator += currentToken.raw;
2135
+ const lines = rawAccumulator.split("\n");
2136
+ depth = 0;
2137
+ let startLineIndex = -1;
2138
+ let endLineIndex = -1;
2139
+ for (let k = 0; k < lines.length; k++) {
2140
+ const line = lines[k];
2141
+ if (line.match(/^:::(\s*)([a-zA-Z0-9_-]+)/)) {
2142
+ if (depth === 0 && startLineIndex === -1) startLineIndex = k;
2143
+ depth++;
2144
+ } else if (line.trim() === ":::") {
2145
+ depth--;
2146
+ if (depth === 0) {
2147
+ endLineIndex = k;
2148
+ foundEnd = true;
2149
+ break;
2150
+ }
2151
+ }
2152
+ }
2153
+ if (foundEnd) {
2154
+ const contentLines = lines.slice(startLineIndex + 1, endLineIndex);
2155
+ contentRaw = contentLines.join("\n");
2156
+ const remainingLines = lines.slice(endLineIndex + 1);
2157
+ const remainingText = remainingLines.join("\n");
2158
+ const containerToken = {
2159
+ type: "container",
2160
+ name,
2161
+ attrs,
2162
+ tokens: this.preprocessTokens(lexer(contentRaw)),
2163
+ raw: rawAccumulator
2164
+ };
2165
+ result.push(containerToken);
2166
+ if (remainingText.trim()) {
2167
+ const remainingTokens = this.preprocessTokens(lexer(remainingText));
2168
+ result.push(...remainingTokens);
2169
+ }
2170
+ i = j + 1;
2171
+ break;
2172
+ }
2173
+ j++;
2174
+ }
2175
+ if (foundEnd) continue;
1082
2176
  }
1083
- newContext.inList = true;
1084
- newContext.listOrdered = listItem.ordered;
1085
- newContext.listIndent = listItem.indent;
1086
- newContext.listMayEnd = false;
1087
- return newContext;
1088
- } else if (isListContinuation(line, context.listIndent ?? 0)) {
1089
- newContext.listMayEnd = isEmptyLine(line);
1090
- return newContext;
1091
- } else {
1092
- newContext.inList = false;
1093
- newContext.listOrdered = void 0;
1094
- newContext.listIndent = void 0;
1095
- newContext.listMayEnd = false;
1096
- return newContext;
1097
- }
1098
- } else {
1099
- if (listItem) {
1100
- return newContext;
1101
- } else if (isEmptyLine(line)) {
1102
- newContext.listMayEnd = true;
1103
- return newContext;
1104
- } else if (isListContinuation(line, context.listIndent ?? 0)) {
1105
- return newContext;
1106
- } else {
1107
- newContext.inList = false;
1108
- newContext.listOrdered = void 0;
1109
- newContext.listIndent = void 0;
1110
- newContext.listMayEnd = false;
1111
- return newContext;
1112
2177
  }
2178
+ result.push(token);
2179
+ i++;
1113
2180
  }
1114
- } else {
1115
- if (listItem) {
1116
- newContext.inList = true;
1117
- newContext.listOrdered = listItem.ordered;
1118
- newContext.listIndent = listItem.indent;
1119
- newContext.listMayEnd = false;
1120
- return newContext;
2181
+ return result;
2182
+ }
2183
+ /**
2184
+ * 转换 tokens 为 MDAST 节点(带位置信息)
2185
+ */
2186
+ transformTokensWithPosition(tokens) {
2187
+ if (!tokens) return [];
2188
+ const results = [];
2189
+ let currentOffset = 0;
2190
+ for (const token of tokens) {
2191
+ const rawLength = token.raw?.length ?? 0;
2192
+ const node = transformBlockToken(token, this.transformContext);
2193
+ if (node) {
2194
+ node.position = {
2195
+ start: { line: 0, column: 0, offset: currentOffset },
2196
+ end: { line: 0, column: 0, offset: currentOffset + rawLength }
2197
+ };
2198
+ results.push(node);
2199
+ }
2200
+ currentOffset += rawLength;
1121
2201
  }
2202
+ return results;
1122
2203
  }
1123
- if (context.inFootnote) {
1124
- if (isEmptyLine(line)) {
1125
- return newContext;
1126
- } else if (isListItemStart(line)) {
1127
- const listItemInfo = isListItemStart(line);
1128
- if (listItemInfo.indent === 0) {
1129
- newContext.inFootnote = false;
1130
- newContext.footnoteIdentifier = void 0;
1131
- } else {
1132
- return newContext;
2204
+ /**
2205
+ * 转换 tokens 为 MDAST 节点(不带位置信息)
2206
+ */
2207
+ transformTokens(tokens) {
2208
+ if (!tokens) return [];
2209
+ return tokens.map((t) => transformBlockToken(t, this.transformContext)).filter(Boolean);
2210
+ }
2211
+ /**
2212
+ * 转换行内 tokens
2213
+ */
2214
+ transformInline(tokens) {
2215
+ if (!tokens) return [];
2216
+ const results = [];
2217
+ for (const token of tokens) {
2218
+ const result = transformInlineToken(token, this.transformContext);
2219
+ if (result) {
2220
+ if (Array.isArray(result)) {
2221
+ results.push(...result);
2222
+ } else {
2223
+ results.push(result);
2224
+ }
1133
2225
  }
1134
- } else if (isHeading(line)) {
1135
- newContext.inFootnote = false;
1136
- newContext.footnoteIdentifier = void 0;
1137
- return newContext;
1138
- } else if (detectFenceStart(line)) {
1139
- newContext.inFootnote = false;
1140
- newContext.footnoteIdentifier = void 0;
1141
- return newContext;
1142
- } else if (isBlockquoteStart(line)) {
1143
- newContext.inFootnote = false;
1144
- newContext.footnoteIdentifier = void 0;
1145
- return newContext;
1146
- } else if (isFootnoteContinuation(line)) {
1147
- return newContext;
1148
- } else if (isFootnoteDefinitionStart(line)) {
1149
- const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
1150
- newContext.footnoteIdentifier = identifier;
1151
- return newContext;
1152
- } else {
1153
- newContext.inFootnote = false;
1154
- newContext.footnoteIdentifier = void 0;
1155
- return newContext;
1156
2226
  }
2227
+ return results;
1157
2228
  }
1158
- return newContext;
1159
- }
1160
-
1161
- // src/utils/index.ts
1162
- var idCounter = 0;
1163
- function generateId(prefix = "block") {
1164
- return `${prefix}-${++idCounter}`;
1165
- }
1166
- function resetIdCounter() {
1167
- idCounter = 0;
1168
- }
1169
- function calculateLineOffset(lines, lineIndex) {
1170
- let offset = 0;
1171
- for (let i = 0; i < lineIndex && i < lines.length; i++) {
1172
- offset += lines[i].length + 1;
2229
+ /**
2230
+ * 解析脚注内容为 AST 节点
2231
+ */
2232
+ parseFootnoteContent(content) {
2233
+ if (!content.trim()) {
2234
+ return [];
2235
+ }
2236
+ const normalizedContent = content.split("\n").map((line, index) => {
2237
+ if (index === 0) return line;
2238
+ if (line.startsWith(" ")) return line.slice(4);
2239
+ if (line.startsWith(" ")) return line.slice(1);
2240
+ return line;
2241
+ }).join("\n");
2242
+ const contentLexer = new Lexer({ gfm: true, breaks: true });
2243
+ const tokens = contentLexer.lex(normalizedContent);
2244
+ return this.transformTokens(tokens);
1173
2245
  }
1174
- return offset;
1175
- }
1176
- function splitLines(text) {
1177
- return text.split("\n");
1178
- }
1179
- function joinLines(lines, start, end) {
1180
- return lines.slice(start, end + 1).join("\n");
1181
- }
1182
- function isDefinitionNode(node) {
1183
- return node.type === "definition";
1184
- }
1185
- function isFootnoteDefinitionNode(node) {
1186
- return node.type === "footnoteDefinition";
1187
- }
2246
+ /**
2247
+ * 处理 HTML 节点
2248
+ *
2249
+ * 使用 html-extension 的 transformHtmlNodes 来处理:
2250
+ * - 合并被空行分割的 HTML 节点
2251
+ * - HTML 解析为 HtmlElementNode 树结构
2252
+ */
2253
+ processHtmlNodes(nodes) {
2254
+ const tempRoot = {
2255
+ type: "root",
2256
+ children: nodes
2257
+ };
2258
+ const transformed = transformHtmlNodes(tempRoot, this.htmlTreeOptions);
2259
+ return transformed.children;
2260
+ }
2261
+ /**
2262
+ * 将 AST 节点转换为 ParsedBlock
2263
+ */
2264
+ nodesToBlocks(nodes, startOffset, rawText, status, generateBlockId) {
2265
+ const blocks = [];
2266
+ for (const node of nodes) {
2267
+ const relativeStart = node.position?.start?.offset ?? 0;
2268
+ const relativeEnd = node.position?.end?.offset ?? rawText.length;
2269
+ const nodeText = rawText.substring(relativeStart, relativeEnd);
2270
+ const absoluteStart = startOffset + relativeStart;
2271
+ const absoluteEnd = startOffset + relativeEnd;
2272
+ blocks.push({
2273
+ id: generateBlockId(),
2274
+ status,
2275
+ node,
2276
+ startOffset: absoluteStart,
2277
+ endOffset: absoluteEnd,
2278
+ rawText: nodeText
2279
+ });
2280
+ }
2281
+ return blocks;
2282
+ }
2283
+ };
1188
2284
 
1189
2285
  // src/parser/IncremarkParser.ts
1190
2286
  var IncremarkParser = class {
1191
- buffer = "";
1192
2287
  lines = [];
1193
2288
  /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
1194
2289
  lineOffsets = [0];
@@ -1197,223 +2292,62 @@ var IncremarkParser = class {
1197
2292
  blockIdCounter = 0;
1198
2293
  context;
1199
2294
  options;
1200
- /** 缓存的容器配置,避免重复计算 */
1201
- containerConfig;
1202
- /** 缓存的 HTML 树配置,避免重复计算 */
1203
- htmlTreeConfig;
2295
+ /** 边界检测器 */
2296
+ boundaryDetector;
2297
+ /** AST 构建器 */
2298
+ astBuilder;
2299
+ /** Definition 管理器 */
2300
+ definitionManager;
2301
+ /** Footnote 管理器 */
2302
+ footnoteManager;
1204
2303
  /** 上次 append 返回的 pending blocks,用于 getAst 复用 */
1205
2304
  lastPendingBlocks = [];
1206
- /** Definition 映射表(用于引用式图片和链接) */
1207
- definitionMap = {};
1208
- /** Footnote Definition 映射表 */
1209
- footnoteDefinitionMap = {};
1210
- /** Footnote Reference 出现顺序(按引用在文档中的顺序) */
1211
- footnoteReferenceOrder = [];
1212
2305
  constructor(options = {}) {
1213
2306
  this.options = {
1214
2307
  gfm: true,
1215
2308
  ...options
1216
2309
  };
1217
2310
  this.context = createInitialContext();
1218
- this.containerConfig = this.computeContainerConfig();
1219
- this.htmlTreeConfig = this.computeHtmlTreeConfig();
2311
+ const BuilderClass = options.astBuilder || MarkedAstBuilder;
2312
+ this.astBuilder = new BuilderClass(this.options);
2313
+ this.boundaryDetector = new BoundaryDetector({ containers: this.astBuilder.containerConfig });
2314
+ this.definitionManager = new DefinitionManager();
2315
+ this.footnoteManager = new FootnoteManager();
1220
2316
  }
1221
2317
  generateBlockId() {
1222
2318
  return `block-${++this.blockIdCounter}`;
1223
2319
  }
1224
- computeContainerConfig() {
1225
- const containers = this.options.containers;
1226
- if (!containers) return void 0;
1227
- return containers === true ? {} : containers;
1228
- }
1229
- computeHtmlTreeConfig() {
1230
- const htmlTree = this.options.htmlTree;
1231
- if (!htmlTree) return void 0;
1232
- return htmlTree === true ? {} : htmlTree;
1233
- }
1234
2320
  /**
1235
- * HTML 节点转换为纯文本
1236
- * 递归处理 AST 中所有 html 类型的节点
1237
- * - 块级 HTML 节点 → 转换为 paragraph 包含 text
1238
- * - 内联 HTML 节点(在段落内部)→ 转换为 text 节点
2321
+ * 更新已完成的 blocks 中的 definitions 和 footnote definitions
1239
2322
  */
1240
- convertHtmlToText(ast) {
1241
- const processInlineChildren = (children) => {
1242
- return children.map((node) => {
1243
- const n = node;
1244
- if (n.type === "html") {
1245
- const htmlNode = n;
1246
- const textNode = {
1247
- type: "text",
1248
- value: htmlNode.value,
1249
- position: htmlNode.position
1250
- };
1251
- return textNode;
1252
- }
1253
- if ("children" in n && Array.isArray(n.children)) {
1254
- const parent = n;
1255
- return {
1256
- ...parent,
1257
- children: processInlineChildren(parent.children)
1258
- };
1259
- }
1260
- return n;
1261
- });
1262
- };
1263
- const processBlockChildren = (children) => {
1264
- return children.map((node) => {
1265
- if (node.type === "html") {
1266
- const htmlNode = node;
1267
- const textNode = {
1268
- type: "text",
1269
- value: htmlNode.value
1270
- };
1271
- const paragraphNode = {
1272
- type: "paragraph",
1273
- children: [textNode],
1274
- position: htmlNode.position
1275
- };
1276
- return paragraphNode;
1277
- }
1278
- if ("children" in node && Array.isArray(node.children)) {
1279
- const parent = node;
1280
- if (node.type === "paragraph" || node.type === "heading" || node.type === "tableCell" || node.type === "delete" || node.type === "emphasis" || node.type === "strong" || node.type === "link" || node.type === "linkReference") {
1281
- return {
1282
- ...parent,
1283
- children: processInlineChildren(parent.children)
1284
- };
1285
- }
1286
- return {
1287
- ...parent,
1288
- children: processBlockChildren(parent.children)
1289
- };
1290
- }
1291
- return node;
1292
- });
1293
- };
1294
- return {
1295
- ...ast,
1296
- children: processBlockChildren(ast.children)
1297
- };
1298
- }
1299
- parse(text) {
1300
- const extensions = [];
1301
- const mdastExtensions = [];
1302
- if (this.options.gfm) {
1303
- extensions.push(gfm());
1304
- mdastExtensions.push(...gfmFromMarkdown(), gfmFootnoteFromMarkdown());
1305
- }
1306
- if (this.options.math) {
1307
- extensions.push(math());
1308
- mdastExtensions.push(mathFromMarkdown());
1309
- }
1310
- if (this.containerConfig !== void 0) {
1311
- extensions.push(directive());
1312
- mdastExtensions.push(directiveFromMarkdown());
1313
- }
1314
- if (this.options.extensions) {
1315
- extensions.push(...this.options.extensions);
1316
- }
1317
- if (this.options.mdastExtensions) {
1318
- mdastExtensions.push(...this.options.mdastExtensions);
1319
- }
1320
- if (this.options.gfm) {
1321
- extensions.push(gfmFootnoteIncremental());
1322
- }
1323
- extensions.push(micromarkReferenceExtension());
1324
- let ast = fromMarkdown(text, { extensions, mdastExtensions });
1325
- if (this.htmlTreeConfig) {
1326
- ast = transformHtmlNodes(ast, this.htmlTreeConfig);
1327
- } else {
1328
- ast = this.convertHtmlToText(ast);
1329
- }
1330
- return ast;
1331
- }
1332
2323
  updateDefinitionsFromCompletedBlocks(blocks) {
1333
- for (const block of blocks) {
1334
- this.definitionMap = {
1335
- ...this.definitionMap,
1336
- ...this.findDefinition(block)
1337
- };
1338
- this.footnoteDefinitionMap = {
1339
- ...this.footnoteDefinitionMap,
1340
- ...this.findFootnoteDefinition(block)
1341
- };
1342
- }
1343
- }
1344
- findDefinition(block) {
1345
- const definitions = [];
1346
- function findDefinitionRecursive(node) {
1347
- if (isDefinitionNode(node)) {
1348
- definitions.push(node);
1349
- }
1350
- if ("children" in node && Array.isArray(node.children)) {
1351
- for (const child of node.children) {
1352
- findDefinitionRecursive(child);
1353
- }
1354
- }
1355
- }
1356
- findDefinitionRecursive(block.node);
1357
- return definitions.reduce((acc, node) => {
1358
- acc[node.identifier] = node;
1359
- return acc;
1360
- }, {});
1361
- }
1362
- findFootnoteDefinition(block) {
1363
- const footnoteDefinitions = [];
1364
- function findFootnoteDefinition(node) {
1365
- if (isFootnoteDefinitionNode(node)) {
1366
- footnoteDefinitions.push(node);
1367
- }
1368
- }
1369
- findFootnoteDefinition(block.node);
1370
- return footnoteDefinitions.reduce((acc, node) => {
1371
- acc[node.identifier] = node;
1372
- return acc;
1373
- }, {});
1374
- }
1375
- /**
1376
- * 收集 AST 中的脚注引用(按出现顺序)
1377
- * 用于确定脚注的显示顺序
1378
- */
1379
- collectFootnoteReferences(nodes) {
1380
- const visitNode = (node) => {
1381
- if (!node) return;
1382
- if (node.type === "footnoteReference") {
1383
- const identifier = node.identifier;
1384
- if (!this.footnoteReferenceOrder.includes(identifier)) {
1385
- this.footnoteReferenceOrder.push(identifier);
1386
- }
1387
- }
1388
- if (node.children && Array.isArray(node.children)) {
1389
- node.children.forEach(visitNode);
1390
- }
1391
- };
1392
- nodes.forEach(visitNode);
2324
+ this.definitionManager.extractFromBlocks(blocks);
2325
+ this.footnoteManager.extractDefinitionsFromBlocks(blocks);
1393
2326
  }
1394
2327
  /**
1395
2328
  * 增量更新 lines 和 lineOffsets
1396
- * 只处理新增的内容,避免全量 split
2329
+ * 优化策略:只 split 新增的 chunk,不拼接旧字符串,避免长行性能劣化
1397
2330
  */
1398
- updateLines() {
2331
+ updateLines(chunk) {
1399
2332
  const prevLineCount = this.lines.length;
1400
2333
  if (prevLineCount === 0) {
1401
- this.lines = this.buffer.split("\n");
2334
+ this.lines = chunk.split("\n");
1402
2335
  this.lineOffsets = [0];
1403
- for (let i = 0; i < this.lines.length; i++) {
2336
+ for (let i = 0; i < this.lines.length - 1; i++) {
1404
2337
  this.lineOffsets.push(this.lineOffsets[i] + this.lines[i].length + 1);
1405
2338
  }
1406
2339
  return;
1407
2340
  }
1408
- const lastLineStart = this.lineOffsets[prevLineCount - 1];
1409
- const textFromLastLine = this.buffer.slice(lastLineStart);
1410
- const newLines = textFromLastLine.split("\n");
1411
- this.lines.length = prevLineCount - 1;
1412
- this.lineOffsets.length = prevLineCount;
1413
- for (let i = 0; i < newLines.length; i++) {
1414
- this.lines.push(newLines[i]);
1415
- const prevOffset = this.lineOffsets[this.lineOffsets.length - 1];
1416
- this.lineOffsets.push(prevOffset + newLines[i].length + 1);
2341
+ const chunkLines = chunk.split("\n");
2342
+ const lastLineIndex = prevLineCount - 1;
2343
+ this.lines[lastLineIndex] += chunkLines[0];
2344
+ for (let i = 1; i < chunkLines.length; i++) {
2345
+ const prevLineIndex = this.lines.length - 1;
2346
+ const prevLineStart = this.lineOffsets[prevLineIndex];
2347
+ const prevLineLength = this.lines[prevLineIndex].length;
2348
+ const newOneOffset = prevLineStart + prevLineLength + 1;
2349
+ this.lineOffsets.push(newOneOffset);
2350
+ this.lines.push(chunkLines[i]);
1417
2351
  }
1418
2352
  }
1419
2353
  /**
@@ -1427,176 +2361,18 @@ var IncremarkParser = class {
1427
2361
  * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
1428
2362
  */
1429
2363
  findStableBoundary() {
1430
- let stableLine = -1;
1431
- let stableContext = this.context;
1432
- let tempContext = { ...this.context };
1433
- for (let i = this.pendingStartLine; i < this.lines.length; i++) {
1434
- const line = this.lines[i];
1435
- const wasInFencedCode = tempContext.inFencedCode;
1436
- const wasInContainer = tempContext.inContainer;
1437
- const wasContainerDepth = tempContext.containerDepth;
1438
- tempContext = updateContext(line, tempContext, this.containerConfig);
1439
- if (wasInFencedCode && !tempContext.inFencedCode) {
1440
- if (i < this.lines.length - 1) {
1441
- stableLine = i;
1442
- stableContext = { ...tempContext };
1443
- }
1444
- continue;
1445
- }
1446
- if (tempContext.inFencedCode) {
1447
- continue;
1448
- }
1449
- if (wasInContainer && wasContainerDepth === 1 && !tempContext.inContainer) {
1450
- if (i < this.lines.length - 1) {
1451
- stableLine = i;
1452
- stableContext = { ...tempContext };
1453
- }
1454
- continue;
1455
- }
1456
- if (tempContext.inContainer) {
1457
- continue;
1458
- }
1459
- const stablePoint = this.checkStability(i, tempContext);
1460
- if (stablePoint >= 0) {
1461
- stableLine = stablePoint;
1462
- stableContext = { ...tempContext };
1463
- }
1464
- }
1465
- return { line: stableLine, contextAtLine: stableContext };
1466
- }
1467
- checkStability(lineIndex, context) {
1468
- if (lineIndex === 0) {
1469
- return -1;
1470
- }
1471
- const line = this.lines[lineIndex];
1472
- const prevLine = this.lines[lineIndex - 1];
1473
- if (context.inContainer) {
1474
- if (this.containerConfig !== void 0) {
1475
- const containerEnd = detectContainerEnd(line, context, this.containerConfig);
1476
- if (containerEnd) {
1477
- return lineIndex - 1;
1478
- }
1479
- }
1480
- return -1;
1481
- }
1482
- if (context.inList) {
1483
- if (!context.listMayEnd) {
1484
- return -1;
1485
- }
1486
- }
1487
- if (isHeading(prevLine) || isThematicBreak(prevLine)) {
1488
- return lineIndex - 1;
1489
- }
1490
- if (lineIndex >= this.lines.length - 1) {
1491
- return -1;
1492
- }
1493
- if (isFootnoteDefinitionStart(prevLine)) {
1494
- if (isEmptyLine(line) || isFootnoteContinuation(line)) {
1495
- return -1;
1496
- }
1497
- if (isFootnoteDefinitionStart(line)) {
1498
- return lineIndex - 1;
1499
- }
1500
- }
1501
- if (!isEmptyLine(prevLine) && isFootnoteContinuation(prevLine)) {
1502
- const footnoteStartLine = this.findFootnoteStart(lineIndex - 1);
1503
- if (footnoteStartLine >= 0) {
1504
- if (isEmptyLine(line) || isFootnoteContinuation(line)) {
1505
- return -1;
1506
- }
1507
- if (isFootnoteDefinitionStart(line)) {
1508
- return lineIndex - 1;
1509
- }
1510
- return lineIndex - 1;
1511
- }
1512
- }
1513
- if (!isEmptyLine(prevLine)) {
1514
- if (isFootnoteDefinitionStart(line) && !isFootnoteDefinitionStart(prevLine)) {
1515
- return lineIndex - 1;
1516
- }
1517
- if (isHeading(line)) {
1518
- return lineIndex - 1;
1519
- }
1520
- if (detectFenceStart(line)) {
1521
- return lineIndex - 1;
1522
- }
1523
- if (isBlockquoteStart(line) && !isBlockquoteStart(prevLine)) {
1524
- return lineIndex - 1;
1525
- }
1526
- if (!context.inList && isListItemStart(line) && !isListItemStart(prevLine)) {
1527
- return lineIndex - 1;
1528
- }
1529
- if (this.containerConfig !== void 0) {
1530
- const container = detectContainer(line, this.containerConfig);
1531
- if (container && !container.isEnd) {
1532
- const prevContainer = detectContainer(prevLine, this.containerConfig);
1533
- if (!prevContainer || prevContainer.isEnd) {
1534
- return lineIndex - 1;
1535
- }
1536
- }
1537
- }
1538
- }
1539
- if (isEmptyLine(line) && !isEmptyLine(prevLine) && !context.inList) {
1540
- return lineIndex;
1541
- }
1542
- return -1;
1543
- }
1544
- /**
1545
- * 从指定行向上查找脚注定义的起始行
1546
- *
1547
- * @param fromLine 开始查找的行索引
1548
- * @returns 脚注起始行索引,如果不属于脚注返回 -1
1549
- *
1550
- * @example
1551
- * // 假设 lines 为:
1552
- * // 0: "[^1]: 第一行"
1553
- * // 1: " 第二行"
1554
- * // 2: " 第三行"
1555
- * findFootnoteStart(2) // 返回 0
1556
- * findFootnoteStart(1) // 返回 0
1557
- */
1558
- findFootnoteStart(fromLine) {
1559
- const maxLookback = 20;
1560
- const startLine = Math.max(0, fromLine - maxLookback);
1561
- for (let i = fromLine; i >= startLine; i--) {
1562
- const line = this.lines[i];
1563
- if (isFootnoteDefinitionStart(line)) {
1564
- return i;
1565
- }
1566
- if (isEmptyLine(line)) {
1567
- continue;
1568
- }
1569
- if (!isFootnoteContinuation(line)) {
1570
- return -1;
1571
- }
1572
- }
1573
- return -1;
1574
- }
1575
- nodesToBlocks(nodes, startOffset, rawText, status) {
1576
- const blocks = [];
1577
- let currentOffset = startOffset;
1578
- for (const node of nodes) {
1579
- const nodeStart = node.position?.start?.offset ?? currentOffset;
1580
- const nodeEnd = node.position?.end?.offset ?? currentOffset + 1;
1581
- const nodeText = rawText.substring(nodeStart - startOffset, nodeEnd - startOffset);
1582
- blocks.push({
1583
- id: this.generateBlockId(),
1584
- status,
1585
- node,
1586
- startOffset: nodeStart,
1587
- endOffset: nodeEnd,
1588
- rawText: nodeText
1589
- });
1590
- currentOffset = nodeEnd;
1591
- }
1592
- return blocks;
2364
+ const result = this.boundaryDetector.findStableBoundary(
2365
+ this.lines,
2366
+ this.pendingStartLine,
2367
+ this.context
2368
+ );
2369
+ return { line: result.line, contextAtLine: result.context };
1593
2370
  }
1594
2371
  /**
1595
2372
  * 追加新的 chunk 并返回增量更新
1596
2373
  */
1597
2374
  append(chunk) {
1598
- this.buffer += chunk;
1599
- this.updateLines();
2375
+ this.updateLines(chunk);
1600
2376
  const { line: stableBoundary, contextAtLine } = this.findStableBoundary();
1601
2377
  const update = {
1602
2378
  completed: [],
@@ -1610,11 +2386,13 @@ var IncremarkParser = class {
1610
2386
  if (stableBoundary >= this.pendingStartLine && stableBoundary >= 0) {
1611
2387
  const stableText = this.lines.slice(this.pendingStartLine, stableBoundary + 1).join("\n");
1612
2388
  const stableOffset = this.getLineOffset(this.pendingStartLine);
1613
- const ast = this.parse(stableText);
1614
- const newBlocks = this.nodesToBlocks(ast.children, stableOffset, stableText, "completed");
2389
+ const ast = this.astBuilder.parse(stableText);
2390
+ const newBlocks = this.astBuilder.nodesToBlocks(ast.children, stableOffset, stableText, "completed", () => this.generateBlockId());
1615
2391
  this.completedBlocks.push(...newBlocks);
1616
2392
  update.completed = newBlocks;
1617
2393
  this.updateDefinitionsFromCompletedBlocks(newBlocks);
2394
+ this.footnoteManager.collectReferencesFromCompletedBlocks(newBlocks);
2395
+ this.boundaryDetector.clearContextCache(this.pendingStartLine);
1618
2396
  this.context = contextAtLine;
1619
2397
  this.pendingStartLine = stableBoundary + 1;
1620
2398
  }
@@ -1622,8 +2400,8 @@ var IncremarkParser = class {
1622
2400
  const pendingText = this.lines.slice(this.pendingStartLine).join("\n");
1623
2401
  if (pendingText.trim()) {
1624
2402
  const pendingOffset = this.getLineOffset(this.pendingStartLine);
1625
- const ast = this.parse(pendingText);
1626
- update.pending = this.nodesToBlocks(ast.children, pendingOffset, pendingText, "pending");
2403
+ const ast = this.astBuilder.parse(pendingText);
2404
+ update.pending = this.astBuilder.nodesToBlocks(ast.children, pendingOffset, pendingText, "pending", () => this.generateBlockId());
1627
2405
  }
1628
2406
  }
1629
2407
  this.lastPendingBlocks = update.pending;
@@ -1631,10 +2409,9 @@ var IncremarkParser = class {
1631
2409
  type: "root",
1632
2410
  children: [...this.completedBlocks.map((b) => b.node), ...update.pending.map((b) => b.node)]
1633
2411
  };
1634
- this.collectFootnoteReferences(update.ast.children);
2412
+ update.footnoteReferenceOrder = this.footnoteManager.collectReferencesFromPending(update.pending);
1635
2413
  update.definitions = this.getDefinitionMap();
1636
2414
  update.footnoteDefinitions = this.getFootnoteDefinitionMap();
1637
- update.footnoteReferenceOrder = this.getFootnoteReferenceOrder();
1638
2415
  this.emitChange(update.pending);
1639
2416
  return update;
1640
2417
  }
@@ -1646,7 +2423,7 @@ var IncremarkParser = class {
1646
2423
  const state = {
1647
2424
  completedBlocks: this.completedBlocks,
1648
2425
  pendingBlocks,
1649
- markdown: this.buffer,
2426
+ markdown: this.lines.join("\n"),
1650
2427
  ast: {
1651
2428
  type: "root",
1652
2429
  children: [
@@ -1654,8 +2431,8 @@ var IncremarkParser = class {
1654
2431
  ...pendingBlocks.map((b) => b.node)
1655
2432
  ]
1656
2433
  },
1657
- definitions: { ...this.definitionMap },
1658
- footnoteDefinitions: { ...this.footnoteDefinitionMap }
2434
+ definitions: { ...this.getDefinitionMap() },
2435
+ footnoteDefinitions: { ...this.getFootnoteDefinitionMap() }
1659
2436
  };
1660
2437
  this.options.onChange(state);
1661
2438
  }
@@ -1670,24 +2447,27 @@ var IncremarkParser = class {
1670
2447
  updated: [],
1671
2448
  pending: [],
1672
2449
  ast: { type: "root", children: [] },
1673
- definitions: {},
1674
- footnoteDefinitions: {},
1675
- footnoteReferenceOrder: []
2450
+ definitions: this.getDefinitionMap(),
2451
+ footnoteDefinitions: this.getFootnoteDefinitionMap(),
2452
+ footnoteReferenceOrder: this.getFootnoteReferenceOrder()
1676
2453
  };
1677
2454
  if (this.pendingStartLine < this.lines.length) {
1678
2455
  const remainingText = this.lines.slice(this.pendingStartLine).join("\n");
1679
2456
  if (remainingText.trim()) {
1680
2457
  const remainingOffset = this.getLineOffset(this.pendingStartLine);
1681
- const ast = this.parse(remainingText);
1682
- const finalBlocks = this.nodesToBlocks(
2458
+ const ast = this.astBuilder.parse(remainingText);
2459
+ const finalBlocks = this.astBuilder.nodesToBlocks(
1683
2460
  ast.children,
1684
2461
  remainingOffset,
1685
2462
  remainingText,
1686
- "completed"
2463
+ "completed",
2464
+ () => this.generateBlockId()
1687
2465
  );
1688
2466
  this.completedBlocks.push(...finalBlocks);
1689
2467
  update.completed = finalBlocks;
1690
2468
  this.updateDefinitionsFromCompletedBlocks(finalBlocks);
2469
+ this.footnoteManager.collectReferencesFromCompletedBlocks(finalBlocks);
2470
+ this.boundaryDetector.clearContextCache(this.pendingStartLine);
1691
2471
  }
1692
2472
  }
1693
2473
  this.lastPendingBlocks = [];
@@ -1696,7 +2476,6 @@ var IncremarkParser = class {
1696
2476
  type: "root",
1697
2477
  children: this.completedBlocks.map((b) => b.node)
1698
2478
  };
1699
- this.collectFootnoteReferences(update.ast.children);
1700
2479
  update.definitions = this.getDefinitionMap();
1701
2480
  update.footnoteDefinitions = this.getFootnoteDefinitionMap();
1702
2481
  update.footnoteReferenceOrder = this.getFootnoteReferenceOrder();
@@ -1719,7 +2498,7 @@ var IncremarkParser = class {
1719
2498
  ...this.completedBlocks.map((b) => b.node),
1720
2499
  ...this.lastPendingBlocks.map((b) => b.node)
1721
2500
  ];
1722
- this.collectFootnoteReferences(children);
2501
+ this.footnoteManager.collectReferencesFromPending(this.lastPendingBlocks);
1723
2502
  return {
1724
2503
  type: "root",
1725
2504
  children
@@ -1735,25 +2514,25 @@ var IncremarkParser = class {
1735
2514
  * 获取当前缓冲区内容
1736
2515
  */
1737
2516
  getBuffer() {
1738
- return this.buffer;
2517
+ return this.lines.join("\n");
1739
2518
  }
1740
2519
  /**
1741
2520
  * 获取 Definition 映射表(用于引用式图片和链接)
1742
2521
  */
1743
2522
  getDefinitionMap() {
1744
- return { ...this.definitionMap };
2523
+ return this.definitionManager.getAll();
1745
2524
  }
1746
2525
  /**
1747
2526
  * 获取 Footnote Definition 映射表
1748
2527
  */
1749
2528
  getFootnoteDefinitionMap() {
1750
- return { ...this.footnoteDefinitionMap };
2529
+ return this.footnoteManager.getDefinitions();
1751
2530
  }
1752
2531
  /**
1753
2532
  * 获取脚注引用的出现顺序
1754
2533
  */
1755
2534
  getFootnoteReferenceOrder() {
1756
- return [...this.footnoteReferenceOrder];
2535
+ return this.footnoteManager.getReferenceOrder();
1757
2536
  }
1758
2537
  /**
1759
2538
  * 设置状态变化回调(用于 DevTools 等)
@@ -1769,7 +2548,6 @@ var IncremarkParser = class {
1769
2548
  * 重置解析器状态
1770
2549
  */
1771
2550
  reset() {
1772
- this.buffer = "";
1773
2551
  this.lines = [];
1774
2552
  this.lineOffsets = [0];
1775
2553
  this.completedBlocks = [];
@@ -1777,9 +2555,8 @@ var IncremarkParser = class {
1777
2555
  this.blockIdCounter = 0;
1778
2556
  this.context = createInitialContext();
1779
2557
  this.lastPendingBlocks = [];
1780
- this.definitionMap = {};
1781
- this.footnoteDefinitionMap = {};
1782
- this.footnoteReferenceOrder = [];
2558
+ this.definitionManager.clear();
2559
+ this.footnoteManager.clear();
1783
2560
  this.emitChange([]);
1784
2561
  }
1785
2562
  /**
@@ -1814,10 +2591,9 @@ function countCharsInNode(n) {
1814
2591
  }
1815
2592
  return 1;
1816
2593
  }
1817
- function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2594
+ function sliceAst(node, maxChars, accumulatedChunks) {
1818
2595
  if (maxChars <= 0) return null;
1819
- if (skipChars >= maxChars) return null;
1820
- let remaining = maxChars - skipChars;
2596
+ let remaining = maxChars;
1821
2597
  let charIndex = 0;
1822
2598
  const chunkRanges = [];
1823
2599
  if (accumulatedChunks && accumulatedChunks.chunks.length > 0) {
@@ -1836,16 +2612,10 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
1836
2612
  if (n.value && typeof n.value === "string") {
1837
2613
  const nodeStart = charIndex;
1838
2614
  const nodeEnd = charIndex + n.value.length;
1839
- if (nodeEnd <= skipChars) {
1840
- charIndex = nodeEnd;
1841
- return null;
1842
- }
1843
- const skipInNode = Math.max(0, skipChars - nodeStart);
1844
- const take = Math.min(n.value.length - skipInNode, remaining);
2615
+ const take = Math.min(n.value.length, remaining);
1845
2616
  remaining -= take;
1846
- if (take === 0) return null;
1847
- const slicedValue = n.value.slice(skipInNode, skipInNode + take);
1848
2617
  charIndex = nodeEnd;
2618
+ const slicedValue = n.value.slice(0, take);
1849
2619
  const result = {
1850
2620
  ...n,
1851
2621
  value: slicedValue
@@ -1854,11 +2624,11 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
1854
2624
  const nodeChunks = [];
1855
2625
  let firstChunkLocalStart = take;
1856
2626
  for (const range of chunkRanges) {
1857
- const overlapStart = Math.max(range.start, nodeStart + skipInNode);
1858
- const overlapEnd = Math.min(range.end, nodeStart + skipInNode + take);
2627
+ const overlapStart = Math.max(range.start, nodeStart);
2628
+ const overlapEnd = Math.min(range.end, nodeStart + take);
1859
2629
  if (overlapStart < overlapEnd) {
1860
- const localStart = overlapStart - (nodeStart + skipInNode);
1861
- const localEnd = overlapEnd - (nodeStart + skipInNode);
2630
+ const localStart = overlapStart - nodeStart;
2631
+ const localEnd = overlapEnd - nodeStart;
1862
2632
  const chunkText = slicedValue.slice(localStart, localEnd);
1863
2633
  if (chunkText.length > 0) {
1864
2634
  if (nodeChunks.length === 0) {
@@ -1880,24 +2650,12 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
1880
2650
  }
1881
2651
  if (n.children && Array.isArray(n.children)) {
1882
2652
  const newChildren = [];
1883
- let childCharIndex = charIndex;
1884
2653
  for (const child of n.children) {
1885
2654
  if (remaining <= 0) break;
1886
- const childChars = countCharsInNode(child);
1887
- const childStart = childCharIndex;
1888
- const childEnd = childCharIndex + childChars;
1889
- if (childEnd <= skipChars) {
1890
- childCharIndex = childEnd;
1891
- continue;
1892
- }
1893
- const savedCharIndex = charIndex;
1894
- charIndex = childStart;
1895
2655
  const processed = process(child);
1896
- charIndex = savedCharIndex;
1897
2656
  if (processed) {
1898
2657
  newChildren.push(processed);
1899
2658
  }
1900
- childCharIndex = childEnd;
1901
2659
  }
1902
2660
  if (newChildren.length === 0) {
1903
2661
  return null;
@@ -1910,10 +2668,7 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
1910
2668
  }
1911
2669
  return process(node);
1912
2670
  }
1913
- function appendToAst(baseNode, sourceNode, startChars, endChars, accumulatedChunks) {
1914
- if (endChars <= startChars) {
1915
- return baseNode;
1916
- }
2671
+ function appendToAst(baseNode, sourceNode, endChars, accumulatedChunks) {
1917
2672
  const fullSlice = sliceAst(sourceNode, endChars, accumulatedChunks);
1918
2673
  if (!fullSlice) {
1919
2674
  return baseNode;
@@ -2438,7 +3193,6 @@ var BlockTransformer = class {
2438
3193
  this.cachedDisplayNode = appendToAst(
2439
3194
  this.cachedDisplayNode,
2440
3195
  block.node,
2441
- this.cachedProgress,
2442
3196
  currentProgress,
2443
3197
  this.getAccumulatedChunks()
2444
3198
  );
@@ -2554,51 +3308,7 @@ function createPlugin(name, matcher, options = {}) {
2554
3308
  ...options
2555
3309
  };
2556
3310
  }
2557
- /**
2558
- * @file Micromark 扩展:支持增量解析的 Reference 语法
2559
- *
2560
- * @description
2561
- * 在增量解析场景中,引用式图片/链接(如 `![Alt][id]`)可能在定义(`[id]: url`)之前出现。
2562
- * 标准 micromark 会检查 parser.defined,如果 id 未定义就解析为文本。
2563
- *
2564
- * 本扩展通过覆盖 labelEnd 构造,移除 parser.defined 检查,
2565
- * 使得 reference 语法总是被解析为 reference token,
2566
- * 由渲染层根据实际的 definitionMap 决定如何渲染。
2567
- *
2568
- * @module micromark-reference-extension
2569
- *
2570
- * @features
2571
- * - ✅ 支持所有 resource 语法(带 title 的图片/链接)
2572
- * - ✅ 支持所有 reference 语法(full, collapsed, shortcut)
2573
- * - ✅ 延迟验证:解析时不检查定义是否存在
2574
- * - ✅ 使用官方 factory 函数,保证与 CommonMark 标准一致
2575
- *
2576
- * @dependencies
2577
- * - micromark-factory-destination: 解析 URL(支持尖括号、括号平衡)
2578
- * - micromark-factory-title: 解析 title(支持三种引号,支持多行)
2579
- * - micromark-factory-label: 解析 label(支持转义、长度限制)
2580
- * - micromark-factory-whitespace: 解析空白符(正确生成 lineEnding/linePrefix token)
2581
- * - micromark-util-character: 字符判断工具
2582
- * - micromark-util-symbol: 常量(codes, types, constants)
2583
- * - micromark-util-types: TypeScript 类型定义
2584
- *
2585
- * @see {@link https://github.com/micromark/micromark} - micromark 官方文档
2586
- * @see {@link https://spec.commonmark.org/0.30/#images} - CommonMark 图片规范
2587
- * @see {@link https://spec.commonmark.org/0.30/#links} - CommonMark 链接规范
2588
- *
2589
- * @example
2590
- * ```typescript
2591
- * import { micromarkReferenceExtension } from './micromark-reference-extension'
2592
- * import { fromMarkdown } from 'mdast-util-from-markdown'
2593
- *
2594
- * const extensions = [micromarkReferenceExtension()]
2595
- * const ast = fromMarkdown(text, { extensions })
2596
- * ```
2597
- *
2598
- * @author Incremark Team
2599
- * @license MIT
2600
- */
2601
3311
 
2602
- export { BlockTransformer, DEFAULT_ATTR_BLACKLIST as HTML_ATTR_BLACKLIST, DEFAULT_PROTOCOL_BLACKLIST as HTML_PROTOCOL_BLACKLIST, DEFAULT_TAG_BLACKLIST as HTML_TAG_BLACKLIST, IncremarkParser, allPlugins, calculateLineOffset, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createHtmlTreeTransformer, createIncremarkParser, createInitialContext, createPlugin, defaultPlugins, detectContainer, detectContainerEnd, detectFenceEnd, detectFenceStart, detectHtmlContentType, findHtmlElementsByTag, generateId, htmlElementToString, htmlTreeExtension, imagePlugin, isBlockBoundary, isBlockquoteStart, isEmptyLine, isHeading, isHtmlBlock, isHtmlElementNode, isListItemStart, isTableDelimiter, isThematicBreak, joinLines, mathPlugin, mermaidPlugin, parseHtmlFragment, parseHtmlTag, resetIdCounter, sliceAst, splitLines, thematicBreakPlugin, transformHtmlNodes, updateContext, walkHtmlElements };
3312
+ export { BlockTransformer, IncremarkParser, MarkedAstBuilder, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createIncremarkParser, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin };
2603
3313
  //# sourceMappingURL=index.js.map
2604
3314
  //# sourceMappingURL=index.js.map