@incremark/core 0.2.7 → 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,19 +1,4 @@
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 { directive } from 'micromark-extension-directive';
8
- import { directiveFromMarkdown } from 'mdast-util-directive';
9
- import { codes, constants, types } from 'micromark-util-symbol';
10
- import { markdownLineEndingOrSpace } from 'micromark-util-character';
11
- import { factoryDestination } from 'micromark-factory-destination';
12
- import { factoryTitle } from 'micromark-factory-title';
13
- import { factoryLabel } from 'micromark-factory-label';
14
- import { factoryWhitespace } from 'micromark-factory-whitespace';
15
- import { gfmFootnote } from 'micromark-extension-gfm-footnote';
16
- import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
1
+ import { Lexer, lexer } from 'marked';
17
2
 
18
3
  // src/detector/index.ts
19
4
  var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
@@ -458,6 +443,8 @@ var EmptyLineBoundaryChecker = class {
458
443
  var BoundaryDetector = class {
459
444
  containerConfig;
460
445
  checkers;
446
+ /** 缓存每一行结束时对应的 Context,避免重复计算 */
447
+ contextCache = /* @__PURE__ */ new Map();
461
448
  constructor(config = {}) {
462
449
  this.containerConfig = config.containers;
463
450
  this.checkers = [
@@ -468,6 +455,17 @@ var BoundaryDetector = class {
468
455
  new EmptyLineBoundaryChecker()
469
456
  ];
470
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
+ }
471
469
  /**
472
470
  * 查找稳定边界
473
471
  * 返回稳定边界行号和该行对应的上下文(用于后续更新,避免重复计算)
@@ -480,13 +478,14 @@ var BoundaryDetector = class {
480
478
  findStableBoundary(lines, startLine, context) {
481
479
  let stableLine = -1;
482
480
  let stableContext = context;
483
- let tempContext = { ...context };
481
+ let tempContext = startLine > 0 && this.contextCache.has(startLine - 1) ? { ...this.contextCache.get(startLine - 1) } : { ...context };
484
482
  for (let i = startLine; i < lines.length; i++) {
485
483
  const line = lines[i];
486
484
  const wasInFencedCode = tempContext.inFencedCode;
487
485
  const wasInContainer = tempContext.inContainer;
488
486
  const wasContainerDepth = tempContext.containerDepth;
489
487
  tempContext = updateContext(line, tempContext, this.containerConfig);
488
+ this.contextCache.set(i, { ...tempContext });
490
489
  if (wasInFencedCode && !tempContext.inFencedCode) {
491
490
  if (i < lines.length - 1) {
492
491
  stableLine = i;
@@ -638,6 +637,9 @@ var DefinitionManager = class {
638
637
  // src/parser/manager/FootnoteManager.ts
639
638
  var FootnoteManager = class {
640
639
  definitions = {};
640
+ /** 已完成部分的脚注引用顺序(缓存) */
641
+ completedReferenceOrder = [];
642
+ /** 所有脚注引用顺序(包括 pending 部分) */
641
643
  referenceOrder = [];
642
644
  /**
643
645
  * 从已完成的 blocks 中提取 footnote definitions
@@ -666,9 +668,52 @@ var FootnoteManager = class {
666
668
  return acc;
667
669
  }, {});
668
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
+ }
669
713
  /**
670
714
  * 收集 AST 中的脚注引用(按出现顺序)
671
715
  *
716
+ * @deprecated 使用 collectReferencesFromCompletedBlocks 和 collectReferencesFromPending 代替
672
717
  * @param nodes AST 节点列表
673
718
  */
674
719
  collectReferences(nodes) {
@@ -704,6 +749,7 @@ var FootnoteManager = class {
704
749
  */
705
750
  clear() {
706
751
  this.definitions = {};
752
+ this.completedReferenceOrder = [];
707
753
  this.referenceOrder = [];
708
754
  }
709
755
  };
@@ -985,11 +1031,104 @@ function isHtmlNode(node) {
985
1031
  function hasChildren(node) {
986
1032
  return "children" in node && Array.isArray(node.children);
987
1033
  }
988
- function processHtmlNodesInArray(nodes, options) {
1034
+ function mergeFragmentedHtmlNodes(nodes) {
989
1035
  const result = [];
990
1036
  let i = 0;
991
1037
  while (i < nodes.length) {
992
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];
993
1132
  if (isHtmlNode(node)) {
994
1133
  const contentType = detectHtmlContentType(node.value);
995
1134
  if (contentType === "fragment") {
@@ -1030,8 +1169,8 @@ function processHtmlNodesInArray(nodes, options) {
1030
1169
  let depth = 1;
1031
1170
  let j = i + 1;
1032
1171
  let foundClosing = false;
1033
- while (j < nodes.length && depth > 0) {
1034
- const nextNode = nodes[j];
1172
+ while (j < mergedNodes.length && depth > 0) {
1173
+ const nextNode = mergedNodes[j];
1035
1174
  if (isHtmlNode(nextNode)) {
1036
1175
  const nextType = detectHtmlContentType(nextNode.value);
1037
1176
  if (nextType === "closing") {
@@ -1094,585 +1233,1050 @@ function transformHtmlNodes(ast, options = {}) {
1094
1233
  children: processHtmlNodesInArray(ast.children, options)
1095
1234
  };
1096
1235
  }
1097
- function micromarkReferenceExtension() {
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);
1243
+ }
1244
+ }
1245
+ return extensions;
1246
+ }
1247
+
1248
+ // src/extensions/marked-extensions/explicitDefinitionExtension.ts
1249
+ function createExplicitDefinitionExtension() {
1098
1250
  return {
1099
- // 在 text 中使用 codes.rightSquareBracket 键覆盖 labelEnd
1100
- text: {
1101
- [codes.rightSquareBracket]: {
1102
- name: "labelEnd",
1103
- resolveAll: resolveAllLabelEnd,
1104
- resolveTo: resolveToLabelEnd,
1105
- tokenize: tokenizeLabelEnd,
1106
- // 添加 add: 'before' 确保先被尝试
1107
- add: "before"
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: "" };
1108
1284
  }
1285
+ return void 0;
1286
+ },
1287
+ renderer() {
1288
+ return "";
1109
1289
  }
1110
1290
  };
1111
1291
  }
1112
- function resolveAllLabelEnd(events) {
1113
- let index = -1;
1114
- const newEvents = [];
1115
- while (++index < events.length) {
1116
- const token = events[index][1];
1117
- newEvents.push(events[index]);
1118
- if (token.type === types.labelImage || token.type === types.labelLink || token.type === types.labelEnd) {
1119
- const offset = token.type === types.labelImage ? 4 : 2;
1120
- token.type = types.data;
1121
- index += offset;
1122
- }
1123
- }
1124
- if (events.length !== newEvents.length) {
1125
- events.length = 0;
1126
- events.push(...newEvents);
1127
- }
1128
- return events;
1129
- }
1130
- function resolveToLabelEnd(events, context) {
1131
- let index = events.length;
1132
- let offset = 0;
1133
- let token;
1134
- let open;
1135
- let close;
1136
- let media;
1137
- while (index--) {
1138
- token = events[index][1];
1139
- if (open !== void 0) {
1140
- if (token.type === types.link || token.type === types.labelLink && token._inactive) {
1141
- break;
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
+ };
1142
1344
  }
1143
- if (events[index][0] === "enter" && token.type === types.labelLink) {
1144
- token._inactive = true;
1345
+ return void 0;
1346
+ },
1347
+ renderer() {
1348
+ return "";
1349
+ }
1350
+ };
1351
+ }
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
+ };
1145
1371
  }
1146
- } else if (close !== void 0) {
1147
- if (events[index][0] === "enter" && (token.type === types.labelImage || token.type === types.labelLink) && !token._balanced) {
1148
- open = index;
1149
- if (token.type !== types.labelLink) {
1150
- offset = 2;
1372
+ return void 0;
1373
+ },
1374
+ renderer() {
1375
+ return "";
1376
+ }
1377
+ };
1378
+ }
1379
+ function createInlineMathExtension() {
1380
+ return {
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
+ };
1398
+ }
1399
+ return void 0;
1400
+ },
1401
+ renderer() {
1402
+ return "";
1403
+ }
1404
+ };
1405
+ }
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}\[\^[^\]]+\]:/)) {
1151
1459
  break;
1152
1460
  }
1461
+ break;
1462
+ }
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
+ };
1513
+ }
1514
+ const selfClosingMatch = matchSelfClosingTag(src);
1515
+ if (selfClosingMatch) {
1516
+ return {
1517
+ type: "inlineHtml",
1518
+ raw: selfClosingMatch,
1519
+ text: selfClosingMatch
1520
+ };
1153
1521
  }
1154
- } else if (token.type === types.labelEnd) {
1155
- 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;
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;
1156
1556
  }
1157
1557
  }
1158
- if (open === void 0 || close === void 0) {
1159
- return events;
1558
+ if (depth === 0) {
1559
+ return src.slice(0, openTag.length + pos);
1160
1560
  }
1161
- const group = {
1162
- type: events[open][1].type === types.labelLink ? types.link : types.image,
1163
- start: { ...events[open][1].start },
1164
- end: { ...events[events.length - 1][1].end }
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];
1567
+ }
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];
1571
+ }
1572
+ return null;
1573
+ }
1574
+ function transformBlockMath(token) {
1575
+ return {
1576
+ type: "math",
1577
+ value: token.text,
1578
+ meta: null
1165
1579
  };
1166
- const label = {
1167
- type: types.label,
1168
- start: { ...events[open][1].start },
1169
- 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
1170
1588
  };
1171
- const text = {
1172
- type: types.labelText,
1173
- start: { ...events[open + offset + 2][1].end },
1174
- 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
1175
1598
  };
1176
- media = [
1177
- ["enter", group, context],
1178
- ["enter", label, context]
1179
- ];
1180
- media.push(...events.slice(open + 1, open + offset + 3));
1181
- media.push(["enter", text, context]);
1182
- media.push(...events.slice(open + offset + 4, close - 3));
1183
- media.push(
1184
- ["exit", text, context],
1185
- events[close - 2],
1186
- events[close - 1],
1187
- ["exit", label, context]
1188
- );
1189
- media.push(...events.slice(close + 1));
1190
- media.push(["exit", group, context]);
1191
- events.splice(open, events.length - open, ...media);
1192
- return events;
1193
- }
1194
- function tokenizeLabelEnd(effects, ok, nok) {
1195
- const self = this;
1196
- let index = self.events.length;
1197
- let labelStart;
1198
- while (index--) {
1199
- if ((self.events[index][1].type === types.labelImage || self.events[index][1].type === types.labelLink) && !self.events[index][1]._balanced) {
1200
- labelStart = self.events[index][1];
1201
- break;
1202
- }
1203
- }
1204
- return start;
1205
- function start(code) {
1206
- if (!labelStart) {
1207
- return nok(code);
1208
- }
1209
- if (labelStart._inactive) {
1210
- return labelEndNok(code);
1211
- }
1212
- if (labelStart.type === types.labelLink) {
1213
- const labelText = self.sliceSerialize({ start: labelStart.end, end: self.now() });
1214
- if (labelText.startsWith("^")) {
1215
- return nok(code);
1216
- }
1217
- }
1218
- effects.enter(types.labelEnd);
1219
- effects.enter(types.labelMarker);
1220
- effects.consume(code);
1221
- effects.exit(types.labelMarker);
1222
- effects.exit(types.labelEnd);
1223
- return after;
1224
- }
1225
- function after(code) {
1226
- if (code === codes.leftParenthesis) {
1227
- return effects.attempt(
1228
- {
1229
- tokenize: tokenizeResource,
1230
- partial: false
1231
- },
1232
- labelEndOk,
1233
- labelEndNok
1234
- // 修复:resource 解析失败时返回 nok
1235
- )(code);
1236
- }
1237
- if (code === codes.leftSquareBracket) {
1238
- 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: [
1239
1608
  {
1240
- tokenize: tokenizeReferenceFull,
1241
- partial: false
1242
- },
1243
- labelEndOk,
1244
- referenceNotFull
1245
- // 修改:即使不是 full reference,也尝试 collapsed
1246
- )(code);
1247
- }
1248
- return labelEndOk(code);
1609
+ type: "paragraph",
1610
+ children: [{ type: "text", value: token.href }]
1611
+ }
1612
+ ]
1613
+ };
1249
1614
  }
1250
- function referenceNotFull(code) {
1251
- return effects.attempt(
1252
- {
1253
- tokenize: tokenizeReferenceCollapsed,
1254
- partial: false
1255
- },
1256
- labelEndOk,
1257
- labelEndOk
1258
- // 修改:即使失败也返回 ok
1259
- )(code);
1260
- }
1261
- function labelEndOk(code) {
1262
- return ok(code);
1263
- }
1264
- function labelEndNok(code) {
1265
- labelStart._balanced = true;
1266
- return nok(code);
1267
- }
1268
- }
1269
- function tokenizeResource(effects, ok, nok) {
1270
- return resourceStart;
1271
- function resourceStart(code) {
1272
- if (code !== codes.leftParenthesis) {
1273
- return nok(code);
1274
- }
1275
- effects.enter(types.resource);
1276
- effects.enter(types.resourceMarker);
1277
- effects.consume(code);
1278
- effects.exit(types.resourceMarker);
1279
- return resourceBefore;
1280
- }
1281
- function resourceBefore(code) {
1282
- return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceOpen)(code) : resourceOpen(code);
1283
- }
1284
- function resourceOpen(code) {
1285
- if (code === codes.rightParenthesis) {
1286
- return resourceEnd(code);
1287
- }
1288
- return factoryDestination(
1289
- effects,
1290
- resourceDestinationAfter,
1291
- resourceDestinationMissing,
1292
- types.resourceDestination,
1293
- types.resourceDestinationLiteral,
1294
- types.resourceDestinationLiteralMarker,
1295
- types.resourceDestinationRaw,
1296
- types.resourceDestinationString,
1297
- constants.linkResourceDestinationBalanceMax
1298
- )(code);
1299
- }
1300
- function resourceDestinationAfter(code) {
1301
- return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceBetween)(code) : resourceEnd(code);
1302
- }
1303
- function resourceDestinationMissing(code) {
1304
- return nok(code);
1305
- }
1306
- function resourceBetween(code) {
1307
- if (code === codes.quotationMark || code === codes.apostrophe || code === codes.leftParenthesis) {
1308
- return factoryTitle(
1309
- effects,
1310
- resourceTitleAfter,
1311
- nok,
1312
- types.resourceTitle,
1313
- types.resourceTitleMarker,
1314
- types.resourceTitleString
1315
- )(code);
1316
- }
1317
- return resourceEnd(code);
1318
- }
1319
- function resourceTitleAfter(code) {
1320
- return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceEnd)(code) : resourceEnd(code);
1321
- }
1322
- function resourceEnd(code) {
1323
- if (code === codes.rightParenthesis) {
1324
- effects.enter(types.resourceMarker);
1325
- effects.consume(code);
1326
- effects.exit(types.resourceMarker);
1327
- effects.exit(types.resource);
1328
- return ok;
1329
- }
1330
- return nok(code);
1331
- }
1332
- }
1333
- function tokenizeReferenceFull(effects, ok, nok) {
1334
- const self = this;
1335
- return referenceFull;
1336
- function referenceFull(code) {
1337
- if (code !== codes.leftSquareBracket) {
1338
- return nok(code);
1339
- }
1340
- return factoryLabel.call(
1341
- self,
1342
- effects,
1343
- referenceFullAfter,
1344
- referenceFullMissing,
1345
- types.reference,
1346
- types.referenceMarker,
1347
- types.referenceString
1348
- )(code);
1349
- }
1350
- function referenceFullAfter(code) {
1351
- return ok(code);
1352
- }
1353
- function referenceFullMissing(code) {
1354
- return nok(code);
1355
- }
1356
- }
1357
- function tokenizeReferenceCollapsed(effects, ok, nok) {
1358
- return referenceCollapsedStart;
1359
- function referenceCollapsedStart(code) {
1360
- if (code !== codes.leftSquareBracket) {
1361
- return nok(code);
1362
- }
1363
- effects.enter(types.reference);
1364
- effects.enter(types.referenceMarker);
1365
- effects.consume(code);
1366
- effects.exit(types.referenceMarker);
1367
- return referenceCollapsedOpen;
1368
- }
1369
- function referenceCollapsedOpen(code) {
1370
- if (code === codes.rightSquareBracket) {
1371
- effects.enter(types.referenceMarker);
1372
- effects.consume(code);
1373
- effects.exit(types.referenceMarker);
1374
- effects.exit(types.reference);
1375
- return ok;
1376
- }
1377
- return nok(code);
1378
- }
1379
- }
1380
- function gfmFootnoteIncremental() {
1381
- const original = gfmFootnote();
1382
1615
  return {
1383
- ...original,
1384
- text: {
1385
- ...original.text,
1386
- // 覆盖 text[91] (`[` 的处理) - 这是脚注引用解析的起点
1387
- [codes.leftSquareBracket]: {
1388
- ...original.text[codes.leftSquareBracket],
1389
- tokenize: tokenizeGfmFootnoteCallIncremental
1390
- },
1391
- // 覆盖 text[93] (`]` 的处理) - 用于处理 ![^1] 这样的情况
1392
- [codes.rightSquareBracket]: {
1393
- ...original.text[codes.rightSquareBracket],
1394
- tokenize: tokenizePotentialGfmFootnoteCallIncremental
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)
1395
1647
  }
1396
- }
1648
+ ]
1397
1649
  };
1398
1650
  }
1399
- function tokenizeGfmFootnoteCallIncremental(effects, ok, nok) {
1400
- let size = 0;
1401
- let data = false;
1402
- return start;
1403
- function start(code) {
1404
- if (code !== codes.leftSquareBracket) {
1405
- return nok(code);
1406
- }
1407
- effects.enter("gfmFootnoteCall");
1408
- effects.enter("gfmFootnoteCallLabelMarker");
1409
- effects.consume(code);
1410
- effects.exit("gfmFootnoteCallLabelMarker");
1411
- return callStart;
1412
- }
1413
- function callStart(code) {
1414
- if (code !== codes.caret) {
1415
- return nok(code);
1416
- }
1417
- effects.enter("gfmFootnoteCallMarker");
1418
- effects.consume(code);
1419
- effects.exit("gfmFootnoteCallMarker");
1420
- effects.enter("gfmFootnoteCallString");
1421
- const token = effects.enter("chunkString");
1422
- token.contentType = "string";
1423
- return callData;
1424
- }
1425
- function callData(code) {
1426
- if (
1427
- // 太长
1428
- size > constants.linkReferenceSizeMax || // 右括号但没有数据
1429
- code === codes.rightSquareBracket && !data || // EOF、换行、空格、制表符、左括号不支持
1430
- code === codes.eof || code === codes.leftSquareBracket || markdownLineEndingOrSpace(code)
1431
- ) {
1432
- return nok(code);
1433
- }
1434
- if (code === codes.rightSquareBracket) {
1435
- effects.exit("chunkString");
1436
- effects.exit("gfmFootnoteCallString");
1437
- effects.enter("gfmFootnoteCallLabelMarker");
1438
- effects.consume(code);
1439
- effects.exit("gfmFootnoteCallLabelMarker");
1440
- effects.exit("gfmFootnoteCall");
1441
- return ok;
1442
- }
1443
- if (!markdownLineEndingOrSpace(code)) {
1444
- data = true;
1445
- }
1446
- size++;
1447
- effects.consume(code);
1448
- return code === codes.backslash ? callEscape : callData;
1449
- }
1450
- function callEscape(code) {
1451
- if (code === codes.leftSquareBracket || code === codes.backslash || code === codes.rightSquareBracket) {
1452
- effects.consume(code);
1453
- size++;
1454
- return callData;
1455
- }
1456
- return callData(code);
1457
- }
1458
- }
1459
- function tokenizePotentialGfmFootnoteCallIncremental(effects, ok, nok) {
1460
- const self = this;
1461
- let index = self.events.length;
1462
- let labelStart;
1463
- while (index--) {
1464
- const token = self.events[index][1];
1465
- if (token.type === "labelImage") {
1466
- labelStart = token;
1467
- break;
1468
- }
1469
- if (token.type === "gfmFootnoteCall" || token.type === "labelLink" || token.type === "label" || token.type === "image" || token.type === "link") {
1470
- break;
1471
- }
1472
- }
1473
- return start;
1474
- function start(code) {
1475
- if (code !== codes.rightSquareBracket) {
1476
- return nok(code);
1477
- }
1478
- if (!labelStart || !labelStart._balanced) {
1479
- return nok(code);
1480
- }
1481
- const id = normalizeIdentifier(
1482
- self.sliceSerialize({
1483
- start: labelStart.end,
1484
- end: self.now()
1485
- })
1486
- );
1487
- if (id.codePointAt(0) !== codes.caret) {
1488
- return nok(code);
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
+ };
1751
+ }
1752
+ const labelChildren = ctx.transformInline(new Lexer().inlineTokens(token.text));
1753
+ return {
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 }]
1759
+ };
1760
+ }
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
+ };
1769
+ }
1770
+ return {
1771
+ type: "link",
1772
+ url: token.href,
1773
+ title: token.title || null,
1774
+ // 对齐 micromark 输出
1775
+ children: ctx.transformInline(token.tokens)
1776
+ };
1777
+ }
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
+ };
1786
+ }
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
+ });
1489
1799
  }
1490
- effects.enter("gfmFootnoteCallLabelMarker");
1491
- effects.consume(code);
1492
- effects.exit("gfmFootnoteCallLabelMarker");
1493
- return ok(code);
1800
+ results.push({
1801
+ type: "footnoteReference",
1802
+ identifier: match[1],
1803
+ label: match[1]
1804
+ });
1805
+ lastIndex = match.index + match[0].length;
1494
1806
  }
1807
+ if (lastIndex < text.length) {
1808
+ results.push({
1809
+ type: "text",
1810
+ value: text.substring(lastIndex)
1811
+ });
1812
+ }
1813
+ return results;
1495
1814
  }
1496
-
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);
1815
+ function transformStrong(token, ctx) {
1816
+ return {
1817
+ type: "strong",
1818
+ children: ctx.transformInline(token.tokens)
1819
+ };
1510
1820
  }
1511
- var AstBuilder = class {
1512
- options;
1513
- containerConfig;
1514
- htmlTreeConfig;
1515
- constructor(options = {}) {
1516
- this.options = options;
1517
- this.containerConfig = this.computeContainerConfig(options);
1518
- this.htmlTreeConfig = this.computeHtmlTreeConfig(options);
1821
+ function transformEmphasis(token, ctx) {
1822
+ return {
1823
+ type: "emphasis",
1824
+ children: ctx.transformInline(token.tokens)
1825
+ };
1826
+ }
1827
+ function transformCodespan(token) {
1828
+ return {
1829
+ type: "inlineCode",
1830
+ value: token.text
1831
+ };
1832
+ }
1833
+ function transformBreak() {
1834
+ return { type: "break" };
1835
+ }
1836
+ function transformDelete(token, ctx) {
1837
+ return {
1838
+ type: "delete",
1839
+ children: ctx.transformInline(token.tokens)
1840
+ };
1841
+ }
1842
+ function transformInlineHtml(token) {
1843
+ const parsed = parseHtmlFragment(token.text);
1844
+ if (parsed.length > 0) {
1845
+ return parsed;
1519
1846
  }
1520
- /**
1521
- * 计算容器配置
1522
- */
1523
- computeContainerConfig(options) {
1524
- const containers = options.containers;
1525
- if (!containers) return void 0;
1526
- return containers === true ? {} : containers;
1847
+ return { type: "text", value: token.text };
1848
+ }
1849
+ function isTokenType(token, type) {
1850
+ return token.type === type;
1851
+ }
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);
1912
+ return null;
1527
1913
  }
1528
- /**
1529
- * 计算 HTML 树配置
1530
- */
1531
- computeHtmlTreeConfig(options) {
1532
- const htmlTree = options.htmlTree;
1533
- if (!htmlTree) return void 0;
1534
- return htmlTree === true ? {} : htmlTree;
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;
1535
1961
  }
1536
- /**
1537
- * 解析文本为 AST
1538
- *
1539
- * @param text Markdown 文本
1540
- * @returns AST
1541
- */
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;
1979
+ }
1980
+ return null;
1981
+ }
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;
1987
+ }
1988
+ if (builtinInlineTransformers[tokenType]) {
1989
+ const result = builtinInlineTransformers[tokenType](token, ctx);
1990
+ if (result !== void 0) return result;
1991
+ }
1992
+ if ("text" in token && typeof token.text === "string") {
1993
+ const text = { type: "text", value: token.text };
1994
+ return text;
1995
+ }
1996
+ return null;
1997
+ }
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
+ };
2017
+ }
2018
+ containerConfig;
2019
+ htmlTreeOptions;
2020
+ globalLinks = {};
2021
+ /** 用户传入的 marked 扩展 */
2022
+ userExtensions = [];
2023
+ /** 转换上下文(用于递归转换) */
2024
+ transformContext;
1542
2025
  parse(text) {
1543
- const extensions = [];
1544
- const mdastExtensions = [];
1545
- if (this.options.gfm) {
1546
- extensions.push(gfm());
1547
- mdastExtensions.push(...gfmFromMarkdown(), gfmFootnoteFromMarkdown());
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
+ }
1548
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];
1549
2055
  if (this.options.math) {
1550
- extensions.push(math());
1551
- mdastExtensions.push(mathFromMarkdown());
1552
- }
1553
- if (this.containerConfig !== void 0) {
1554
- extensions.push(directive());
1555
- mdastExtensions.push(directiveFromMarkdown());
1556
- }
1557
- if (this.options.extensions) {
1558
- extensions.push(...this.options.extensions);
1559
- }
1560
- if (this.options.mdastExtensions) {
1561
- mdastExtensions.push(...this.options.mdastExtensions);
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
2078
+ }
2079
+ };
2080
+ const lexerInstance = new Lexer(lexerOptions);
2081
+ if (lexerInstance.tokens && lexerInstance.tokens.links) {
2082
+ Object.assign(lexerInstance.tokens.links, this.globalLinks);
1562
2083
  }
1563
- if (this.options.gfm) {
1564
- extensions.push(gfmFootnoteIncremental());
2084
+ let tokens = lexerInstance.lex(normalizedText);
2085
+ if (lexerInstance.tokens && lexerInstance.tokens.links) {
2086
+ Object.assign(this.globalLinks, lexerInstance.tokens.links);
1565
2087
  }
1566
- extensions.push(micromarkReferenceExtension());
1567
- let ast = fromMarkdown(text, { extensions, mdastExtensions });
1568
- if (this.htmlTreeConfig) {
1569
- ast = transformHtmlNodes(ast, this.htmlTreeConfig);
1570
- } else {
1571
- ast = this.convertHtmlToText(ast);
2088
+ tokens = this.preprocessTokens(tokens);
2089
+ let children = this.transformTokensWithPosition(tokens);
2090
+ if (this.htmlTreeOptions) {
2091
+ children = this.processHtmlNodes(children);
1572
2092
  }
1573
- return ast;
2093
+ return {
2094
+ type: "root",
2095
+ children
2096
+ };
1574
2097
  }
1575
2098
  /**
1576
- * HTML 节点转换为纯文本(当未启用 HTML 树转换时)
2099
+ * 预处理 tokens
1577
2100
  *
1578
- * @param ast AST
1579
- * @returns 转换后的 AST
2101
+ * 处理容器指令和遗留的脚注定义(从 paragraph 中提取)
1580
2102
  */
1581
- convertHtmlToText(ast) {
1582
- return {
1583
- ...ast,
1584
- children: this.processBlockChildren(ast.children)
1585
- };
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;
2176
+ }
2177
+ }
2178
+ result.push(token);
2179
+ i++;
2180
+ }
2181
+ return result;
1586
2182
  }
1587
2183
  /**
1588
- * 处理块级节点
2184
+ * 转换 tokens 为 MDAST 节点(带位置信息)
1589
2185
  */
1590
- processBlockChildren(children) {
1591
- return children.map((node) => {
1592
- if (node.type === "html") {
1593
- return this.convertBlockHtmlToParagraph(node);
1594
- }
1595
- if ("children" in node && Array.isArray(node.children)) {
1596
- const parent = node;
1597
- const children2 = isInlineContainer(node) ? this.processInlineChildren(parent.children) : this.processBlockChildren(parent.children);
1598
- return {
1599
- ...parent,
1600
- children: children2
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 }
1601
2197
  };
2198
+ results.push(node);
1602
2199
  }
1603
- return node;
1604
- });
2200
+ currentOffset += rawLength;
2201
+ }
2202
+ return results;
1605
2203
  }
1606
2204
  /**
1607
- * 处理内联节点
2205
+ * 转换 tokens 为 MDAST 节点(不带位置信息)
1608
2206
  */
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
- };
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
+ }
1621
2225
  }
1622
- return n;
1623
- });
2226
+ }
2227
+ return results;
1624
2228
  }
1625
2229
  /**
1626
- * 将块级 HTML 节点转换为段落
2230
+ * 解析脚注内容为 AST 节点
1627
2231
  */
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;
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);
1639
2245
  }
1640
2246
  /**
1641
- * 将内联 HTML 节点转换为纯文本节点
2247
+ * 处理 HTML 节点
2248
+ *
2249
+ * 使用 html-extension 的 transformHtmlNodes 来处理:
2250
+ * - 合并被空行分割的 HTML 节点
2251
+ * - 将 HTML 解析为 HtmlElementNode 树结构
1642
2252
  */
1643
- convertInlineHtmlToText(htmlNode) {
1644
- return {
1645
- type: "text",
1646
- value: htmlNode.value,
1647
- position: htmlNode.position
2253
+ processHtmlNodes(nodes) {
2254
+ const tempRoot = {
2255
+ type: "root",
2256
+ children: nodes
1648
2257
  };
2258
+ const transformed = transformHtmlNodes(tempRoot, this.htmlTreeOptions);
2259
+ return transformed.children;
1649
2260
  }
1650
2261
  /**
1651
2262
  * 将 AST 节点转换为 ParsedBlock
1652
- *
1653
- * @param nodes AST 节点列表
1654
- * @param startOffset 起始偏移量
1655
- * @param rawText 原始文本
1656
- * @param status 块状态
1657
- * @param generateBlockId 生成块 ID 的函数
1658
- * @returns ParsedBlock 列表
1659
2263
  */
1660
2264
  nodesToBlocks(nodes, startOffset, rawText, status, generateBlockId) {
1661
2265
  const blocks = [];
1662
- let currentOffset = startOffset;
1663
2266
  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);
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;
1667
2272
  blocks.push({
1668
2273
  id: generateBlockId(),
1669
2274
  status,
1670
2275
  node,
1671
- startOffset: nodeStart,
1672
- endOffset: nodeEnd,
2276
+ startOffset: absoluteStart,
2277
+ endOffset: absoluteEnd,
1673
2278
  rawText: nodeText
1674
2279
  });
1675
- currentOffset = nodeEnd;
1676
2280
  }
1677
2281
  return blocks;
1678
2282
  }
@@ -1680,7 +2284,6 @@ var AstBuilder = class {
1680
2284
 
1681
2285
  // src/parser/IncremarkParser.ts
1682
2286
  var IncremarkParser = class {
1683
- buffer = "";
1684
2287
  lines = [];
1685
2288
  /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
1686
2289
  lineOffsets = [0];
@@ -1705,7 +2308,8 @@ var IncremarkParser = class {
1705
2308
  ...options
1706
2309
  };
1707
2310
  this.context = createInitialContext();
1708
- this.astBuilder = new AstBuilder(this.options);
2311
+ const BuilderClass = options.astBuilder || MarkedAstBuilder;
2312
+ this.astBuilder = new BuilderClass(this.options);
1709
2313
  this.boundaryDetector = new BoundaryDetector({ containers: this.astBuilder.containerConfig });
1710
2314
  this.definitionManager = new DefinitionManager();
1711
2315
  this.footnoteManager = new FootnoteManager();
@@ -1720,36 +2324,30 @@ var IncremarkParser = class {
1720
2324
  this.definitionManager.extractFromBlocks(blocks);
1721
2325
  this.footnoteManager.extractDefinitionsFromBlocks(blocks);
1722
2326
  }
1723
- /**
1724
- * 收集 AST 中的脚注引用(按出现顺序)
1725
- * 用于确定脚注的显示顺序
1726
- */
1727
- collectFootnoteReferences(nodes) {
1728
- this.footnoteManager.collectReferences(nodes);
1729
- }
1730
2327
  /**
1731
2328
  * 增量更新 lines 和 lineOffsets
1732
- * 只处理新增的内容,避免全量 split
2329
+ * 优化策略:只 split 新增的 chunk,不拼接旧字符串,避免长行性能劣化
1733
2330
  */
1734
- updateLines() {
2331
+ updateLines(chunk) {
1735
2332
  const prevLineCount = this.lines.length;
1736
2333
  if (prevLineCount === 0) {
1737
- this.lines = this.buffer.split("\n");
2334
+ this.lines = chunk.split("\n");
1738
2335
  this.lineOffsets = [0];
1739
- for (let i = 0; i < this.lines.length; i++) {
2336
+ for (let i = 0; i < this.lines.length - 1; i++) {
1740
2337
  this.lineOffsets.push(this.lineOffsets[i] + this.lines[i].length + 1);
1741
2338
  }
1742
2339
  return;
1743
2340
  }
1744
- const lastLineStart = this.lineOffsets[prevLineCount - 1];
1745
- const textFromLastLine = this.buffer.slice(lastLineStart);
1746
- const newLines = textFromLastLine.split("\n");
1747
- this.lines.length = prevLineCount - 1;
1748
- this.lineOffsets.length = prevLineCount;
1749
- for (let i = 0; i < newLines.length; i++) {
1750
- this.lines.push(newLines[i]);
1751
- const prevOffset = this.lineOffsets[this.lineOffsets.length - 1];
1752
- 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]);
1753
2351
  }
1754
2352
  }
1755
2353
  /**
@@ -1774,8 +2372,7 @@ var IncremarkParser = class {
1774
2372
  * 追加新的 chunk 并返回增量更新
1775
2373
  */
1776
2374
  append(chunk) {
1777
- this.buffer += chunk;
1778
- this.updateLines();
2375
+ this.updateLines(chunk);
1779
2376
  const { line: stableBoundary, contextAtLine } = this.findStableBoundary();
1780
2377
  const update = {
1781
2378
  completed: [],
@@ -1794,6 +2391,8 @@ var IncremarkParser = class {
1794
2391
  this.completedBlocks.push(...newBlocks);
1795
2392
  update.completed = newBlocks;
1796
2393
  this.updateDefinitionsFromCompletedBlocks(newBlocks);
2394
+ this.footnoteManager.collectReferencesFromCompletedBlocks(newBlocks);
2395
+ this.boundaryDetector.clearContextCache(this.pendingStartLine);
1797
2396
  this.context = contextAtLine;
1798
2397
  this.pendingStartLine = stableBoundary + 1;
1799
2398
  }
@@ -1810,10 +2409,9 @@ var IncremarkParser = class {
1810
2409
  type: "root",
1811
2410
  children: [...this.completedBlocks.map((b) => b.node), ...update.pending.map((b) => b.node)]
1812
2411
  };
1813
- this.collectFootnoteReferences(update.ast.children);
2412
+ update.footnoteReferenceOrder = this.footnoteManager.collectReferencesFromPending(update.pending);
1814
2413
  update.definitions = this.getDefinitionMap();
1815
2414
  update.footnoteDefinitions = this.getFootnoteDefinitionMap();
1816
- update.footnoteReferenceOrder = this.getFootnoteReferenceOrder();
1817
2415
  this.emitChange(update.pending);
1818
2416
  return update;
1819
2417
  }
@@ -1825,7 +2423,7 @@ var IncremarkParser = class {
1825
2423
  const state = {
1826
2424
  completedBlocks: this.completedBlocks,
1827
2425
  pendingBlocks,
1828
- markdown: this.buffer,
2426
+ markdown: this.lines.join("\n"),
1829
2427
  ast: {
1830
2428
  type: "root",
1831
2429
  children: [
@@ -1868,6 +2466,8 @@ var IncremarkParser = class {
1868
2466
  this.completedBlocks.push(...finalBlocks);
1869
2467
  update.completed = finalBlocks;
1870
2468
  this.updateDefinitionsFromCompletedBlocks(finalBlocks);
2469
+ this.footnoteManager.collectReferencesFromCompletedBlocks(finalBlocks);
2470
+ this.boundaryDetector.clearContextCache(this.pendingStartLine);
1871
2471
  }
1872
2472
  }
1873
2473
  this.lastPendingBlocks = [];
@@ -1876,7 +2476,6 @@ var IncremarkParser = class {
1876
2476
  type: "root",
1877
2477
  children: this.completedBlocks.map((b) => b.node)
1878
2478
  };
1879
- this.collectFootnoteReferences(update.ast.children);
1880
2479
  update.definitions = this.getDefinitionMap();
1881
2480
  update.footnoteDefinitions = this.getFootnoteDefinitionMap();
1882
2481
  update.footnoteReferenceOrder = this.getFootnoteReferenceOrder();
@@ -1899,7 +2498,7 @@ var IncremarkParser = class {
1899
2498
  ...this.completedBlocks.map((b) => b.node),
1900
2499
  ...this.lastPendingBlocks.map((b) => b.node)
1901
2500
  ];
1902
- this.collectFootnoteReferences(children);
2501
+ this.footnoteManager.collectReferencesFromPending(this.lastPendingBlocks);
1903
2502
  return {
1904
2503
  type: "root",
1905
2504
  children
@@ -1915,7 +2514,7 @@ var IncremarkParser = class {
1915
2514
  * 获取当前缓冲区内容
1916
2515
  */
1917
2516
  getBuffer() {
1918
- return this.buffer;
2517
+ return this.lines.join("\n");
1919
2518
  }
1920
2519
  /**
1921
2520
  * 获取 Definition 映射表(用于引用式图片和链接)
@@ -1949,7 +2548,6 @@ var IncremarkParser = class {
1949
2548
  * 重置解析器状态
1950
2549
  */
1951
2550
  reset() {
1952
- this.buffer = "";
1953
2551
  this.lines = [];
1954
2552
  this.lineOffsets = [0];
1955
2553
  this.completedBlocks = [];
@@ -1993,10 +2591,9 @@ function countCharsInNode(n) {
1993
2591
  }
1994
2592
  return 1;
1995
2593
  }
1996
- function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2594
+ function sliceAst(node, maxChars, accumulatedChunks) {
1997
2595
  if (maxChars <= 0) return null;
1998
- if (skipChars >= maxChars) return null;
1999
- let remaining = maxChars - skipChars;
2596
+ let remaining = maxChars;
2000
2597
  let charIndex = 0;
2001
2598
  const chunkRanges = [];
2002
2599
  if (accumulatedChunks && accumulatedChunks.chunks.length > 0) {
@@ -2015,16 +2612,10 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2015
2612
  if (n.value && typeof n.value === "string") {
2016
2613
  const nodeStart = charIndex;
2017
2614
  const nodeEnd = charIndex + n.value.length;
2018
- if (nodeEnd <= skipChars) {
2019
- charIndex = nodeEnd;
2020
- return null;
2021
- }
2022
- const skipInNode = Math.max(0, skipChars - nodeStart);
2023
- const take = Math.min(n.value.length - skipInNode, remaining);
2615
+ const take = Math.min(n.value.length, remaining);
2024
2616
  remaining -= take;
2025
- if (take === 0) return null;
2026
- const slicedValue = n.value.slice(skipInNode, skipInNode + take);
2027
2617
  charIndex = nodeEnd;
2618
+ const slicedValue = n.value.slice(0, take);
2028
2619
  const result = {
2029
2620
  ...n,
2030
2621
  value: slicedValue
@@ -2033,11 +2624,11 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2033
2624
  const nodeChunks = [];
2034
2625
  let firstChunkLocalStart = take;
2035
2626
  for (const range of chunkRanges) {
2036
- const overlapStart = Math.max(range.start, nodeStart + skipInNode);
2037
- 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);
2038
2629
  if (overlapStart < overlapEnd) {
2039
- const localStart = overlapStart - (nodeStart + skipInNode);
2040
- const localEnd = overlapEnd - (nodeStart + skipInNode);
2630
+ const localStart = overlapStart - nodeStart;
2631
+ const localEnd = overlapEnd - nodeStart;
2041
2632
  const chunkText = slicedValue.slice(localStart, localEnd);
2042
2633
  if (chunkText.length > 0) {
2043
2634
  if (nodeChunks.length === 0) {
@@ -2059,24 +2650,12 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2059
2650
  }
2060
2651
  if (n.children && Array.isArray(n.children)) {
2061
2652
  const newChildren = [];
2062
- let childCharIndex = charIndex;
2063
2653
  for (const child of n.children) {
2064
2654
  if (remaining <= 0) break;
2065
- const childChars = countCharsInNode(child);
2066
- const childStart = childCharIndex;
2067
- const childEnd = childCharIndex + childChars;
2068
- if (childEnd <= skipChars) {
2069
- childCharIndex = childEnd;
2070
- continue;
2071
- }
2072
- const savedCharIndex = charIndex;
2073
- charIndex = childStart;
2074
2655
  const processed = process(child);
2075
- charIndex = savedCharIndex;
2076
2656
  if (processed) {
2077
2657
  newChildren.push(processed);
2078
2658
  }
2079
- childCharIndex = childEnd;
2080
2659
  }
2081
2660
  if (newChildren.length === 0) {
2082
2661
  return null;
@@ -2089,10 +2668,7 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2089
2668
  }
2090
2669
  return process(node);
2091
2670
  }
2092
- function appendToAst(baseNode, sourceNode, startChars, endChars, accumulatedChunks) {
2093
- if (endChars <= startChars) {
2094
- return baseNode;
2095
- }
2671
+ function appendToAst(baseNode, sourceNode, endChars, accumulatedChunks) {
2096
2672
  const fullSlice = sliceAst(sourceNode, endChars, accumulatedChunks);
2097
2673
  if (!fullSlice) {
2098
2674
  return baseNode;
@@ -2617,7 +3193,6 @@ var BlockTransformer = class {
2617
3193
  this.cachedDisplayNode = appendToAst(
2618
3194
  this.cachedDisplayNode,
2619
3195
  block.node,
2620
- this.cachedProgress,
2621
3196
  currentProgress,
2622
3197
  this.getAccumulatedChunks()
2623
3198
  );
@@ -2733,51 +3308,7 @@ function createPlugin(name, matcher, options = {}) {
2733
3308
  ...options
2734
3309
  };
2735
3310
  }
2736
- /**
2737
- * @file Micromark 扩展:支持增量解析的 Reference 语法
2738
- *
2739
- * @description
2740
- * 在增量解析场景中,引用式图片/链接(如 `![Alt][id]`)可能在定义(`[id]: url`)之前出现。
2741
- * 标准 micromark 会检查 parser.defined,如果 id 未定义就解析为文本。
2742
- *
2743
- * 本扩展通过覆盖 labelEnd 构造,移除 parser.defined 检查,
2744
- * 使得 reference 语法总是被解析为 reference token,
2745
- * 由渲染层根据实际的 definitionMap 决定如何渲染。
2746
- *
2747
- * @module micromark-reference-extension
2748
- *
2749
- * @features
2750
- * - ✅ 支持所有 resource 语法(带 title 的图片/链接)
2751
- * - ✅ 支持所有 reference 语法(full, collapsed, shortcut)
2752
- * - ✅ 延迟验证:解析时不检查定义是否存在
2753
- * - ✅ 使用官方 factory 函数,保证与 CommonMark 标准一致
2754
- *
2755
- * @dependencies
2756
- * - micromark-factory-destination: 解析 URL(支持尖括号、括号平衡)
2757
- * - micromark-factory-title: 解析 title(支持三种引号,支持多行)
2758
- * - micromark-factory-label: 解析 label(支持转义、长度限制)
2759
- * - micromark-factory-whitespace: 解析空白符(正确生成 lineEnding/linePrefix token)
2760
- * - micromark-util-character: 字符判断工具
2761
- * - micromark-util-symbol: 常量(codes, types, constants)
2762
- * - micromark-util-types: TypeScript 类型定义
2763
- *
2764
- * @see {@link https://github.com/micromark/micromark} - micromark 官方文档
2765
- * @see {@link https://spec.commonmark.org/0.30/#images} - CommonMark 图片规范
2766
- * @see {@link https://spec.commonmark.org/0.30/#links} - CommonMark 链接规范
2767
- *
2768
- * @example
2769
- * ```typescript
2770
- * import { micromarkReferenceExtension } from './micromark-reference-extension'
2771
- * import { fromMarkdown } from 'mdast-util-from-markdown'
2772
- *
2773
- * const extensions = [micromarkReferenceExtension()]
2774
- * const ast = fromMarkdown(text, { extensions })
2775
- * ```
2776
- *
2777
- * @author Incremark Team
2778
- * @license MIT
2779
- */
2780
3311
 
2781
- export { BlockTransformer, IncremarkParser, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createIncremarkParser, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin };
3312
+ export { BlockTransformer, IncremarkParser, MarkedAstBuilder, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createIncremarkParser, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin };
2782
3313
  //# sourceMappingURL=index.js.map
2783
3314
  //# sourceMappingURL=index.js.map