@incremark/core 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ import { gfm } from 'micromark-extension-gfm';
4
4
  import { gfmFootnoteFromMarkdown } from 'mdast-util-gfm-footnote';
5
5
  import { math } from 'micromark-extension-math';
6
6
  import { mathFromMarkdown } from 'mdast-util-math';
7
+ import { directive } from 'micromark-extension-directive';
8
+ import { directiveFromMarkdown } from 'mdast-util-directive';
7
9
  import { codes, constants, types } from 'micromark-util-symbol';
8
10
  import { markdownLineEndingOrSpace } from 'micromark-util-character';
9
11
  import { factoryDestination } from 'micromark-factory-destination';
@@ -12,10 +14,699 @@ import { factoryLabel } from 'micromark-factory-label';
12
14
  import { factoryWhitespace } from 'micromark-factory-whitespace';
13
15
  import { gfmFootnote } from 'micromark-extension-gfm-footnote';
14
16
  import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
15
- import { directive } from 'micromark-extension-directive';
16
- import { directiveFromMarkdown } from 'mdast-util-directive';
17
17
 
18
- // src/parser/IncremarkParser.ts
18
+ // src/detector/index.ts
19
+ var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
20
+ var RE_EMPTY_LINE = /^\s*$/;
21
+ var RE_HEADING = /^#{1,6}\s/;
22
+ var RE_THEMATIC_BREAK = /^(\*{3,}|-{3,}|_{3,})\s*$/;
23
+ var RE_BLOCKQUOTE = /^\s{0,3}>/;
24
+ var RE_ESCAPE_SPECIAL = /[.*+?^${}()|[\]\\]/g;
25
+ var RE_FOOTNOTE_DEFINITION = /^\[\^([^\]]+)\]:\s/;
26
+ var RE_FOOTNOTE_CONTINUATION = /^(?: |\t)/;
27
+ var fenceEndPatternCache = /* @__PURE__ */ new Map();
28
+ var containerPatternCache = /* @__PURE__ */ new Map();
29
+ function detectFenceStart(line) {
30
+ const match = line.match(RE_FENCE_START);
31
+ if (match) {
32
+ const fence = match[2];
33
+ const char = fence[0];
34
+ return { char, length: fence.length };
35
+ }
36
+ return null;
37
+ }
38
+ function detectFenceEnd(line, context) {
39
+ if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
40
+ return false;
41
+ }
42
+ const cacheKey = `${context.fenceChar}-${context.fenceLength}`;
43
+ let pattern = fenceEndPatternCache.get(cacheKey);
44
+ if (!pattern) {
45
+ pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
46
+ fenceEndPatternCache.set(cacheKey, pattern);
47
+ }
48
+ return pattern.test(line);
49
+ }
50
+ function isEmptyLine(line) {
51
+ return RE_EMPTY_LINE.test(line);
52
+ }
53
+ function isSetextHeadingUnderline(line, prevLine) {
54
+ const trimmed = line.trim();
55
+ if (!/^={3,}$|^-{3,}$/.test(trimmed)) {
56
+ return false;
57
+ }
58
+ if (!prevLine) {
59
+ return false;
60
+ }
61
+ const trimmedPrev = prevLine.trim();
62
+ if (trimmedPrev === "") {
63
+ return false;
64
+ }
65
+ if (/^#{1,6}\s/.test(trimmedPrev) || /^(\*{3,}|-{3,}|_{3,})\s*$/.test(trimmedPrev)) {
66
+ return false;
67
+ }
68
+ if (/^(\s*)([-*+])\s/.test(trimmedPrev) || /^(\s*)(\d{1,9})[.)]\s/.test(trimmedPrev)) {
69
+ return false;
70
+ }
71
+ if (/^\s{0,3}>/.test(trimmedPrev)) {
72
+ return false;
73
+ }
74
+ if (/^(\s*)(`{3,}|~{3,})/.test(trimmedPrev)) {
75
+ return false;
76
+ }
77
+ const underlineIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
78
+ if (underlineIndent > 3) {
79
+ return false;
80
+ }
81
+ const contentIndent = prevLine.match(/^(\s*)/)?.[1].length ?? 0;
82
+ if (contentIndent > 3) {
83
+ return false;
84
+ }
85
+ return true;
86
+ }
87
+ function isHeading(line) {
88
+ return RE_HEADING.test(line);
89
+ }
90
+ function isThematicBreak(line) {
91
+ return RE_THEMATIC_BREAK.test(line.trim());
92
+ }
93
+ function isListItemStart(line) {
94
+ const hasListMarker = /^(\s*)([-*+]|\d{1,9}[.)])/.test(line);
95
+ if (!hasListMarker) {
96
+ return null;
97
+ }
98
+ const match = line.match(/^(\s*)([-*+]|\d{1,9}[.)])(.*)/);
99
+ if (match) {
100
+ const indent = match[1].length;
101
+ const marker = match[2];
102
+ const rest = match[3];
103
+ if (rest.trim()) {
104
+ const isOrdered = /^\d{1,9}[.)]/.test(marker);
105
+ return { ordered: isOrdered, indent };
106
+ }
107
+ if (/^\s+$/.test(rest)) {
108
+ const isOrdered = /^\d{1,9}[.)]/.test(marker);
109
+ return { ordered: isOrdered, indent };
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ function isBlockquoteStart(line) {
115
+ return RE_BLOCKQUOTE.test(line);
116
+ }
117
+ function isFootnoteDefinitionStart(line) {
118
+ return RE_FOOTNOTE_DEFINITION.test(line);
119
+ }
120
+ function isFootnoteContinuation(line) {
121
+ return RE_FOOTNOTE_CONTINUATION.test(line);
122
+ }
123
+ function detectContainer(line, config) {
124
+ const marker = config?.marker || ":";
125
+ const minLength = config?.minMarkerLength || 3;
126
+ const cacheKey = `${marker}-${minLength}`;
127
+ let pattern = containerPatternCache.get(cacheKey);
128
+ if (!pattern) {
129
+ const escapedMarker = marker.replace(RE_ESCAPE_SPECIAL, "\\$&");
130
+ pattern = new RegExp(
131
+ `^(\\s*)(${escapedMarker}{${minLength},})(?:\\s*(\\w[\\w-]*))?(?:\\{[^}]*\\})?(?:\\s+(.*))?\\s*$`
132
+ );
133
+ containerPatternCache.set(cacheKey, pattern);
134
+ }
135
+ const match = line.match(pattern);
136
+ if (!match) {
137
+ return null;
138
+ }
139
+ const markerLength = match[2].length;
140
+ const name = match[3] || "";
141
+ const isEnd = !name && !match[4];
142
+ if (!isEnd && config?.allowedNames && config.allowedNames.length > 0) {
143
+ if (!config.allowedNames.includes(name)) {
144
+ return null;
145
+ }
146
+ }
147
+ return { name, markerLength, isEnd };
148
+ }
149
+ function detectContainerEnd(line, context, config) {
150
+ if (!context.inContainer || !context.containerMarkerLength) {
151
+ return false;
152
+ }
153
+ const result = detectContainer(line, config);
154
+ if (!result) {
155
+ return false;
156
+ }
157
+ return result.isEnd && result.markerLength >= context.containerMarkerLength;
158
+ }
159
+ var CodeContextUpdater = class {
160
+ update(line, context) {
161
+ const newContext = { ...context };
162
+ if (context.inFencedCode) {
163
+ if (detectFenceEnd(line, context)) {
164
+ newContext.inFencedCode = false;
165
+ newContext.fenceChar = void 0;
166
+ newContext.fenceLength = void 0;
167
+ return newContext;
168
+ }
169
+ return null;
170
+ }
171
+ const fence = detectFenceStart(line);
172
+ if (fence) {
173
+ newContext.inFencedCode = true;
174
+ newContext.fenceChar = fence.char;
175
+ newContext.fenceLength = fence.length;
176
+ return newContext;
177
+ }
178
+ return null;
179
+ }
180
+ };
181
+ var ContainerContextUpdater = class {
182
+ update(line, context, config) {
183
+ if (config === void 0) {
184
+ return null;
185
+ }
186
+ const newContext = { ...context };
187
+ if (context.inContainer) {
188
+ if (detectContainerEnd(line, context, config)) {
189
+ newContext.containerDepth = context.containerDepth - 1;
190
+ if (newContext.containerDepth === 0) {
191
+ newContext.inContainer = false;
192
+ newContext.containerMarkerLength = void 0;
193
+ newContext.containerName = void 0;
194
+ }
195
+ return newContext;
196
+ }
197
+ const nested = detectContainer(line, config);
198
+ if (nested && !nested.isEnd) {
199
+ newContext.containerDepth = context.containerDepth + 1;
200
+ return newContext;
201
+ }
202
+ return newContext;
203
+ } else {
204
+ const container = detectContainer(line, config);
205
+ if (container && !container.isEnd) {
206
+ newContext.inContainer = true;
207
+ newContext.containerMarkerLength = container.markerLength;
208
+ newContext.containerName = container.name;
209
+ newContext.containerDepth = 1;
210
+ return newContext;
211
+ }
212
+ }
213
+ return null;
214
+ }
215
+ };
216
+ var FootnoteContextUpdater = class {
217
+ update(line, context) {
218
+ const newContext = { ...context };
219
+ if (!context.inFootnote && isFootnoteDefinitionStart(line)) {
220
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
221
+ newContext.inFootnote = true;
222
+ newContext.footnoteIdentifier = identifier;
223
+ return newContext;
224
+ }
225
+ if (context.inFootnote) {
226
+ if (isFootnoteDefinitionStart(line)) {
227
+ const identifier = line.match(RE_FOOTNOTE_DEFINITION)?.[1];
228
+ newContext.footnoteIdentifier = identifier;
229
+ return newContext;
230
+ }
231
+ if (isEmptyLine(line)) {
232
+ return { ...context };
233
+ }
234
+ const listItem = isListItemStart(line);
235
+ if (listItem) {
236
+ if (listItem.indent === 0) {
237
+ newContext.inFootnote = false;
238
+ newContext.footnoteIdentifier = void 0;
239
+ } else {
240
+ return { ...context };
241
+ }
242
+ return null;
243
+ }
244
+ if (isHeading(line) || detectFenceStart(line) || isBlockquoteStart(line)) {
245
+ newContext.inFootnote = false;
246
+ newContext.footnoteIdentifier = void 0;
247
+ return newContext;
248
+ }
249
+ if (isFootnoteContinuation(line)) {
250
+ return { ...context };
251
+ }
252
+ newContext.inFootnote = false;
253
+ newContext.footnoteIdentifier = void 0;
254
+ return newContext;
255
+ }
256
+ return null;
257
+ }
258
+ };
259
+ var ListContextUpdater = class {
260
+ /**
261
+ * 检测是否是列表项的延续内容(缩进内容或空行)
262
+ */
263
+ isListContinuation(line, listIndent) {
264
+ if (isEmptyLine(line)) {
265
+ return true;
266
+ }
267
+ const contentIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
268
+ return contentIndent > listIndent;
269
+ }
270
+ update(line, context) {
271
+ const newContext = { ...context };
272
+ const listItem = isListItemStart(line);
273
+ if (context.inList) {
274
+ if (context.listMayEnd) {
275
+ if (listItem) {
276
+ if (listItem.ordered === context.listOrdered && listItem.indent === context.listIndent) {
277
+ newContext.listMayEnd = false;
278
+ return newContext;
279
+ }
280
+ newContext.listOrdered = listItem.ordered;
281
+ newContext.listIndent = listItem.indent;
282
+ newContext.listMayEnd = false;
283
+ return newContext;
284
+ } else if (this.isListContinuation(line, context.listIndent ?? 0)) {
285
+ newContext.listMayEnd = isEmptyLine(line);
286
+ return newContext;
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
+ } else {
295
+ if (listItem) {
296
+ return null;
297
+ } else if (isEmptyLine(line)) {
298
+ newContext.listMayEnd = true;
299
+ return newContext;
300
+ } else if (this.isListContinuation(line, context.listIndent ?? 0)) {
301
+ return null;
302
+ } else {
303
+ newContext.inList = false;
304
+ newContext.listOrdered = void 0;
305
+ newContext.listIndent = void 0;
306
+ newContext.listMayEnd = false;
307
+ return newContext;
308
+ }
309
+ }
310
+ } else {
311
+ if (listItem) {
312
+ newContext.inList = true;
313
+ newContext.listOrdered = listItem.ordered;
314
+ newContext.listIndent = listItem.indent;
315
+ newContext.listMayEnd = false;
316
+ return newContext;
317
+ }
318
+ }
319
+ return null;
320
+ }
321
+ };
322
+ function createInitialContext() {
323
+ return {
324
+ inFencedCode: false,
325
+ listDepth: 0,
326
+ blockquoteDepth: 0,
327
+ inContainer: false,
328
+ containerDepth: 0,
329
+ inList: false,
330
+ inFootnote: false,
331
+ footnoteIdentifier: void 0
332
+ };
333
+ }
334
+ var ContextManager = class {
335
+ updaters = [
336
+ new CodeContextUpdater(),
337
+ new ContainerContextUpdater(),
338
+ new FootnoteContextUpdater(),
339
+ new ListContextUpdater()
340
+ ];
341
+ /**
342
+ * 更新上下文(处理一行后)
343
+ *
344
+ * @param line 当前行
345
+ * @param context 当前上下文
346
+ * @param containerConfig 容器配置
347
+ * @returns 更新后的上下文
348
+ */
349
+ update(line, context, containerConfig) {
350
+ const config = containerConfig === true ? {} : containerConfig === false ? void 0 : containerConfig;
351
+ for (const updater of this.updaters) {
352
+ const result = updater.update(line, context, config);
353
+ if (result !== null) {
354
+ return result;
355
+ }
356
+ }
357
+ return { ...context };
358
+ }
359
+ };
360
+ var contextManager = new ContextManager();
361
+ function updateContext(line, context, containerConfig) {
362
+ return contextManager.update(line, context, containerConfig);
363
+ }
364
+
365
+ // src/parser/boundary/BoundaryDetector.ts
366
+ var ContainerBoundaryChecker = class {
367
+ constructor(containerConfig) {
368
+ this.containerConfig = containerConfig;
369
+ }
370
+ check(lineIndex, context, lines) {
371
+ const line = lines[lineIndex];
372
+ if (!context.inContainer) {
373
+ return -1;
374
+ }
375
+ if (this.containerConfig !== void 0) {
376
+ const containerEnd = detectContainerEnd(line, context, this.containerConfig);
377
+ if (containerEnd) {
378
+ return lineIndex - 1;
379
+ }
380
+ }
381
+ return -1;
382
+ }
383
+ };
384
+ var ListBoundaryChecker = class {
385
+ check(lineIndex, context, lines) {
386
+ if (!context.inList) {
387
+ return -1;
388
+ }
389
+ if (!context.listMayEnd) {
390
+ return -1;
391
+ }
392
+ const line = lines[lineIndex];
393
+ const listItem = isListItemStart(line);
394
+ const contentIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
395
+ const isListContent = contentIndent > (context.listIndent ?? 0);
396
+ if (!listItem && !isListContent && !isEmptyLine(line)) {
397
+ return lineIndex - 1;
398
+ }
399
+ return -1;
400
+ }
401
+ };
402
+ var FootnoteBoundaryChecker = class {
403
+ check(lineIndex, context, lines) {
404
+ const line = lines[lineIndex];
405
+ const prevLine = lines[lineIndex - 1];
406
+ if (isFootnoteDefinitionStart(prevLine)) {
407
+ if (isEmptyLine(line) || isFootnoteContinuation(line)) {
408
+ return -1;
409
+ }
410
+ if (isFootnoteDefinitionStart(line)) {
411
+ return lineIndex - 1;
412
+ }
413
+ }
414
+ if (!isEmptyLine(prevLine) && isFootnoteContinuation(prevLine)) {
415
+ return -1;
416
+ }
417
+ if (!isEmptyLine(prevLine) && isFootnoteDefinitionStart(line) && !isFootnoteDefinitionStart(prevLine)) {
418
+ return lineIndex - 1;
419
+ }
420
+ return -1;
421
+ }
422
+ };
423
+ var NewBlockBoundaryChecker = class {
424
+ check(lineIndex, context, lines) {
425
+ const line = lines[lineIndex];
426
+ const prevLine = lines[lineIndex - 1];
427
+ if (isEmptyLine(prevLine)) {
428
+ return -1;
429
+ }
430
+ if (isSetextHeadingUnderline(line, prevLine)) {
431
+ return lineIndex - 1;
432
+ }
433
+ if (isHeading(line)) {
434
+ return lineIndex - 1;
435
+ }
436
+ if (detectFenceStart(line)) {
437
+ return lineIndex - 1;
438
+ }
439
+ if (isBlockquoteStart(line) && !isBlockquoteStart(prevLine)) {
440
+ return lineIndex - 1;
441
+ }
442
+ if (!context.inList && isListItemStart(line) && !isListItemStart(prevLine)) {
443
+ return lineIndex - 1;
444
+ }
445
+ return -1;
446
+ }
447
+ };
448
+ var EmptyLineBoundaryChecker = class {
449
+ check(lineIndex, context, lines) {
450
+ const line = lines[lineIndex];
451
+ const prevLine = lines[lineIndex - 1];
452
+ if (isEmptyLine(line) && !isEmptyLine(prevLine) && !context.inList) {
453
+ return lineIndex;
454
+ }
455
+ return -1;
456
+ }
457
+ };
458
+ var BoundaryDetector = class {
459
+ containerConfig;
460
+ checkers;
461
+ constructor(config = {}) {
462
+ this.containerConfig = config.containers;
463
+ this.checkers = [
464
+ new ContainerBoundaryChecker(this.containerConfig),
465
+ new ListBoundaryChecker(),
466
+ new FootnoteBoundaryChecker(),
467
+ new NewBlockBoundaryChecker(),
468
+ new EmptyLineBoundaryChecker()
469
+ ];
470
+ }
471
+ /**
472
+ * 查找稳定边界
473
+ * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
474
+ *
475
+ * @param lines 所有行
476
+ * @param startLine 起始行
477
+ * @param context 当前上下文
478
+ * @returns 稳定边界结果
479
+ */
480
+ findStableBoundary(lines, startLine, context) {
481
+ let stableLine = -1;
482
+ let stableContext = context;
483
+ let tempContext = { ...context };
484
+ for (let i = startLine; i < lines.length; i++) {
485
+ const line = lines[i];
486
+ const wasInFencedCode = tempContext.inFencedCode;
487
+ const wasInContainer = tempContext.inContainer;
488
+ const wasContainerDepth = tempContext.containerDepth;
489
+ tempContext = updateContext(line, tempContext, this.containerConfig);
490
+ if (wasInFencedCode && !tempContext.inFencedCode) {
491
+ if (i < lines.length - 1) {
492
+ stableLine = i;
493
+ stableContext = { ...tempContext };
494
+ }
495
+ continue;
496
+ }
497
+ if (tempContext.inFencedCode) {
498
+ continue;
499
+ }
500
+ if (wasInContainer && wasContainerDepth === 1 && !tempContext.inContainer) {
501
+ if (i < lines.length - 1) {
502
+ stableLine = i;
503
+ stableContext = { ...tempContext };
504
+ }
505
+ continue;
506
+ }
507
+ if (tempContext.inContainer) {
508
+ continue;
509
+ }
510
+ const stablePoint = this.checkStability(i, tempContext, lines);
511
+ if (stablePoint >= 0) {
512
+ stableLine = stablePoint;
513
+ stableContext = { ...tempContext };
514
+ }
515
+ }
516
+ return { line: stableLine, context: stableContext };
517
+ }
518
+ /**
519
+ * 检查指定行是否是稳定边界
520
+ * 使用责任链模式,依次调用各个检查器
521
+ *
522
+ * @param lineIndex 行索引
523
+ * @param context 当前上下文
524
+ * @param lines 所有行
525
+ * @returns 稳定边界行号,如果不是稳定边界返回 -1
526
+ */
527
+ checkStability(lineIndex, context, lines) {
528
+ if (lineIndex === 0) {
529
+ return -1;
530
+ }
531
+ const line = lines[lineIndex];
532
+ const prevLine = lines[lineIndex - 1];
533
+ if (context.inFootnote) {
534
+ if (isFootnoteDefinitionStart(prevLine) && !isEmptyLine(line)) {
535
+ if (isFootnoteContinuation(line)) {
536
+ return -1;
537
+ }
538
+ return lineIndex - 1;
539
+ }
540
+ if (isEmptyLine(prevLine) && (isEmptyLine(line) || isFootnoteContinuation(line))) {
541
+ return -1;
542
+ }
543
+ return -1;
544
+ }
545
+ if (isHeading(prevLine) || isThematicBreak(prevLine)) {
546
+ return lineIndex - 1;
547
+ }
548
+ if (isSetextHeadingUnderline(prevLine, lines[lineIndex - 2])) {
549
+ return lineIndex - 1;
550
+ }
551
+ if (lineIndex >= lines.length - 1) {
552
+ return -1;
553
+ }
554
+ for (const checker of this.checkers) {
555
+ const stablePoint = checker.check(lineIndex, context, lines);
556
+ if (stablePoint >= 0) {
557
+ return stablePoint;
558
+ }
559
+ }
560
+ return -1;
561
+ }
562
+ };
563
+
564
+ // src/utils/index.ts
565
+ function isDefinitionNode(node) {
566
+ return node.type === "definition";
567
+ }
568
+ function isFootnoteDefinitionNode(node) {
569
+ return node.type === "footnoteDefinition";
570
+ }
571
+ function traverseAst(node, visitor) {
572
+ const stopEarly = visitor(node);
573
+ if (stopEarly === true) {
574
+ return;
575
+ }
576
+ if ("children" in node && Array.isArray(node.children)) {
577
+ for (const child of node.children) {
578
+ traverseAst(child, visitor);
579
+ }
580
+ }
581
+ }
582
+ function collectAstNodes(node, predicate) {
583
+ const results = [];
584
+ traverseAst(node, (node2) => {
585
+ if (predicate(node2)) {
586
+ results.push(node2);
587
+ }
588
+ });
589
+ return results;
590
+ }
591
+
592
+ // src/parser/manager/DefinitionManager.ts
593
+ var DefinitionManager = class {
594
+ definitions = {};
595
+ /**
596
+ * 从已完成的 blocks 中提取 definitions
597
+ *
598
+ * @param blocks 已完成的块
599
+ */
600
+ extractFromBlocks(blocks) {
601
+ for (const block of blocks) {
602
+ const newDefinitions = this.findDefinitions(block);
603
+ this.definitions = {
604
+ ...this.definitions,
605
+ ...newDefinitions
606
+ };
607
+ }
608
+ }
609
+ /**
610
+ * 从 block 中提取 definitions
611
+ *
612
+ * @param block 解析块
613
+ * @returns Definition 映射表
614
+ */
615
+ findDefinitions(block) {
616
+ const definitions = collectAstNodes(block.node, isDefinitionNode);
617
+ return definitions.reduce((acc, node) => {
618
+ acc[node.identifier] = node;
619
+ return acc;
620
+ }, {});
621
+ }
622
+ /**
623
+ * 获取所有 definitions
624
+ *
625
+ * @returns Definition 映射表
626
+ */
627
+ getAll() {
628
+ return this.definitions;
629
+ }
630
+ /**
631
+ * 清空所有 definitions
632
+ */
633
+ clear() {
634
+ this.definitions = {};
635
+ }
636
+ };
637
+
638
+ // src/parser/manager/FootnoteManager.ts
639
+ var FootnoteManager = class {
640
+ definitions = {};
641
+ referenceOrder = [];
642
+ /**
643
+ * 从已完成的 blocks 中提取 footnote definitions
644
+ *
645
+ * @param blocks 已完成的块
646
+ */
647
+ extractDefinitionsFromBlocks(blocks) {
648
+ for (const block of blocks) {
649
+ const newDefinitions = this.findFootnoteDefinitions(block);
650
+ this.definitions = {
651
+ ...this.definitions,
652
+ ...newDefinitions
653
+ };
654
+ }
655
+ }
656
+ /**
657
+ * 从 block 中提取 footnote definitions
658
+ *
659
+ * @param block 解析块
660
+ * @returns Footnote Definition 映射表
661
+ */
662
+ findFootnoteDefinitions(block) {
663
+ const definitions = collectAstNodes(block.node, isFootnoteDefinitionNode);
664
+ return definitions.reduce((acc, node) => {
665
+ acc[node.identifier] = node;
666
+ return acc;
667
+ }, {});
668
+ }
669
+ /**
670
+ * 收集 AST 中的脚注引用(按出现顺序)
671
+ *
672
+ * @param nodes AST 节点列表
673
+ */
674
+ collectReferences(nodes) {
675
+ nodes.forEach((node) => {
676
+ traverseAst(node, (n) => {
677
+ if (n.type === "footnoteReference") {
678
+ const identifier = n.identifier;
679
+ if (!this.referenceOrder.includes(identifier)) {
680
+ this.referenceOrder.push(identifier);
681
+ }
682
+ }
683
+ });
684
+ });
685
+ }
686
+ /**
687
+ * 获取所有 footnote definitions
688
+ *
689
+ * @returns Footnote Definition 映射表
690
+ */
691
+ getDefinitions() {
692
+ return this.definitions;
693
+ }
694
+ /**
695
+ * 获取脚注引用顺序
696
+ *
697
+ * @returns 脚注引用顺序
698
+ */
699
+ getReferenceOrder() {
700
+ return this.referenceOrder;
701
+ }
702
+ /**
703
+ * 清空所有 footnote definitions 和引用顺序
704
+ */
705
+ clear() {
706
+ this.definitions = {};
707
+ this.referenceOrder = [];
708
+ }
709
+ };
19
710
 
20
711
  // src/extensions/html-extension/index.ts
21
712
  var DEFAULT_TAG_BLACKLIST = [
@@ -403,65 +1094,6 @@ function transformHtmlNodes(ast, options = {}) {
403
1094
  children: processHtmlNodesInArray(ast.children, options)
404
1095
  };
405
1096
  }
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);
426
- }
427
- }
428
- }
429
- function findHtmlElementsByTag(root, tagName) {
430
- const result = [];
431
- walkHtmlElements(root, (node) => {
432
- if (node.tagName === tagName.toLowerCase()) {
433
- result.push(node);
434
- }
435
- });
436
- return result;
437
- }
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);
454
- }
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());
461
- }
462
- function escapeHtml(text) {
463
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
464
- }
465
1097
  function micromarkReferenceExtension() {
466
1098
  return {
467
1099
  // 在 text 中使用 codes.rightSquareBracket 键覆盖 labelEnd
@@ -862,440 +1494,51 @@ function tokenizePotentialGfmFootnoteCallIncremental(effects, ok, nok) {
862
1494
  }
863
1495
  }
864
1496
 
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;
887
- }
888
- function detectFenceEnd(line, context) {
889
- if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
890
- return false;
891
- }
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);
897
- }
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);
905
- }
906
- function isThematicBreak(line) {
907
- return RE_THEMATIC_BREAK.test(line.trim());
908
- }
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;
929
- }
930
- function isBlockquoteStart(line) {
931
- return RE_BLOCKQUOTE.test(line);
932
- }
933
- function isHtmlBlock(line) {
934
- return RE_HTML_BLOCK_1.test(line) || RE_HTML_BLOCK_2.test(line);
935
- }
936
- function isTableDelimiter(line) {
937
- return RE_TABLE_DELIMITER.test(line.trim());
938
- }
939
- function isFootnoteDefinitionStart(line) {
940
- return RE_FOOTNOTE_DEFINITION.test(line);
941
- }
942
- function isFootnoteContinuation(line) {
943
- return RE_FOOTNOTE_CONTINUATION.test(line);
944
- }
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) {
959
- return null;
960
- }
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;
974
- }
975
- const result = detectContainer(line, config);
976
- if (!result) {
977
- return false;
978
- }
979
- return result.isEnd && result.markerLength >= context.containerMarkerLength;
980
- }
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;
987
- }
988
- if (isHeading(currentLine) && !isEmptyLine(prevLine)) {
989
- return true;
990
- }
991
- if (isThematicBreak(currentLine)) {
992
- return true;
993
- }
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
- };
1010
- }
1011
- function isListContinuation(line, listIndent) {
1012
- if (isEmptyLine(line)) {
1013
- return true;
1014
- }
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;
1061
- }
1062
- }
1063
- }
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;
1082
- }
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
- }
1113
- }
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;
1121
- }
1122
- }
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;
1133
- }
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
- }
1157
- }
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;
1173
- }
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
- }
1188
-
1189
- // src/parser/IncremarkParser.ts
1190
- var IncremarkParser = class {
1191
- buffer = "";
1192
- lines = [];
1193
- /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
1194
- lineOffsets = [0];
1195
- completedBlocks = [];
1196
- pendingStartLine = 0;
1197
- blockIdCounter = 0;
1198
- context;
1497
+ // src/parser/ast/AstBuilder.ts
1498
+ var INLINE_CONTAINER_TYPES = [
1499
+ "paragraph",
1500
+ "heading",
1501
+ "tableCell",
1502
+ "delete",
1503
+ "emphasis",
1504
+ "strong",
1505
+ "link",
1506
+ "linkReference"
1507
+ ];
1508
+ function isInlineContainer(node) {
1509
+ return INLINE_CONTAINER_TYPES.includes(node.type);
1510
+ }
1511
+ var AstBuilder = class {
1199
1512
  options;
1200
- /** 缓存的容器配置,避免重复计算 */
1201
1513
  containerConfig;
1202
- /** 缓存的 HTML 树配置,避免重复计算 */
1203
1514
  htmlTreeConfig;
1204
- /** 上次 append 返回的 pending blocks,用于 getAst 复用 */
1205
- lastPendingBlocks = [];
1206
- /** Definition 映射表(用于引用式图片和链接) */
1207
- definitionMap = {};
1208
- /** Footnote Definition 映射表 */
1209
- footnoteDefinitionMap = {};
1210
- /** Footnote Reference 出现顺序(按引用在文档中的顺序) */
1211
- footnoteReferenceOrder = [];
1212
1515
  constructor(options = {}) {
1213
- this.options = {
1214
- gfm: true,
1215
- ...options
1216
- };
1217
- this.context = createInitialContext();
1218
- this.containerConfig = this.computeContainerConfig();
1219
- this.htmlTreeConfig = this.computeHtmlTreeConfig();
1516
+ this.options = options;
1517
+ this.containerConfig = this.computeContainerConfig(options);
1518
+ this.htmlTreeConfig = this.computeHtmlTreeConfig(options);
1220
1519
  }
1221
- generateBlockId() {
1222
- return `block-${++this.blockIdCounter}`;
1223
- }
1224
- computeContainerConfig() {
1225
- const containers = this.options.containers;
1520
+ /**
1521
+ * 计算容器配置
1522
+ */
1523
+ computeContainerConfig(options) {
1524
+ const containers = options.containers;
1226
1525
  if (!containers) return void 0;
1227
1526
  return containers === true ? {} : containers;
1228
1527
  }
1229
- computeHtmlTreeConfig() {
1230
- const htmlTree = this.options.htmlTree;
1528
+ /**
1529
+ * 计算 HTML 树配置
1530
+ */
1531
+ computeHtmlTreeConfig(options) {
1532
+ const htmlTree = options.htmlTree;
1231
1533
  if (!htmlTree) return void 0;
1232
1534
  return htmlTree === true ? {} : htmlTree;
1233
1535
  }
1234
1536
  /**
1235
- * HTML 节点转换为纯文本
1236
- * 递归处理 AST 中所有 html 类型的节点
1237
- * - 块级 HTML 节点 → 转换为 paragraph 包含 text
1238
- * - 内联 HTML 节点(在段落内部)→ 转换为 text 节点
1537
+ * 解析文本为 AST
1538
+ *
1539
+ * @param text Markdown 文本
1540
+ * @returns AST
1239
1541
  */
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
1542
  parse(text) {
1300
1543
  const extensions = [];
1301
1544
  const mdastExtensions = [];
@@ -1329,67 +1572,160 @@ var IncremarkParser = class {
1329
1572
  }
1330
1573
  return ast;
1331
1574
  }
1332
- 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
- }
1575
+ /**
1576
+ * HTML 节点转换为纯文本(当未启用 HTML 树转换时)
1577
+ *
1578
+ * @param ast AST
1579
+ * @returns 转换后的 AST
1580
+ */
1581
+ convertHtmlToText(ast) {
1582
+ return {
1583
+ ...ast,
1584
+ children: this.processBlockChildren(ast.children)
1585
+ };
1343
1586
  }
1344
- findDefinition(block) {
1345
- const definitions = [];
1346
- function findDefinitionRecursive(node) {
1347
- if (isDefinitionNode(node)) {
1348
- definitions.push(node);
1587
+ /**
1588
+ * 处理块级节点
1589
+ */
1590
+ processBlockChildren(children) {
1591
+ return children.map((node) => {
1592
+ if (node.type === "html") {
1593
+ return this.convertBlockHtmlToParagraph(node);
1349
1594
  }
1350
1595
  if ("children" in node && Array.isArray(node.children)) {
1351
- for (const child of node.children) {
1352
- findDefinitionRecursive(child);
1353
- }
1596
+ const parent = node;
1597
+ const children2 = isInlineContainer(node) ? this.processInlineChildren(parent.children) : this.processBlockChildren(parent.children);
1598
+ return {
1599
+ ...parent,
1600
+ children: children2
1601
+ };
1354
1602
  }
1355
- }
1356
- findDefinitionRecursive(block.node);
1357
- return definitions.reduce((acc, node) => {
1358
- acc[node.identifier] = node;
1359
- return acc;
1360
- }, {});
1603
+ return node;
1604
+ });
1361
1605
  }
1362
- findFootnoteDefinition(block) {
1363
- const footnoteDefinitions = [];
1364
- function findFootnoteDefinition(node) {
1365
- if (isFootnoteDefinitionNode(node)) {
1366
- footnoteDefinitions.push(node);
1606
+ /**
1607
+ * 处理内联节点
1608
+ */
1609
+ processInlineChildren(children) {
1610
+ return children.map((node) => {
1611
+ const n = node;
1612
+ if (n.type === "html") {
1613
+ return this.convertInlineHtmlToText(n);
1614
+ }
1615
+ if ("children" in n && Array.isArray(n.children)) {
1616
+ const parent = n;
1617
+ return {
1618
+ ...parent,
1619
+ children: this.processInlineChildren(parent.children)
1620
+ };
1367
1621
  }
1622
+ return n;
1623
+ });
1624
+ }
1625
+ /**
1626
+ * 将块级 HTML 节点转换为段落
1627
+ */
1628
+ convertBlockHtmlToParagraph(htmlNode) {
1629
+ const textNode = {
1630
+ type: "text",
1631
+ value: htmlNode.value
1632
+ };
1633
+ const paragraphNode = {
1634
+ type: "paragraph",
1635
+ children: [textNode],
1636
+ position: htmlNode.position
1637
+ };
1638
+ return paragraphNode;
1639
+ }
1640
+ /**
1641
+ * 将内联 HTML 节点转换为纯文本节点
1642
+ */
1643
+ convertInlineHtmlToText(htmlNode) {
1644
+ return {
1645
+ type: "text",
1646
+ value: htmlNode.value,
1647
+ position: htmlNode.position
1648
+ };
1649
+ }
1650
+ /**
1651
+ * 将 AST 节点转换为 ParsedBlock
1652
+ *
1653
+ * @param nodes AST 节点列表
1654
+ * @param startOffset 起始偏移量
1655
+ * @param rawText 原始文本
1656
+ * @param status 块状态
1657
+ * @param generateBlockId 生成块 ID 的函数
1658
+ * @returns ParsedBlock 列表
1659
+ */
1660
+ nodesToBlocks(nodes, startOffset, rawText, status, generateBlockId) {
1661
+ const blocks = [];
1662
+ let currentOffset = startOffset;
1663
+ for (const node of nodes) {
1664
+ const nodeStart = node.position?.start?.offset ?? currentOffset;
1665
+ const nodeEnd = node.position?.end?.offset ?? currentOffset + 1;
1666
+ const nodeText = rawText.substring(nodeStart - startOffset, nodeEnd - startOffset);
1667
+ blocks.push({
1668
+ id: generateBlockId(),
1669
+ status,
1670
+ node,
1671
+ startOffset: nodeStart,
1672
+ endOffset: nodeEnd,
1673
+ rawText: nodeText
1674
+ });
1675
+ currentOffset = nodeEnd;
1368
1676
  }
1369
- findFootnoteDefinition(block.node);
1370
- return footnoteDefinitions.reduce((acc, node) => {
1371
- acc[node.identifier] = node;
1372
- return acc;
1373
- }, {});
1677
+ return blocks;
1678
+ }
1679
+ };
1680
+
1681
+ // src/parser/IncremarkParser.ts
1682
+ var IncremarkParser = class {
1683
+ buffer = "";
1684
+ lines = [];
1685
+ /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
1686
+ lineOffsets = [0];
1687
+ completedBlocks = [];
1688
+ pendingStartLine = 0;
1689
+ blockIdCounter = 0;
1690
+ context;
1691
+ options;
1692
+ /** 边界检测器 */
1693
+ boundaryDetector;
1694
+ /** AST 构建器 */
1695
+ astBuilder;
1696
+ /** Definition 管理器 */
1697
+ definitionManager;
1698
+ /** Footnote 管理器 */
1699
+ footnoteManager;
1700
+ /** 上次 append 返回的 pending blocks,用于 getAst 复用 */
1701
+ lastPendingBlocks = [];
1702
+ constructor(options = {}) {
1703
+ this.options = {
1704
+ gfm: true,
1705
+ ...options
1706
+ };
1707
+ this.context = createInitialContext();
1708
+ this.astBuilder = new AstBuilder(this.options);
1709
+ this.boundaryDetector = new BoundaryDetector({ containers: this.astBuilder.containerConfig });
1710
+ this.definitionManager = new DefinitionManager();
1711
+ this.footnoteManager = new FootnoteManager();
1712
+ }
1713
+ generateBlockId() {
1714
+ return `block-${++this.blockIdCounter}`;
1715
+ }
1716
+ /**
1717
+ * 更新已完成的 blocks 中的 definitions 和 footnote definitions
1718
+ */
1719
+ updateDefinitionsFromCompletedBlocks(blocks) {
1720
+ this.definitionManager.extractFromBlocks(blocks);
1721
+ this.footnoteManager.extractDefinitionsFromBlocks(blocks);
1374
1722
  }
1375
1723
  /**
1376
1724
  * 收集 AST 中的脚注引用(按出现顺序)
1377
1725
  * 用于确定脚注的显示顺序
1378
1726
  */
1379
1727
  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);
1728
+ this.footnoteManager.collectReferences(nodes);
1393
1729
  }
1394
1730
  /**
1395
1731
  * 增量更新 lines 和 lineOffsets
@@ -1427,169 +1763,12 @@ var IncremarkParser = class {
1427
1763
  * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
1428
1764
  */
1429
1765
  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;
1766
+ const result = this.boundaryDetector.findStableBoundary(
1767
+ this.lines,
1768
+ this.pendingStartLine,
1769
+ this.context
1770
+ );
1771
+ return { line: result.line, contextAtLine: result.context };
1593
1772
  }
1594
1773
  /**
1595
1774
  * 追加新的 chunk 并返回增量更新
@@ -1610,8 +1789,8 @@ var IncremarkParser = class {
1610
1789
  if (stableBoundary >= this.pendingStartLine && stableBoundary >= 0) {
1611
1790
  const stableText = this.lines.slice(this.pendingStartLine, stableBoundary + 1).join("\n");
1612
1791
  const stableOffset = this.getLineOffset(this.pendingStartLine);
1613
- const ast = this.parse(stableText);
1614
- const newBlocks = this.nodesToBlocks(ast.children, stableOffset, stableText, "completed");
1792
+ const ast = this.astBuilder.parse(stableText);
1793
+ const newBlocks = this.astBuilder.nodesToBlocks(ast.children, stableOffset, stableText, "completed", () => this.generateBlockId());
1615
1794
  this.completedBlocks.push(...newBlocks);
1616
1795
  update.completed = newBlocks;
1617
1796
  this.updateDefinitionsFromCompletedBlocks(newBlocks);
@@ -1622,8 +1801,8 @@ var IncremarkParser = class {
1622
1801
  const pendingText = this.lines.slice(this.pendingStartLine).join("\n");
1623
1802
  if (pendingText.trim()) {
1624
1803
  const pendingOffset = this.getLineOffset(this.pendingStartLine);
1625
- const ast = this.parse(pendingText);
1626
- update.pending = this.nodesToBlocks(ast.children, pendingOffset, pendingText, "pending");
1804
+ const ast = this.astBuilder.parse(pendingText);
1805
+ update.pending = this.astBuilder.nodesToBlocks(ast.children, pendingOffset, pendingText, "pending", () => this.generateBlockId());
1627
1806
  }
1628
1807
  }
1629
1808
  this.lastPendingBlocks = update.pending;
@@ -1654,8 +1833,8 @@ var IncremarkParser = class {
1654
1833
  ...pendingBlocks.map((b) => b.node)
1655
1834
  ]
1656
1835
  },
1657
- definitions: { ...this.definitionMap },
1658
- footnoteDefinitions: { ...this.footnoteDefinitionMap }
1836
+ definitions: { ...this.getDefinitionMap() },
1837
+ footnoteDefinitions: { ...this.getFootnoteDefinitionMap() }
1659
1838
  };
1660
1839
  this.options.onChange(state);
1661
1840
  }
@@ -1670,20 +1849,21 @@ var IncremarkParser = class {
1670
1849
  updated: [],
1671
1850
  pending: [],
1672
1851
  ast: { type: "root", children: [] },
1673
- definitions: {},
1674
- footnoteDefinitions: {},
1675
- footnoteReferenceOrder: []
1852
+ definitions: this.getDefinitionMap(),
1853
+ footnoteDefinitions: this.getFootnoteDefinitionMap(),
1854
+ footnoteReferenceOrder: this.getFootnoteReferenceOrder()
1676
1855
  };
1677
1856
  if (this.pendingStartLine < this.lines.length) {
1678
1857
  const remainingText = this.lines.slice(this.pendingStartLine).join("\n");
1679
1858
  if (remainingText.trim()) {
1680
1859
  const remainingOffset = this.getLineOffset(this.pendingStartLine);
1681
- const ast = this.parse(remainingText);
1682
- const finalBlocks = this.nodesToBlocks(
1860
+ const ast = this.astBuilder.parse(remainingText);
1861
+ const finalBlocks = this.astBuilder.nodesToBlocks(
1683
1862
  ast.children,
1684
1863
  remainingOffset,
1685
1864
  remainingText,
1686
- "completed"
1865
+ "completed",
1866
+ () => this.generateBlockId()
1687
1867
  );
1688
1868
  this.completedBlocks.push(...finalBlocks);
1689
1869
  update.completed = finalBlocks;
@@ -1741,19 +1921,19 @@ var IncremarkParser = class {
1741
1921
  * 获取 Definition 映射表(用于引用式图片和链接)
1742
1922
  */
1743
1923
  getDefinitionMap() {
1744
- return { ...this.definitionMap };
1924
+ return this.definitionManager.getAll();
1745
1925
  }
1746
1926
  /**
1747
1927
  * 获取 Footnote Definition 映射表
1748
1928
  */
1749
1929
  getFootnoteDefinitionMap() {
1750
- return { ...this.footnoteDefinitionMap };
1930
+ return this.footnoteManager.getDefinitions();
1751
1931
  }
1752
1932
  /**
1753
1933
  * 获取脚注引用的出现顺序
1754
1934
  */
1755
1935
  getFootnoteReferenceOrder() {
1756
- return [...this.footnoteReferenceOrder];
1936
+ return this.footnoteManager.getReferenceOrder();
1757
1937
  }
1758
1938
  /**
1759
1939
  * 设置状态变化回调(用于 DevTools 等)
@@ -1777,9 +1957,8 @@ var IncremarkParser = class {
1777
1957
  this.blockIdCounter = 0;
1778
1958
  this.context = createInitialContext();
1779
1959
  this.lastPendingBlocks = [];
1780
- this.definitionMap = {};
1781
- this.footnoteDefinitionMap = {};
1782
- this.footnoteReferenceOrder = [];
1960
+ this.definitionManager.clear();
1961
+ this.footnoteManager.clear();
1783
1962
  this.emitChange([]);
1784
1963
  }
1785
1964
  /**
@@ -2599,6 +2778,6 @@ function createPlugin(name, matcher, options = {}) {
2599
2778
  * @license MIT
2600
2779
  */
2601
2780
 
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 };
2781
+ export { BlockTransformer, IncremarkParser, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createIncremarkParser, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin };
2603
2782
  //# sourceMappingURL=index.js.map
2604
2783
  //# sourceMappingURL=index.js.map