@incremark/core 0.2.7 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,593 +1233,1124 @@ 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
+ };
1344
+ }
1345
+ return void 0;
1346
+ },
1347
+ renderer() {
1348
+ return "";
1349
+ }
1350
+ };
1351
+ }
1352
+
1353
+ // src/extensions/marked-extensions/mathExtension.ts
1354
+ function resolveOptions(options) {
1355
+ return {
1356
+ tex: options?.tex ?? false
1357
+ };
1358
+ }
1359
+ function createBlockMathExtension(options) {
1360
+ const resolved = resolveOptions(options);
1361
+ return {
1362
+ name: "blockMath",
1363
+ level: "block",
1364
+ start(src) {
1365
+ const dollarMatch = src.match(/^ {0,3}\$\$/m);
1366
+ let bracketMatch = null;
1367
+ if (resolved.tex) {
1368
+ bracketMatch = src.match(/^ {0,3}\\\[/m);
1369
+ }
1370
+ if (dollarMatch && bracketMatch) {
1371
+ return Math.min(dollarMatch.index, bracketMatch.index);
1372
+ }
1373
+ return dollarMatch?.index ?? bracketMatch?.index;
1374
+ },
1375
+ tokenizer(src) {
1376
+ const dollarRule = /^ {0,3}\$\$([\s\S]*?)\$\$ *(?:\n+|$)/;
1377
+ const dollarMatch = dollarRule.exec(src);
1378
+ if (dollarMatch) {
1379
+ return {
1380
+ type: "blockMath",
1381
+ raw: dollarMatch[0],
1382
+ text: dollarMatch[1].trim()
1383
+ };
1384
+ }
1385
+ if (resolved.tex) {
1386
+ const bracketRule = /^ {0,3}\\\[([\s\S]*?)\\\] *(?:\n+|$)/;
1387
+ const bracketMatch = bracketRule.exec(src);
1388
+ if (bracketMatch) {
1389
+ return {
1390
+ type: "blockMath",
1391
+ raw: bracketMatch[0],
1392
+ text: bracketMatch[1].trim()
1393
+ };
1394
+ }
1395
+ }
1396
+ return void 0;
1397
+ },
1398
+ renderer() {
1399
+ return "";
1400
+ }
1401
+ };
1402
+ }
1403
+ function createInlineMathExtension(options) {
1404
+ const resolved = resolveOptions(options);
1405
+ return {
1406
+ name: "inlineMath",
1407
+ level: "inline",
1408
+ start(src) {
1409
+ const dollarIndex = src.indexOf("$");
1410
+ const validDollarIndex = dollarIndex !== -1 && src[dollarIndex + 1] !== "$" ? dollarIndex : -1;
1411
+ let parenIndex = -1;
1412
+ if (resolved.tex) {
1413
+ parenIndex = src.indexOf("\\(");
1142
1414
  }
1143
- if (events[index][0] === "enter" && token.type === types.labelLink) {
1144
- token._inactive = true;
1415
+ if (validDollarIndex !== -1 && parenIndex !== -1) {
1416
+ return Math.min(validDollarIndex, parenIndex);
1145
1417
  }
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;
1418
+ if (validDollarIndex !== -1) return validDollarIndex;
1419
+ if (parenIndex !== -1) return parenIndex;
1420
+ return void 0;
1421
+ },
1422
+ tokenizer(src) {
1423
+ const dollarRule = /^\$(?!\$)((?:\\.|[^\\\n$])+?)\$(?!\d)/;
1424
+ const dollarMatch = dollarRule.exec(src);
1425
+ if (dollarMatch) {
1426
+ return {
1427
+ type: "inlineMath",
1428
+ raw: dollarMatch[0],
1429
+ text: dollarMatch[1].trim()
1430
+ };
1431
+ }
1432
+ if (resolved.tex) {
1433
+ const parenRule = /^\\\(([\s\S]*?)\\\)/;
1434
+ const parenMatch = parenRule.exec(src);
1435
+ if (parenMatch) {
1436
+ return {
1437
+ type: "inlineMath",
1438
+ raw: parenMatch[0],
1439
+ text: parenMatch[1].trim()
1440
+ };
1441
+ }
1442
+ }
1443
+ return void 0;
1444
+ },
1445
+ renderer() {
1446
+ return "";
1447
+ }
1448
+ };
1449
+ }
1450
+
1451
+ // src/extensions/marked-extensions/footnoteDefinitionExtension.ts
1452
+ function createFootnoteDefinitionExtension() {
1453
+ return {
1454
+ name: "footnoteDefinitionBlock",
1455
+ level: "block",
1456
+ start(src) {
1457
+ const match = src.match(/^ {0,3}\[\^[^\]]+\]:/m);
1458
+ return match?.index;
1459
+ },
1460
+ tokenizer(src) {
1461
+ const firstLineRule = /^ {0,3}\[\^([a-zA-Z0-9_-]+)\]:\s*(.*)/;
1462
+ const firstLineMatch = firstLineRule.exec(src);
1463
+ if (!firstLineMatch) return void 0;
1464
+ const identifier = firstLineMatch[1];
1465
+ let content = firstLineMatch[2];
1466
+ let raw = firstLineMatch[0];
1467
+ const remaining = src.slice(raw.length);
1468
+ const lines = remaining.split("\n");
1469
+ let lineIndex = 0;
1470
+ if (lines[0] === "" && remaining.startsWith("\n")) {
1471
+ lineIndex = 1;
1472
+ raw += "\n";
1473
+ content += "\n";
1474
+ }
1475
+ while (lineIndex < lines.length) {
1476
+ const line = lines[lineIndex];
1477
+ if (line.trim() === "") {
1478
+ let hasIndentedLineAfter = false;
1479
+ for (let j = lineIndex + 1; j < lines.length; j++) {
1480
+ const nextLine = lines[j];
1481
+ if (nextLine.trim() === "") continue;
1482
+ if (nextLine.match(/^( |\t)/)) {
1483
+ hasIndentedLineAfter = true;
1484
+ }
1485
+ break;
1486
+ }
1487
+ if (hasIndentedLineAfter) {
1488
+ raw += line + (lineIndex < lines.length - 1 ? "\n" : "");
1489
+ content += "\n" + line;
1490
+ lineIndex++;
1491
+ continue;
1492
+ } else {
1493
+ break;
1494
+ }
1495
+ }
1496
+ if (line.match(/^( |\t)/)) {
1497
+ raw += line + (lineIndex < lines.length - 1 ? "\n" : "");
1498
+ content += "\n" + line;
1499
+ lineIndex++;
1500
+ continue;
1501
+ }
1502
+ if (line.match(/^ {0,3}\[\^[^\]]+\]:/)) {
1151
1503
  break;
1152
1504
  }
1505
+ break;
1506
+ }
1507
+ const trimmedContent = content.replace(/\n+$/, "");
1508
+ return {
1509
+ type: "footnoteDefinitionBlock",
1510
+ raw,
1511
+ identifier,
1512
+ content: trimmedContent
1513
+ };
1514
+ },
1515
+ renderer() {
1516
+ return "";
1517
+ }
1518
+ };
1519
+ }
1520
+
1521
+ // src/extensions/marked-extensions/inlineHtmlExtension.ts
1522
+ var SELF_CLOSING_TAGS = /* @__PURE__ */ new Set([
1523
+ "area",
1524
+ "base",
1525
+ "br",
1526
+ "col",
1527
+ "embed",
1528
+ "hr",
1529
+ "img",
1530
+ "input",
1531
+ "link",
1532
+ "meta",
1533
+ "param",
1534
+ "source",
1535
+ "track",
1536
+ "wbr"
1537
+ ]);
1538
+ function createInlineHtmlExtension() {
1539
+ return {
1540
+ name: "inlineHtml",
1541
+ level: "inline",
1542
+ start(src) {
1543
+ const index = src.indexOf("<");
1544
+ if (index === -1) return void 0;
1545
+ const afterLt = src.slice(index + 1);
1546
+ if (!/^[a-zA-Z\/]/.test(afterLt)) return void 0;
1547
+ return index;
1548
+ },
1549
+ tokenizer(src) {
1550
+ const completeTagMatch = matchCompleteHtmlElement(src);
1551
+ if (completeTagMatch) {
1552
+ return {
1553
+ type: "inlineHtml",
1554
+ raw: completeTagMatch,
1555
+ text: completeTagMatch
1556
+ };
1153
1557
  }
1154
- } else if (token.type === types.labelEnd) {
1155
- close = index;
1558
+ const selfClosingMatch = matchSelfClosingTag(src);
1559
+ if (selfClosingMatch) {
1560
+ return {
1561
+ type: "inlineHtml",
1562
+ raw: selfClosingMatch,
1563
+ text: selfClosingMatch
1564
+ };
1565
+ }
1566
+ return void 0;
1567
+ },
1568
+ renderer() {
1569
+ return "";
1156
1570
  }
1571
+ };
1572
+ }
1573
+ function matchCompleteHtmlElement(src) {
1574
+ const openTagMatch = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*>/.exec(src);
1575
+ if (!openTagMatch) return null;
1576
+ const tagName = openTagMatch[1].toLowerCase();
1577
+ const openTag = openTagMatch[0];
1578
+ if (SELF_CLOSING_TAGS.has(tagName)) {
1579
+ return openTag;
1580
+ }
1581
+ const afterOpenTag = src.slice(openTag.length);
1582
+ let depth = 1;
1583
+ let pos = 0;
1584
+ const openPattern = new RegExp(`<${tagName}(?:\\s[^>]*)?>`, "gi");
1585
+ const closePattern = new RegExp(`</${tagName}>`, "gi");
1586
+ while (depth > 0 && pos < afterOpenTag.length) {
1587
+ openPattern.lastIndex = pos;
1588
+ closePattern.lastIndex = pos;
1589
+ const nextOpen = openPattern.exec(afterOpenTag);
1590
+ const nextClose = closePattern.exec(afterOpenTag);
1591
+ if (!nextClose) {
1592
+ return null;
1593
+ }
1594
+ if (nextOpen && nextOpen.index < nextClose.index) {
1595
+ depth++;
1596
+ pos = nextOpen.index + nextOpen[0].length;
1597
+ } else {
1598
+ depth--;
1599
+ pos = nextClose.index + nextClose[0].length;
1600
+ }
1601
+ }
1602
+ if (depth === 0) {
1603
+ return src.slice(0, openTag.length + pos);
1604
+ }
1605
+ return null;
1606
+ }
1607
+ function matchSelfClosingTag(src) {
1608
+ const explicitSelfClosing = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*\/>/.exec(src);
1609
+ if (explicitSelfClosing) {
1610
+ return explicitSelfClosing[0];
1157
1611
  }
1158
- if (open === void 0 || close === void 0) {
1159
- return events;
1612
+ const implicitSelfClosing = /^<([a-zA-Z][a-zA-Z0-9]*)((?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*>/.exec(src);
1613
+ if (implicitSelfClosing && SELF_CLOSING_TAGS.has(implicitSelfClosing[1].toLowerCase())) {
1614
+ return implicitSelfClosing[0];
1160
1615
  }
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 }
1616
+ return null;
1617
+ }
1618
+ function transformBlockMath(token) {
1619
+ return {
1620
+ type: "math",
1621
+ value: token.text,
1622
+ meta: null
1165
1623
  };
1166
- const label = {
1167
- type: types.label,
1168
- start: { ...events[open][1].start },
1169
- end: { ...events[close][1].end }
1624
+ }
1625
+ function transformFootnoteDefinitionBlock(token, ctx) {
1626
+ const children = ctx.parseFootnoteContent(token.content);
1627
+ return {
1628
+ type: "footnoteDefinition",
1629
+ identifier: token.identifier,
1630
+ label: token.identifier,
1631
+ children
1170
1632
  };
1171
- const text = {
1172
- type: types.labelText,
1173
- start: { ...events[open + offset + 2][1].end },
1174
- end: { ...events[close - 2][1].start }
1633
+ }
1634
+ function transformExplicitDefinition(token) {
1635
+ if (!token.identifier || !token.url) return null;
1636
+ return {
1637
+ type: "definition",
1638
+ identifier: token.identifier,
1639
+ label: token.identifier,
1640
+ url: token.url,
1641
+ title: token.title ?? null
1175
1642
  };
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(
1643
+ }
1644
+ function transformDef(token) {
1645
+ if (token.tag.startsWith("^")) {
1646
+ const footnoteId = token.tag.slice(1);
1647
+ return {
1648
+ type: "footnoteDefinition",
1649
+ identifier: footnoteId,
1650
+ label: footnoteId,
1651
+ children: [
1239
1652
  {
1240
- tokenize: tokenizeReferenceFull,
1241
- partial: false
1242
- },
1243
- labelEndOk,
1244
- referenceNotFull
1245
- // 修改:即使不是 full reference,也尝试 collapsed
1246
- )(code);
1247
- }
1248
- return labelEndOk(code);
1653
+ type: "paragraph",
1654
+ children: [{ type: "text", value: token.href }]
1655
+ }
1656
+ ]
1657
+ };
1249
1658
  }
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
1659
  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
1660
+ type: "definition",
1661
+ identifier: token.tag,
1662
+ label: token.tag,
1663
+ url: token.href,
1664
+ title: token.title ?? null
1665
+ };
1666
+ }
1667
+ function transformContainer(token, ctx) {
1668
+ const attributes = {};
1669
+ const attrRegex = /([a-zA-Z0-9_-]+)=?("([^"]*)"|'([^']*)'|([^ ]*))?/g;
1670
+ let match;
1671
+ while ((match = attrRegex.exec(token.attrs)) !== null) {
1672
+ attributes[match[1]] = match[3] || match[4] || match[5] || "";
1673
+ }
1674
+ const children = ctx.transformTokensWithPosition(token.tokens);
1675
+ return {
1676
+ type: "containerDirective",
1677
+ name: token.name,
1678
+ attributes,
1679
+ children
1680
+ };
1681
+ }
1682
+ function transformFootnoteDefToken(token, ctx) {
1683
+ return {
1684
+ type: "footnoteDefinition",
1685
+ identifier: token.identifier,
1686
+ label: token.identifier,
1687
+ children: [
1688
+ {
1689
+ type: "paragraph",
1690
+ children: ctx.transformInline(token.tokens)
1395
1691
  }
1396
- }
1692
+ ]
1397
1693
  };
1398
1694
  }
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);
1695
+ function transformHeading(token, ctx) {
1696
+ return {
1697
+ type: "heading",
1698
+ depth: token.depth,
1699
+ children: ctx.transformInline(token.tokens)
1700
+ };
1701
+ }
1702
+ function transformParagraph(token, ctx) {
1703
+ return {
1704
+ type: "paragraph",
1705
+ children: ctx.transformInline(token.tokens)
1706
+ };
1707
+ }
1708
+ function transformCode(token) {
1709
+ return {
1710
+ type: "code",
1711
+ lang: token.lang || null,
1712
+ meta: null,
1713
+ // 对齐 micromark 输出
1714
+ value: token.text
1715
+ };
1716
+ }
1717
+ function transformBlockquote(token, ctx) {
1718
+ const children = ctx.transformTokens(token.tokens);
1719
+ return {
1720
+ type: "blockquote",
1721
+ children
1722
+ };
1723
+ }
1724
+ function transformList(token, ctx) {
1725
+ const children = token.items.map((item) => ({
1726
+ type: "listItem",
1727
+ spread: item.loose,
1728
+ checked: item.checked ?? null,
1729
+ // 对齐 micromark 输出(GFM 任务列表)
1730
+ children: ctx.transformTokens(item.tokens)
1731
+ }));
1732
+ return {
1733
+ type: "list",
1734
+ ordered: token.ordered,
1735
+ start: token.ordered ? token.start || 1 : null,
1736
+ // 对齐 micromark:有序列表有 start,无序列表为 null
1737
+ spread: token.loose,
1738
+ children
1739
+ };
1740
+ }
1741
+ function transformTable(token, ctx) {
1742
+ const headerCells = token.header.map((cell) => ({
1743
+ type: "tableCell",
1744
+ children: ctx.transformInline(cell.tokens)
1745
+ }));
1746
+ const bodyRows = token.rows.map((row) => ({
1747
+ type: "tableRow",
1748
+ children: row.map((cell) => ({
1749
+ type: "tableCell",
1750
+ children: ctx.transformInline(cell.tokens)
1751
+ }))
1752
+ }));
1753
+ return {
1754
+ type: "table",
1755
+ align: token.align,
1756
+ children: [{ type: "tableRow", children: headerCells }, ...bodyRows]
1757
+ };
1758
+ }
1759
+ function transformHr() {
1760
+ return { type: "thematicBreak" };
1761
+ }
1762
+ function transformHtml(token) {
1763
+ return {
1764
+ type: "html",
1765
+ value: token.text
1766
+ };
1767
+ }
1768
+ function transformTextBlock(token, ctx) {
1769
+ if (token.tokens) {
1770
+ return {
1771
+ type: "paragraph",
1772
+ children: ctx.transformInline(token.tokens)
1773
+ };
1774
+ }
1775
+ return {
1776
+ type: "paragraph",
1777
+ children: [{ type: "text", value: token.text }]
1778
+ };
1779
+ }
1780
+ function transformInlineMath(token) {
1781
+ return {
1782
+ type: "inlineMath",
1783
+ value: token.text
1784
+ };
1785
+ }
1786
+ function transformOptimisticReference(token, ctx) {
1787
+ if (token.isImage) {
1788
+ return {
1789
+ type: "imageReference",
1790
+ identifier: token.identifier,
1791
+ label: token.label,
1792
+ referenceType: token.referenceType,
1793
+ alt: token.text
1794
+ };
1795
+ }
1796
+ const labelChildren = ctx.transformInline(new Lexer().inlineTokens(token.text));
1797
+ return {
1798
+ type: "linkReference",
1799
+ identifier: token.identifier,
1800
+ label: token.label,
1801
+ referenceType: token.referenceType,
1802
+ children: labelChildren.length ? labelChildren : [{ type: "text", value: token.text }]
1803
+ };
1804
+ }
1805
+ function transformLink(token, ctx) {
1806
+ if (token.text.startsWith("^") && token.text.length > 1) {
1807
+ const footnoteId = token.text.slice(1);
1808
+ return {
1809
+ type: "footnoteReference",
1810
+ identifier: footnoteId,
1811
+ label: footnoteId
1812
+ };
1813
+ }
1814
+ return {
1815
+ type: "link",
1816
+ url: token.href,
1817
+ title: token.title || null,
1818
+ // 对齐 micromark 输出
1819
+ children: ctx.transformInline(token.tokens)
1820
+ };
1821
+ }
1822
+ function transformImage(token) {
1823
+ return {
1824
+ type: "image",
1825
+ url: token.href,
1826
+ title: token.title || null,
1827
+ // 对齐 micromark 输出
1828
+ alt: token.text
1829
+ };
1830
+ }
1831
+ function transformText(token) {
1832
+ const results = [];
1833
+ const text = token.text;
1834
+ const footnoteRegex = /\[\^([a-zA-Z0-9_-]+)\]/g;
1835
+ let lastIndex = 0;
1836
+ let match;
1837
+ while ((match = footnoteRegex.exec(text)) !== null) {
1838
+ if (match.index > lastIndex) {
1839
+ results.push({
1840
+ type: "text",
1841
+ value: text.substring(lastIndex, match.index)
1842
+ });
1489
1843
  }
1490
- effects.enter("gfmFootnoteCallLabelMarker");
1491
- effects.consume(code);
1492
- effects.exit("gfmFootnoteCallLabelMarker");
1493
- return ok(code);
1844
+ results.push({
1845
+ type: "footnoteReference",
1846
+ identifier: match[1],
1847
+ label: match[1]
1848
+ });
1849
+ lastIndex = match.index + match[0].length;
1850
+ }
1851
+ if (lastIndex < text.length) {
1852
+ results.push({
1853
+ type: "text",
1854
+ value: text.substring(lastIndex)
1855
+ });
1494
1856
  }
1857
+ return results;
1495
1858
  }
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);
1859
+ function transformStrong(token, ctx) {
1860
+ return {
1861
+ type: "strong",
1862
+ children: ctx.transformInline(token.tokens)
1863
+ };
1510
1864
  }
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);
1865
+ function transformEmphasis(token, ctx) {
1866
+ return {
1867
+ type: "emphasis",
1868
+ children: ctx.transformInline(token.tokens)
1869
+ };
1870
+ }
1871
+ function transformCodespan(token) {
1872
+ return {
1873
+ type: "inlineCode",
1874
+ value: token.text
1875
+ };
1876
+ }
1877
+ function transformBreak() {
1878
+ return { type: "break" };
1879
+ }
1880
+ function transformDelete(token, ctx) {
1881
+ return {
1882
+ type: "delete",
1883
+ children: ctx.transformInline(token.tokens)
1884
+ };
1885
+ }
1886
+ function transformInlineHtml(token) {
1887
+ const parsed = parseHtmlFragment(token.text);
1888
+ if (parsed.length > 0) {
1889
+ return parsed;
1519
1890
  }
1520
- /**
1521
- * 计算容器配置
1522
- */
1523
- computeContainerConfig(options) {
1524
- const containers = options.containers;
1525
- if (!containers) return void 0;
1526
- return containers === true ? {} : containers;
1891
+ return { type: "text", value: token.text };
1892
+ }
1893
+ function isTokenType(token, type) {
1894
+ return token.type === type;
1895
+ }
1896
+ var builtinBlockTransformers = {
1897
+ blockMath: (token) => {
1898
+ if (isTokenType(token, "blockMath")) return transformBlockMath(token);
1899
+ return null;
1900
+ },
1901
+ footnoteDefinitionBlock: (token, ctx) => {
1902
+ if (isTokenType(token, "footnoteDefinitionBlock"))
1903
+ return transformFootnoteDefinitionBlock(token, ctx);
1904
+ return null;
1905
+ },
1906
+ explicitDefinition: (token) => {
1907
+ if (isTokenType(token, "explicitDefinition"))
1908
+ return transformExplicitDefinition(token);
1909
+ return null;
1910
+ },
1911
+ def: (token) => {
1912
+ if (isTokenType(token, "def")) return transformDef(token);
1913
+ return null;
1914
+ },
1915
+ container: (token, ctx) => {
1916
+ if (isTokenType(token, "container")) return transformContainer(token, ctx);
1917
+ return null;
1918
+ },
1919
+ footnoteDefinition: (token, ctx) => {
1920
+ if (isTokenType(token, "footnoteDefinition"))
1921
+ return transformFootnoteDefToken(token, ctx);
1922
+ return null;
1923
+ },
1924
+ heading: (token, ctx) => {
1925
+ if (isTokenType(token, "heading")) return transformHeading(token, ctx);
1926
+ return null;
1927
+ },
1928
+ paragraph: (token, ctx) => {
1929
+ if (isTokenType(token, "paragraph")) return transformParagraph(token, ctx);
1930
+ return null;
1931
+ },
1932
+ code: (token) => {
1933
+ if (isTokenType(token, "code")) return transformCode(token);
1934
+ return null;
1935
+ },
1936
+ blockquote: (token, ctx) => {
1937
+ if (isTokenType(token, "blockquote")) return transformBlockquote(token, ctx);
1938
+ return null;
1939
+ },
1940
+ list: (token, ctx) => {
1941
+ if (isTokenType(token, "list")) return transformList(token, ctx);
1942
+ return null;
1943
+ },
1944
+ table: (token, ctx) => {
1945
+ if (isTokenType(token, "table")) return transformTable(token, ctx);
1946
+ return null;
1947
+ },
1948
+ hr: () => transformHr(),
1949
+ html: (token) => {
1950
+ if (isTokenType(token, "html")) return transformHtml(token);
1951
+ return null;
1952
+ },
1953
+ space: () => null,
1954
+ text: (token, ctx) => {
1955
+ if (isTokenType(token, "text")) return transformTextBlock(token, ctx);
1956
+ return null;
1527
1957
  }
1528
- /**
1529
- * 计算 HTML 树配置
1530
- */
1531
- computeHtmlTreeConfig(options) {
1532
- const htmlTree = options.htmlTree;
1533
- if (!htmlTree) return void 0;
1534
- return htmlTree === true ? {} : htmlTree;
1958
+ };
1959
+ var builtinInlineTransformers = {
1960
+ inlineMath: (token) => {
1961
+ if (isTokenType(token, "inlineMath")) return transformInlineMath(token);
1962
+ return null;
1963
+ },
1964
+ optimisticReference: (token, ctx) => {
1965
+ if (isTokenType(token, "optimisticReference"))
1966
+ return transformOptimisticReference(token, ctx);
1967
+ return null;
1968
+ },
1969
+ link: (token, ctx) => {
1970
+ if (isTokenType(token, "link")) return transformLink(token, ctx);
1971
+ return null;
1972
+ },
1973
+ image: (token) => {
1974
+ if (isTokenType(token, "image")) return transformImage(token);
1975
+ return null;
1976
+ },
1977
+ text: (token) => {
1978
+ if (isTokenType(token, "text")) return transformText(token);
1979
+ return null;
1980
+ },
1981
+ escape: (token) => {
1982
+ if (isTokenType(token, "escape")) return transformText(token);
1983
+ return null;
1984
+ },
1985
+ strong: (token, ctx) => {
1986
+ if (isTokenType(token, "strong")) return transformStrong(token, ctx);
1987
+ return null;
1988
+ },
1989
+ em: (token, ctx) => {
1990
+ if (isTokenType(token, "em")) return transformEmphasis(token, ctx);
1991
+ return null;
1992
+ },
1993
+ codespan: (token) => {
1994
+ if (isTokenType(token, "codespan")) return transformCodespan(token);
1995
+ return null;
1996
+ },
1997
+ br: () => transformBreak(),
1998
+ del: (token, ctx) => {
1999
+ if (isTokenType(token, "del")) return transformDelete(token, ctx);
2000
+ return null;
2001
+ },
2002
+ inlineHtml: (token) => {
2003
+ if (isTokenType(token, "inlineHtml")) return transformInlineHtml(token);
2004
+ return null;
1535
2005
  }
1536
- /**
1537
- * 解析文本为 AST
1538
- *
1539
- * @param text Markdown 文本
1540
- * @returns AST
1541
- */
2006
+ };
2007
+ function transformBlockToken(token, ctx) {
2008
+ const tokenType = token.type;
2009
+ if (ctx.customBlockTransformers?.[tokenType]) {
2010
+ const result = ctx.customBlockTransformers[tokenType](token, ctx);
2011
+ if (result !== void 0) return result;
2012
+ }
2013
+ if (builtinBlockTransformers[tokenType]) {
2014
+ const result = builtinBlockTransformers[tokenType](token, ctx);
2015
+ if (result !== void 0) return result;
2016
+ }
2017
+ if ("text" in token && typeof token.text === "string") {
2018
+ const paragraph = {
2019
+ type: "paragraph",
2020
+ children: [{ type: "text", value: token.text }]
2021
+ };
2022
+ return paragraph;
2023
+ }
2024
+ return null;
2025
+ }
2026
+ function transformInlineToken(token, ctx) {
2027
+ const tokenType = token.type;
2028
+ if (ctx.customInlineTransformers?.[tokenType]) {
2029
+ const result = ctx.customInlineTransformers[tokenType](token, ctx);
2030
+ if (result !== void 0) return result;
2031
+ }
2032
+ if (builtinInlineTransformers[tokenType]) {
2033
+ const result = builtinInlineTransformers[tokenType](token, ctx);
2034
+ if (result !== void 0) return result;
2035
+ }
2036
+ if ("text" in token && typeof token.text === "string") {
2037
+ const text = { type: "text", value: token.text };
2038
+ return text;
2039
+ }
2040
+ return null;
2041
+ }
2042
+
2043
+ // src/parser/ast/MarkedAstBuildter.ts
2044
+ var MarkedAstBuilder = class {
2045
+ constructor(options = {}) {
2046
+ this.options = options;
2047
+ this.containerConfig = typeof options.containers === "object" ? options.containers : options.containers === true ? {} : void 0;
2048
+ this.htmlTreeOptions = typeof options.htmlTree === "object" ? options.htmlTree : options.htmlTree === true ? {} : void 0;
2049
+ if (options.plugins) {
2050
+ this.userExtensions.push(...extractMarkedExtensions(options.plugins));
2051
+ }
2052
+ if (options.markedExtensions) {
2053
+ this.userExtensions.push(...options.markedExtensions);
2054
+ }
2055
+ this.transformContext = {
2056
+ transformTokens: this.transformTokens.bind(this),
2057
+ transformTokensWithPosition: this.transformTokensWithPosition.bind(this),
2058
+ transformInline: this.transformInline.bind(this),
2059
+ parseFootnoteContent: this.parseFootnoteContent.bind(this)
2060
+ };
2061
+ }
2062
+ containerConfig;
2063
+ htmlTreeOptions;
2064
+ globalLinks = {};
2065
+ /** 用户传入的 marked 扩展 */
2066
+ userExtensions = [];
2067
+ /** 转换上下文(用于递归转换) */
2068
+ transformContext;
1542
2069
  parse(text) {
1543
- const extensions = [];
1544
- const mdastExtensions = [];
1545
- if (this.options.gfm) {
1546
- extensions.push(gfm());
1547
- mdastExtensions.push(...gfmFromMarkdown(), gfmFootnoteFromMarkdown());
2070
+ const normalizedText = text.replace(/[\u00A0\u200b\u202f]/g, " ");
2071
+ const optimisticRefExt = createOptimisticReferenceExtension();
2072
+ const explicitDefExt = createExplicitDefinitionExtension();
2073
+ const footnoteDefExt = createFootnoteDefinitionExtension();
2074
+ const userBlockExts = [];
2075
+ const userBlockStartExts = [];
2076
+ const userInlineExts = [];
2077
+ const userInlineStartExts = [];
2078
+ for (const ext of this.userExtensions) {
2079
+ if (ext.level === "block") {
2080
+ if (ext.tokenizer) userBlockExts.push(ext.tokenizer);
2081
+ if (ext.start) userBlockStartExts.push(ext.start);
2082
+ } else if (ext.level === "inline") {
2083
+ if (ext.tokenizer) userInlineExts.push(ext.tokenizer);
2084
+ if (ext.start) userInlineStartExts.push(ext.start);
2085
+ }
1548
2086
  }
2087
+ const blockExts = [
2088
+ footnoteDefExt.tokenizer,
2089
+ explicitDefExt.tokenizer,
2090
+ ...userBlockExts
2091
+ ];
2092
+ const blockStartExts = [
2093
+ footnoteDefExt.start,
2094
+ explicitDefExt.start,
2095
+ ...userBlockStartExts
2096
+ ];
2097
+ const inlineExts = [optimisticRefExt.tokenizer, ...userInlineExts];
2098
+ const inlineStartExts = [optimisticRefExt.start, ...userInlineStartExts];
1549
2099
  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);
2100
+ const mathOptions = typeof this.options.math === "object" ? this.options.math : {};
2101
+ const blockMathExt = createBlockMathExtension(mathOptions);
2102
+ const inlineMathExt = createInlineMathExtension(mathOptions);
2103
+ blockExts.unshift(blockMathExt.tokenizer);
2104
+ blockStartExts.unshift(blockMathExt.start);
2105
+ inlineExts.unshift(inlineMathExt.tokenizer);
2106
+ inlineStartExts.unshift(inlineMathExt.start);
2107
+ }
2108
+ if (this.htmlTreeOptions) {
2109
+ const inlineHtmlExt = createInlineHtmlExtension();
2110
+ inlineExts.unshift(inlineHtmlExt.tokenizer);
2111
+ inlineStartExts.unshift(inlineHtmlExt.start);
2112
+ }
2113
+ const lexerOptions = {
2114
+ gfm: true,
2115
+ breaks: false,
2116
+ // 关闭软换行转 break,与 Micromark 保持一致
2117
+ ...this.options,
2118
+ extensions: {
2119
+ inline: inlineExts,
2120
+ startInline: inlineStartExts,
2121
+ block: blockExts,
2122
+ startBlock: blockStartExts
2123
+ }
2124
+ };
2125
+ const lexerInstance = new Lexer(lexerOptions);
2126
+ if (lexerInstance.tokens && lexerInstance.tokens.links) {
2127
+ Object.assign(lexerInstance.tokens.links, this.globalLinks);
1562
2128
  }
1563
- if (this.options.gfm) {
1564
- extensions.push(gfmFootnoteIncremental());
2129
+ let tokens = lexerInstance.lex(normalizedText);
2130
+ if (lexerInstance.tokens && lexerInstance.tokens.links) {
2131
+ Object.assign(this.globalLinks, lexerInstance.tokens.links);
1565
2132
  }
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);
2133
+ tokens = this.preprocessTokens(tokens);
2134
+ let children = this.transformTokensWithPosition(tokens);
2135
+ if (this.htmlTreeOptions) {
2136
+ children = this.processHtmlNodes(children);
1572
2137
  }
1573
- return ast;
2138
+ return {
2139
+ type: "root",
2140
+ children
2141
+ };
1574
2142
  }
1575
2143
  /**
1576
- * HTML 节点转换为纯文本(当未启用 HTML 树转换时)
2144
+ * 预处理 tokens
1577
2145
  *
1578
- * @param ast AST
1579
- * @returns 转换后的 AST
2146
+ * 处理容器指令和遗留的脚注定义(从 paragraph 中提取)
1580
2147
  */
1581
- convertHtmlToText(ast) {
1582
- return {
1583
- ...ast,
1584
- children: this.processBlockChildren(ast.children)
1585
- };
2148
+ preprocessTokens(tokens) {
2149
+ const result = [];
2150
+ let i = 0;
2151
+ while (i < tokens.length) {
2152
+ const token = tokens[i];
2153
+ if (token.type === "paragraph") {
2154
+ const text = token.text;
2155
+ const footnoteMatch = text.match(/^\[\^([a-zA-Z0-9_-]+)\]:\s+([\s\S]*)$/);
2156
+ if (footnoteMatch) {
2157
+ const defToken = {
2158
+ type: "footnoteDefinition",
2159
+ identifier: footnoteMatch[1],
2160
+ text: footnoteMatch[2],
2161
+ tokens: new Lexer().inlineTokens(footnoteMatch[2]),
2162
+ raw: token.raw
2163
+ };
2164
+ result.push(defToken);
2165
+ i++;
2166
+ continue;
2167
+ }
2168
+ const containerStartMatch = text.match(/^:::(\s*)([a-zA-Z0-9_-]+)(.*?)(\n|$)/);
2169
+ if (containerStartMatch) {
2170
+ const name = containerStartMatch[2];
2171
+ const attrs = containerStartMatch[3].trim();
2172
+ let rawAccumulator = "";
2173
+ let j = i;
2174
+ let depth = 0;
2175
+ let foundEnd = false;
2176
+ let contentRaw = "";
2177
+ while (j < tokens.length) {
2178
+ const currentToken = tokens[j];
2179
+ rawAccumulator += currentToken.raw;
2180
+ const lines = rawAccumulator.split("\n");
2181
+ depth = 0;
2182
+ let startLineIndex = -1;
2183
+ let endLineIndex = -1;
2184
+ for (let k = 0; k < lines.length; k++) {
2185
+ const line = lines[k];
2186
+ if (line.match(/^:::(\s*)([a-zA-Z0-9_-]+)/)) {
2187
+ if (depth === 0 && startLineIndex === -1) startLineIndex = k;
2188
+ depth++;
2189
+ } else if (line.trim() === ":::") {
2190
+ depth--;
2191
+ if (depth === 0) {
2192
+ endLineIndex = k;
2193
+ foundEnd = true;
2194
+ break;
2195
+ }
2196
+ }
2197
+ }
2198
+ if (foundEnd) {
2199
+ const contentLines = lines.slice(startLineIndex + 1, endLineIndex);
2200
+ contentRaw = contentLines.join("\n");
2201
+ const remainingLines = lines.slice(endLineIndex + 1);
2202
+ const remainingText = remainingLines.join("\n");
2203
+ const containerToken = {
2204
+ type: "container",
2205
+ name,
2206
+ attrs,
2207
+ tokens: this.preprocessTokens(lexer(contentRaw)),
2208
+ raw: rawAccumulator
2209
+ };
2210
+ result.push(containerToken);
2211
+ if (remainingText.trim()) {
2212
+ const remainingTokens = this.preprocessTokens(lexer(remainingText));
2213
+ result.push(...remainingTokens);
2214
+ }
2215
+ i = j + 1;
2216
+ break;
2217
+ }
2218
+ j++;
2219
+ }
2220
+ if (foundEnd) continue;
2221
+ }
2222
+ }
2223
+ result.push(token);
2224
+ i++;
2225
+ }
2226
+ return result;
1586
2227
  }
1587
2228
  /**
1588
- * 处理块级节点
2229
+ * 转换 tokens 为 MDAST 节点(带位置信息)
1589
2230
  */
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
2231
+ transformTokensWithPosition(tokens) {
2232
+ if (!tokens) return [];
2233
+ const results = [];
2234
+ let currentOffset = 0;
2235
+ for (const token of tokens) {
2236
+ const rawLength = token.raw?.length ?? 0;
2237
+ const node = transformBlockToken(token, this.transformContext);
2238
+ if (node) {
2239
+ node.position = {
2240
+ start: { line: 0, column: 0, offset: currentOffset },
2241
+ end: { line: 0, column: 0, offset: currentOffset + rawLength }
1601
2242
  };
2243
+ results.push(node);
1602
2244
  }
1603
- return node;
1604
- });
2245
+ currentOffset += rawLength;
2246
+ }
2247
+ return results;
1605
2248
  }
1606
2249
  /**
1607
- * 处理内联节点
2250
+ * 转换 tokens 为 MDAST 节点(不带位置信息)
1608
2251
  */
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
- };
2252
+ transformTokens(tokens) {
2253
+ if (!tokens) return [];
2254
+ return tokens.map((t) => transformBlockToken(t, this.transformContext)).filter(Boolean);
2255
+ }
2256
+ /**
2257
+ * 转换行内 tokens
2258
+ */
2259
+ transformInline(tokens) {
2260
+ if (!tokens) return [];
2261
+ const results = [];
2262
+ for (const token of tokens) {
2263
+ const result = transformInlineToken(token, this.transformContext);
2264
+ if (result) {
2265
+ if (Array.isArray(result)) {
2266
+ results.push(...result);
2267
+ } else {
2268
+ results.push(result);
2269
+ }
1621
2270
  }
1622
- return n;
1623
- });
2271
+ }
2272
+ return results;
1624
2273
  }
1625
2274
  /**
1626
- * 将块级 HTML 节点转换为段落
2275
+ * 解析脚注内容为 AST 节点
1627
2276
  */
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;
2277
+ parseFootnoteContent(content) {
2278
+ if (!content.trim()) {
2279
+ return [];
2280
+ }
2281
+ const normalizedContent = content.split("\n").map((line, index) => {
2282
+ if (index === 0) return line;
2283
+ if (line.startsWith(" ")) return line.slice(4);
2284
+ if (line.startsWith(" ")) return line.slice(1);
2285
+ return line;
2286
+ }).join("\n");
2287
+ const contentLexer = new Lexer({ gfm: true, breaks: true });
2288
+ const tokens = contentLexer.lex(normalizedContent);
2289
+ return this.transformTokens(tokens);
1639
2290
  }
1640
2291
  /**
1641
- * 将内联 HTML 节点转换为纯文本节点
2292
+ * 处理 HTML 节点
2293
+ *
2294
+ * 使用 html-extension 的 transformHtmlNodes 来处理:
2295
+ * - 合并被空行分割的 HTML 节点
2296
+ * - 将 HTML 解析为 HtmlElementNode 树结构
1642
2297
  */
1643
- convertInlineHtmlToText(htmlNode) {
1644
- return {
1645
- type: "text",
1646
- value: htmlNode.value,
1647
- position: htmlNode.position
2298
+ processHtmlNodes(nodes) {
2299
+ const tempRoot = {
2300
+ type: "root",
2301
+ children: nodes
1648
2302
  };
2303
+ const transformed = transformHtmlNodes(tempRoot, this.htmlTreeOptions);
2304
+ return transformed.children;
1649
2305
  }
1650
2306
  /**
1651
2307
  * 将 AST 节点转换为 ParsedBlock
1652
- *
1653
- * @param nodes AST 节点列表
1654
- * @param startOffset 起始偏移量
1655
- * @param rawText 原始文本
1656
- * @param status 块状态
1657
- * @param generateBlockId 生成块 ID 的函数
1658
- * @returns ParsedBlock 列表
1659
2308
  */
1660
2309
  nodesToBlocks(nodes, startOffset, rawText, status, generateBlockId) {
1661
2310
  const blocks = [];
1662
- let currentOffset = startOffset;
1663
2311
  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);
2312
+ const relativeStart = node.position?.start?.offset ?? 0;
2313
+ const relativeEnd = node.position?.end?.offset ?? rawText.length;
2314
+ const nodeText = rawText.substring(relativeStart, relativeEnd);
2315
+ const absoluteStart = startOffset + relativeStart;
2316
+ const absoluteEnd = startOffset + relativeEnd;
1667
2317
  blocks.push({
1668
2318
  id: generateBlockId(),
1669
2319
  status,
1670
2320
  node,
1671
- startOffset: nodeStart,
1672
- endOffset: nodeEnd,
2321
+ startOffset: absoluteStart,
2322
+ endOffset: absoluteEnd,
1673
2323
  rawText: nodeText
1674
2324
  });
1675
- currentOffset = nodeEnd;
1676
2325
  }
1677
2326
  return blocks;
1678
2327
  }
2328
+ /**
2329
+ * 更新配置选项
2330
+ * @param options 部分配置选项
2331
+ */
2332
+ updateOptions(options) {
2333
+ Object.assign(this.options, options);
2334
+ if ("containers" in options) {
2335
+ this.containerConfig = typeof options.containers === "object" ? options.containers : options.containers === true ? {} : void 0;
2336
+ }
2337
+ if ("htmlTree" in options) {
2338
+ this.htmlTreeOptions = typeof options.htmlTree === "object" ? options.htmlTree : options.htmlTree === true ? {} : void 0;
2339
+ }
2340
+ if (options.plugins || options.markedExtensions) {
2341
+ this.userExtensions.length = 0;
2342
+ if (options.plugins) {
2343
+ this.userExtensions.push(...extractMarkedExtensions(options.plugins));
2344
+ }
2345
+ if (options.markedExtensions) {
2346
+ this.userExtensions.push(...options.markedExtensions);
2347
+ }
2348
+ }
2349
+ }
1679
2350
  };
1680
2351
 
1681
2352
  // src/parser/IncremarkParser.ts
1682
2353
  var IncremarkParser = class {
1683
- buffer = "";
1684
2354
  lines = [];
1685
2355
  /** 行偏移量前缀和:lineOffsets[i] = 第i行起始位置的偏移量 */
1686
2356
  lineOffsets = [0];
@@ -1705,7 +2375,8 @@ var IncremarkParser = class {
1705
2375
  ...options
1706
2376
  };
1707
2377
  this.context = createInitialContext();
1708
- this.astBuilder = new AstBuilder(this.options);
2378
+ const BuilderClass = options.astBuilder || MarkedAstBuilder;
2379
+ this.astBuilder = new BuilderClass(this.options);
1709
2380
  this.boundaryDetector = new BoundaryDetector({ containers: this.astBuilder.containerConfig });
1710
2381
  this.definitionManager = new DefinitionManager();
1711
2382
  this.footnoteManager = new FootnoteManager();
@@ -1720,36 +2391,30 @@ var IncremarkParser = class {
1720
2391
  this.definitionManager.extractFromBlocks(blocks);
1721
2392
  this.footnoteManager.extractDefinitionsFromBlocks(blocks);
1722
2393
  }
1723
- /**
1724
- * 收集 AST 中的脚注引用(按出现顺序)
1725
- * 用于确定脚注的显示顺序
1726
- */
1727
- collectFootnoteReferences(nodes) {
1728
- this.footnoteManager.collectReferences(nodes);
1729
- }
1730
2394
  /**
1731
2395
  * 增量更新 lines 和 lineOffsets
1732
- * 只处理新增的内容,避免全量 split
2396
+ * 优化策略:只 split 新增的 chunk,不拼接旧字符串,避免长行性能劣化
1733
2397
  */
1734
- updateLines() {
2398
+ updateLines(chunk) {
1735
2399
  const prevLineCount = this.lines.length;
1736
2400
  if (prevLineCount === 0) {
1737
- this.lines = this.buffer.split("\n");
2401
+ this.lines = chunk.split("\n");
1738
2402
  this.lineOffsets = [0];
1739
- for (let i = 0; i < this.lines.length; i++) {
2403
+ for (let i = 0; i < this.lines.length - 1; i++) {
1740
2404
  this.lineOffsets.push(this.lineOffsets[i] + this.lines[i].length + 1);
1741
2405
  }
1742
2406
  return;
1743
2407
  }
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);
2408
+ const chunkLines = chunk.split("\n");
2409
+ const lastLineIndex = prevLineCount - 1;
2410
+ this.lines[lastLineIndex] += chunkLines[0];
2411
+ for (let i = 1; i < chunkLines.length; i++) {
2412
+ const prevLineIndex = this.lines.length - 1;
2413
+ const prevLineStart = this.lineOffsets[prevLineIndex];
2414
+ const prevLineLength = this.lines[prevLineIndex].length;
2415
+ const newOneOffset = prevLineStart + prevLineLength + 1;
2416
+ this.lineOffsets.push(newOneOffset);
2417
+ this.lines.push(chunkLines[i]);
1753
2418
  }
1754
2419
  }
1755
2420
  /**
@@ -1774,8 +2439,7 @@ var IncremarkParser = class {
1774
2439
  * 追加新的 chunk 并返回增量更新
1775
2440
  */
1776
2441
  append(chunk) {
1777
- this.buffer += chunk;
1778
- this.updateLines();
2442
+ this.updateLines(chunk);
1779
2443
  const { line: stableBoundary, contextAtLine } = this.findStableBoundary();
1780
2444
  const update = {
1781
2445
  completed: [],
@@ -1794,6 +2458,8 @@ var IncremarkParser = class {
1794
2458
  this.completedBlocks.push(...newBlocks);
1795
2459
  update.completed = newBlocks;
1796
2460
  this.updateDefinitionsFromCompletedBlocks(newBlocks);
2461
+ this.footnoteManager.collectReferencesFromCompletedBlocks(newBlocks);
2462
+ this.boundaryDetector.clearContextCache(this.pendingStartLine);
1797
2463
  this.context = contextAtLine;
1798
2464
  this.pendingStartLine = stableBoundary + 1;
1799
2465
  }
@@ -1810,10 +2476,9 @@ var IncremarkParser = class {
1810
2476
  type: "root",
1811
2477
  children: [...this.completedBlocks.map((b) => b.node), ...update.pending.map((b) => b.node)]
1812
2478
  };
1813
- this.collectFootnoteReferences(update.ast.children);
2479
+ update.footnoteReferenceOrder = this.footnoteManager.collectReferencesFromPending(update.pending);
1814
2480
  update.definitions = this.getDefinitionMap();
1815
2481
  update.footnoteDefinitions = this.getFootnoteDefinitionMap();
1816
- update.footnoteReferenceOrder = this.getFootnoteReferenceOrder();
1817
2482
  this.emitChange(update.pending);
1818
2483
  return update;
1819
2484
  }
@@ -1825,7 +2490,7 @@ var IncremarkParser = class {
1825
2490
  const state = {
1826
2491
  completedBlocks: this.completedBlocks,
1827
2492
  pendingBlocks,
1828
- markdown: this.buffer,
2493
+ markdown: this.lines.join("\n"),
1829
2494
  ast: {
1830
2495
  type: "root",
1831
2496
  children: [
@@ -1868,6 +2533,8 @@ var IncremarkParser = class {
1868
2533
  this.completedBlocks.push(...finalBlocks);
1869
2534
  update.completed = finalBlocks;
1870
2535
  this.updateDefinitionsFromCompletedBlocks(finalBlocks);
2536
+ this.footnoteManager.collectReferencesFromCompletedBlocks(finalBlocks);
2537
+ this.boundaryDetector.clearContextCache(this.pendingStartLine);
1871
2538
  }
1872
2539
  }
1873
2540
  this.lastPendingBlocks = [];
@@ -1876,7 +2543,6 @@ var IncremarkParser = class {
1876
2543
  type: "root",
1877
2544
  children: this.completedBlocks.map((b) => b.node)
1878
2545
  };
1879
- this.collectFootnoteReferences(update.ast.children);
1880
2546
  update.definitions = this.getDefinitionMap();
1881
2547
  update.footnoteDefinitions = this.getFootnoteDefinitionMap();
1882
2548
  update.footnoteReferenceOrder = this.getFootnoteReferenceOrder();
@@ -1899,7 +2565,7 @@ var IncremarkParser = class {
1899
2565
  ...this.completedBlocks.map((b) => b.node),
1900
2566
  ...this.lastPendingBlocks.map((b) => b.node)
1901
2567
  ];
1902
- this.collectFootnoteReferences(children);
2568
+ this.footnoteManager.collectReferencesFromPending(this.lastPendingBlocks);
1903
2569
  return {
1904
2570
  type: "root",
1905
2571
  children
@@ -1915,7 +2581,7 @@ var IncremarkParser = class {
1915
2581
  * 获取当前缓冲区内容
1916
2582
  */
1917
2583
  getBuffer() {
1918
- return this.buffer;
2584
+ return this.lines.join("\n");
1919
2585
  }
1920
2586
  /**
1921
2587
  * 获取 Definition 映射表(用于引用式图片和链接)
@@ -1949,7 +2615,6 @@ var IncremarkParser = class {
1949
2615
  * 重置解析器状态
1950
2616
  */
1951
2617
  reset() {
1952
- this.buffer = "";
1953
2618
  this.lines = [];
1954
2619
  this.lineOffsets = [0];
1955
2620
  this.completedBlocks = [];
@@ -1971,6 +2636,40 @@ var IncremarkParser = class {
1971
2636
  this.append(content);
1972
2637
  return this.finalize();
1973
2638
  }
2639
+ /**
2640
+ * 更新解析器配置(动态更新,不需要重建 parser 实例)
2641
+ *
2642
+ * 注意:更新配置后会自动调用 reset() 重置状态
2643
+ *
2644
+ * @param options 部分配置选项
2645
+ *
2646
+ * @example
2647
+ * ```ts
2648
+ * // 动态启用 TeX 数学公式语法
2649
+ * parser.updateOptions({ math: { tex: true } })
2650
+ *
2651
+ * // 禁用 GFM
2652
+ * parser.updateOptions({ gfm: false })
2653
+ *
2654
+ * // 切换引擎
2655
+ * import { MicromarkAstBuilder } from '@incremark/core/engines/micromark'
2656
+ * parser.updateOptions({ astBuilder: MicromarkAstBuilder })
2657
+ * ```
2658
+ */
2659
+ updateOptions(options) {
2660
+ this.reset();
2661
+ Object.assign(this.options, options);
2662
+ if (options.astBuilder) {
2663
+ const BuilderClass = options.astBuilder;
2664
+ if (!(this.astBuilder instanceof BuilderClass)) {
2665
+ this.astBuilder = new BuilderClass(this.options);
2666
+ } else {
2667
+ this.astBuilder.updateOptions(options);
2668
+ }
2669
+ } else {
2670
+ this.astBuilder.updateOptions(options);
2671
+ }
2672
+ }
1974
2673
  };
1975
2674
  function createIncremarkParser(options) {
1976
2675
  return new IncremarkParser(options);
@@ -1993,10 +2692,9 @@ function countCharsInNode(n) {
1993
2692
  }
1994
2693
  return 1;
1995
2694
  }
1996
- function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2695
+ function sliceAst(node, maxChars, accumulatedChunks) {
1997
2696
  if (maxChars <= 0) return null;
1998
- if (skipChars >= maxChars) return null;
1999
- let remaining = maxChars - skipChars;
2697
+ let remaining = maxChars;
2000
2698
  let charIndex = 0;
2001
2699
  const chunkRanges = [];
2002
2700
  if (accumulatedChunks && accumulatedChunks.chunks.length > 0) {
@@ -2015,16 +2713,10 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2015
2713
  if (n.value && typeof n.value === "string") {
2016
2714
  const nodeStart = charIndex;
2017
2715
  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);
2716
+ const take = Math.min(n.value.length, remaining);
2024
2717
  remaining -= take;
2025
- if (take === 0) return null;
2026
- const slicedValue = n.value.slice(skipInNode, skipInNode + take);
2027
2718
  charIndex = nodeEnd;
2719
+ const slicedValue = n.value.slice(0, take);
2028
2720
  const result = {
2029
2721
  ...n,
2030
2722
  value: slicedValue
@@ -2033,11 +2725,11 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2033
2725
  const nodeChunks = [];
2034
2726
  let firstChunkLocalStart = take;
2035
2727
  for (const range of chunkRanges) {
2036
- const overlapStart = Math.max(range.start, nodeStart + skipInNode);
2037
- const overlapEnd = Math.min(range.end, nodeStart + skipInNode + take);
2728
+ const overlapStart = Math.max(range.start, nodeStart);
2729
+ const overlapEnd = Math.min(range.end, nodeStart + take);
2038
2730
  if (overlapStart < overlapEnd) {
2039
- const localStart = overlapStart - (nodeStart + skipInNode);
2040
- const localEnd = overlapEnd - (nodeStart + skipInNode);
2731
+ const localStart = overlapStart - nodeStart;
2732
+ const localEnd = overlapEnd - nodeStart;
2041
2733
  const chunkText = slicedValue.slice(localStart, localEnd);
2042
2734
  if (chunkText.length > 0) {
2043
2735
  if (nodeChunks.length === 0) {
@@ -2059,24 +2751,12 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2059
2751
  }
2060
2752
  if (n.children && Array.isArray(n.children)) {
2061
2753
  const newChildren = [];
2062
- let childCharIndex = charIndex;
2063
2754
  for (const child of n.children) {
2064
2755
  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
2756
  const processed = process(child);
2075
- charIndex = savedCharIndex;
2076
2757
  if (processed) {
2077
2758
  newChildren.push(processed);
2078
2759
  }
2079
- childCharIndex = childEnd;
2080
2760
  }
2081
2761
  if (newChildren.length === 0) {
2082
2762
  return null;
@@ -2089,10 +2769,7 @@ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
2089
2769
  }
2090
2770
  return process(node);
2091
2771
  }
2092
- function appendToAst(baseNode, sourceNode, startChars, endChars, accumulatedChunks) {
2093
- if (endChars <= startChars) {
2094
- return baseNode;
2095
- }
2772
+ function appendToAst(baseNode, sourceNode, endChars, accumulatedChunks) {
2096
2773
  const fullSlice = sliceAst(sourceNode, endChars, accumulatedChunks);
2097
2774
  if (!fullSlice) {
2098
2775
  return baseNode;
@@ -2617,7 +3294,6 @@ var BlockTransformer = class {
2617
3294
  this.cachedDisplayNode = appendToAst(
2618
3295
  this.cachedDisplayNode,
2619
3296
  block.node,
2620
- this.cachedProgress,
2621
3297
  currentProgress,
2622
3298
  this.getAccumulatedChunks()
2623
3299
  );
@@ -2733,51 +3409,7 @@ function createPlugin(name, matcher, options = {}) {
2733
3409
  ...options
2734
3410
  };
2735
3411
  }
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
3412
 
2781
- export { BlockTransformer, IncremarkParser, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createIncremarkParser, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin };
3413
+ export { BlockTransformer, IncremarkParser, MarkedAstBuilder, allPlugins, cloneNode, codeBlockPlugin, countChars, createBlockTransformer, createIncremarkParser, createPlugin, defaultPlugins, imagePlugin, mathPlugin, mermaidPlugin, sliceAst, thematicBreakPlugin };
2782
3414
  //# sourceMappingURL=index.js.map
2783
3415
  //# sourceMappingURL=index.js.map