asciidoc-abundant-tree 0.2.1 → 0.2.2

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/README.md CHANGED
@@ -295,7 +295,7 @@ const book = await parseAbundantTree({
295
295
  });
296
296
  ```
297
297
 
298
- `attributes` are the include attributes as Asciidoctor parses them, the same an Asciidoctor include processor receives, so the bookshelf build and this parser can register the same selector. A resolver that throws fails construction with `include.resolver-failed`; returned text that does not match its source leaves those lines without coordinates and reports `origin-map.selection-mismatch`.
298
+ `attributes` are the include attributes as Asciidoctor parses them, the same an Asciidoctor include processor receives, so the bookshelf build and this parser can register the same selector. A blank positional attribute is `null`: the first one in `[,beta]`, the second one in `[beta,,gamma]`. A resolver that throws fails construction with `include.resolver-failed`; returned text that does not match its source leaves those lines without coordinates and reports `origin-map.selection-mismatch`.
299
299
 
300
300
  `parseAbundantTree` reads only the supplied source file. Interdocument xrefs keep their raw target and official href when Asciidoctor exposes one, but this package does not open the referenced `.adoc` file.
301
301
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { o as exportAnimationYaml } from "../src-DMOLXZbF.mjs";
2
+ import { o as exportAnimationYaml } from "../src-DmT-MEPN.mjs";
3
3
  import process from "node:process";
4
4
  //#region src/animation-yaml-export/cli.ts
5
5
  const USAGE = `Usage:
@@ -154,7 +154,7 @@ type XrefOccurrenceNode = NodeBase & {
154
154
  };
155
155
  type AnchorOccurrenceNode = NodeBase & {
156
156
  kind: "anchor";
157
- syntax: "double-bracket" | "macro";
157
+ syntax: "double-bracket" | "macro" | "bibliography";
158
158
  raw: string;
159
159
  ids: string[];
160
160
  reftext?: string;
@@ -179,6 +179,7 @@ type TargetNode = NodeBase & {
179
179
  title?: string;
180
180
  idOrigin: "source" | "asciidoctor-generated" | "unknown";
181
181
  sourceSpan?: SourceSpan;
182
+ containingSectionId?: string;
182
183
  };
183
184
  type TextNode = NodeBase & {
184
185
  kind: "text";
package/dist/browser.mjs CHANGED
@@ -204,6 +204,7 @@ function addAnchorTargets(targets, anchorOccurrences) {
204
204
  title: anchor.reftext,
205
205
  idOrigin: "source",
206
206
  sourceSpan: anchor.sourceSpan,
207
+ containingSectionId: anchor.containingSectionId,
207
208
  asciidoctor: definedObject({
208
209
  resolvedId: id,
209
210
  resolvedType: targetType,
@@ -371,7 +372,8 @@ function recoverSourceAwareSourceLayer(document, logicalSpan, options) {
371
372
  const lastRecord = sourceRecords.at(-1);
372
373
  if (!firstRecord || !lastRecord) return recoveryFailure$1("source-coordinate.generated-line", `Parser core interval ${logicalSpan.startLine}-${logicalSpan.endLine} has no source-backed lines for ${options.diagnosticContext}.`, options.logicalSourceSpan);
373
374
  const relativePath = firstRecord.origin.relativePath;
374
- if (!relativePath || sourceRecords.some((record) => record.origin.relativePath !== relativePath)) return recoveryFailure$1("block-origin.multi-source-span", `Block span ${logicalSpan.startLine}-${logicalSpan.endLine} maps to more than one origin source file for ${options.diagnosticContext}.`, options.logicalSourceSpan);
375
+ if (firstRecord.origin.absolutePath !== lastRecord.origin.absolutePath) return recoveryFailure$1("block-origin.multi-source-span", `${options.diagnosticContext} starts at ${originLabel(firstRecord.origin)} and ends at ${originLabel(lastRecord.origin)}; no single origin source file holds its raw.`, options.logicalSourceSpan);
376
+ if (!authorLinesInSourceOrder(sourceRecords, firstRecord.origin)) return recoveryFailure$1("block-origin.multi-source-span", `${options.diagnosticContext} from ${originLabel(firstRecord.origin)} to ${originLabel(lastRecord.origin)} reads ${relativePath} out of source order; no contiguous raw exists.`, options.logicalSourceSpan);
375
377
  const sourceFile = sourceFileForOrigin$1(document, firstRecord.origin);
376
378
  if (!sourceFile) return recoveryFailure$1("line-origin.source-file-missing", `Origin file line table is missing for ${relativePath}.`, options.logicalSourceSpan);
377
379
  const lineSpan = {
@@ -423,6 +425,47 @@ function recoverSourceAwarePointSourceLayer(document, logicalSourceSpan, raw) {
423
425
  }
424
426
  };
425
427
  }
428
+ /**
429
+ * 块的 content span 在作者文件里的行区间。
430
+ *
431
+ * 内容的首行或尾行来自被引入的文件时,作者文件里与之对应的是那条 include 指令:从块里紧挨在内容之前的
432
+ * 作者行往后一行起,到紧挨在内容之后的作者行往前一行止。作者文件由块的首行决定,调用方应已用
433
+ * {@link recoverSourceAwareSourceLayer} 确认整块落在同一个作者文件里。
434
+ */
435
+ function recoverSourceAwareContentLineSpan(document, blockSpan, contentSpan) {
436
+ const blockRecords = recordsForSpan(document, blockSpan)?.filter(hasOrigin);
437
+ const author = blockRecords?.[0]?.origin.absolutePath;
438
+ if (!blockRecords || author === void 0) return;
439
+ const authored = blockRecords.filter((record) => record.origin.absolutePath === author);
440
+ const inside = (record) => record.logicalLine >= contentSpan.startLine && record.logicalLine <= contentSpan.endLine;
441
+ const firstInside = authored.find(inside);
442
+ const lastInside = authored.findLast(inside);
443
+ const before = authored.findLast((record) => record.logicalLine < contentSpan.startLine);
444
+ const after = authored.find((record) => record.logicalLine > contentSpan.endLine);
445
+ const startsWithAuthorLine = firstInside !== void 0 && firstInside.logicalLine === firstOriginLine(blockRecords, contentSpan);
446
+ const endsWithAuthorLine = lastInside !== void 0 && lastInside.logicalLine === lastOriginLine(blockRecords, contentSpan);
447
+ const startLine = startsWithAuthorLine ? firstInside.origin.sourceLine : before && before.origin.sourceLine + 1;
448
+ const endLine = endsWithAuthorLine ? lastInside.origin.sourceLine : after && after.origin.sourceLine - 1;
449
+ if (startLine === void 0 || endLine === void 0 || startLine > endLine) return;
450
+ return {
451
+ startLine,
452
+ endLine
453
+ };
454
+ }
455
+ /**
456
+ * 一段逻辑行在源文件里落在哪些行上:`relativePath` 与 `line` 的组合,用 {@link originPointKey} 编成键。
457
+ *
458
+ * 出现处回源后只剩来源坐标;判断它在不在一个块里,就看它的来源是不是这个块某条逻辑行的来源
459
+ * (10 号书「结构关系在 Logical Document 上判定」)。被引入文件里的行也在其中。
460
+ */
461
+ function originPointsForSpan(document, logicalSpan) {
462
+ const points = /* @__PURE__ */ new Set();
463
+ for (const record of recordsForSpan(document, logicalSpan) ?? []) if (hasOrigin(record)) points.add(originPointKey(record.origin.relativePath, record.origin.sourceLine));
464
+ return points;
465
+ }
466
+ function originPointKey(relativePath, line) {
467
+ return `${relativePath}\n${line}`;
468
+ }
426
469
  function recoverSourceAwareDocumentTitleSourceLayer(document, logicalTitleSpan) {
427
470
  if (!logicalTitleSpan) return;
428
471
  return recoverSourceAwarePointSourceLayer(document, logicalTitleSpan, document.lines[logicalTitleSpan.start.line - 1]?.text ?? "");
@@ -460,22 +503,6 @@ function recoverSourceAwareTitleSpan(document, logicalTitleSpan) {
460
503
  const recovered = recoverSourceAwareSpan(document, logicalTitleSpan, false);
461
504
  return recovered.ok ? recovered.sourceSpan : void 0;
462
505
  }
463
- function sourceScopeFromSourceAwareInterval(document, span, headingLogicalLine) {
464
- const headingRecord = document.lines[headingLogicalLine - 1];
465
- if (!headingRecord || !hasOrigin(headingRecord)) return;
466
- let endLine = headingRecord.origin.sourceLine;
467
- for (let logicalLine = span.startLine; logicalLine <= span.endLine; logicalLine += 1) {
468
- const record = document.lines[logicalLine - 1];
469
- if (!record || !hasOrigin(record)) continue;
470
- if (record.origin.relativePath !== headingRecord.origin.relativePath) continue;
471
- endLine = Math.max(endLine, record.origin.sourceLine);
472
- }
473
- return {
474
- relativePath: headingRecord.origin.relativePath,
475
- startLine: headingRecord.origin.sourceLine,
476
- endLine
477
- };
478
- }
479
506
  function recoverSourceAwareSpan(document, logicalSourceSpan, requireColumns) {
480
507
  const start = recoverSourceAwarePoint(document, logicalSourceSpan.start, requireColumns);
481
508
  if (!start.ok) return start;
@@ -487,7 +514,7 @@ function recoverSourceAwareSpan(document, logicalSourceSpan, requireColumns) {
487
514
  line: start.line
488
515
  }
489
516
  };
490
- if (start.relativePath !== end.relativePath) return recoveryFailure$1("block-origin.multi-source-span", `Source span ${logicalSourceSpan.start.line}-${logicalSourceSpan.end.line} maps to more than one origin source file.`, logicalSourceSpan);
517
+ if (start.relativePath !== end.relativePath) return recoveryFailure$1("block-origin.multi-source-span", `Source span starts at ${start.relativePath}:${start.line} and ends at ${end.relativePath}:${end.line}; no single origin source file holds it.`, logicalSourceSpan);
491
518
  return {
492
519
  ok: true,
493
520
  relativePath: start.relativePath,
@@ -557,6 +584,24 @@ function recordsForSpan(document, span) {
557
584
  function hasOrigin(record) {
558
585
  return "origin" in record;
559
586
  }
587
+ function originLabel(origin) {
588
+ return `${origin.relativePath}:${origin.sourceLine}`;
589
+ }
590
+ function authorLinesInSourceOrder(records, author) {
591
+ let previous = 0;
592
+ for (const record of records) {
593
+ if (record.origin.absolutePath !== author.absolutePath) continue;
594
+ if (record.origin.sourceLine <= previous) return false;
595
+ previous = record.origin.sourceLine;
596
+ }
597
+ return true;
598
+ }
599
+ function firstOriginLine(records, span) {
600
+ return records.find((record) => record.logicalLine >= span.startLine && record.logicalLine <= span.endLine)?.logicalLine;
601
+ }
602
+ function lastOriginLine(records, span) {
603
+ return records.findLast((record) => record.logicalLine >= span.startLine && record.logicalLine <= span.endLine)?.logicalLine;
604
+ }
560
605
  function sourceFileForOrigin$1(document, origin) {
561
606
  return document.sourceFiles.find((sourceFile) => sourceFile.absolutePath === origin.absolutePath);
562
607
  }
@@ -1101,7 +1146,7 @@ function buildDescriptionList(block, startLine, context, interval) {
1101
1146
  delimiter: descriptionListDelimiterMatch(context.lineTable, startLine)?.raw,
1102
1147
  metadata: metadata.map((surface) => surface.node),
1103
1148
  metadataSpan: interval?.metadataSpan,
1104
- contentSpan: source ? originLineSpan(context, contentSpan) : void 0,
1149
+ contentSpan: source ? originLineSpan(context, contentSpan, span) : void 0,
1105
1150
  span: source?.span,
1106
1151
  source,
1107
1152
  asciidoctor: definedObject({
@@ -1380,7 +1425,7 @@ function buildListing(block, delimiterLine, context, interval) {
1380
1425
  metadata: metadata.map((surface) => surface.node),
1381
1426
  content: block.getSource?.() ?? (contentSpan ? sourceLines(context.lineTable, contentSpan.startLine, contentSpan.endLine).join("\n") : void 0),
1382
1427
  metadataSpan: interval?.metadataSpan,
1383
- contentSpan: source ? originLineSpan(context, contentSpan) : void 0,
1428
+ contentSpan: source ? originLineSpan(context, contentSpan, span) : void 0,
1384
1429
  span: source?.span,
1385
1430
  source,
1386
1431
  asciidoctor: definedObject({
@@ -1484,10 +1529,15 @@ function registerOfficialBlockTarget(context, block, options) {
1484
1529
  title: options.title ?? block.getTitle?.(),
1485
1530
  idOrigin: options.idOrigin ?? (options.targetType === "section" ? "asciidoctor-generated" : "source"),
1486
1531
  sourceSpan: options.sourceSpan,
1532
+ containingSectionId: options.targetType === "section" ? void 0 : blockSectionId(context, block),
1487
1533
  source: options.source,
1488
1534
  asciidoctor
1489
1535
  }));
1490
1536
  }
1537
+ function blockSectionId(context, block) {
1538
+ const line = context.intervalByBlock.get(block)?.span.startLine;
1539
+ return line === void 0 ? void 0 : context.sectionByLine.get(line)?.ids[0];
1540
+ }
1491
1541
  function blockSourceLayer(context, interval, fallbackSpan, diagnosticContext) {
1492
1542
  if (!context.logicalSource && !context.sourceAwareDocument) {
1493
1543
  const sourceSpan = sourceSpanFromLineSpan(context.lineTable, fallbackSpan);
@@ -1517,15 +1567,9 @@ function blockSourceLayer(context, interval, fallbackSpan, diagnosticContext) {
1517
1567
  function sameLineSpan(left, right) {
1518
1568
  return left.startLine === right.startLine && left.endLine === right.endLine;
1519
1569
  }
1520
- function originLineSpan(context, span) {
1570
+ function originLineSpan(context, span, blockSpan) {
1521
1571
  if (!span || !context.logicalSource && !context.sourceAwareDocument) return span;
1522
- if (context.sourceAwareDocument) {
1523
- const recovered = recoverSourceAwareSourceLayer(context.sourceAwareDocument, span, {
1524
- raw: false,
1525
- diagnosticContext: "block content"
1526
- });
1527
- return recovered.ok ? recovered.lineSpan : void 0;
1528
- }
1572
+ if (context.sourceAwareDocument) return recoverSourceAwareContentLineSpan(context.sourceAwareDocument, blockSpan, span);
1529
1573
  if (!context.logicalSource) return;
1530
1574
  const recovered = recoverOriginSourceLayer(context.logicalSource, span, {
1531
1575
  raw: false,
@@ -1544,6 +1588,13 @@ function collectHeadingInlineMetadataInSpan(context, logicalSpan, source) {
1544
1588
  }
1545
1589
  function collectOccurrencesInSpan(context, groupedByLine, occurrences, logicalSpan, source) {
1546
1590
  if (!context.logicalSource && !context.sourceAwareDocument) return collectOccurrencesInLineRange(groupedByLine, logicalSpan.startLine, logicalSpan.endLine);
1591
+ if (context.sourceAwareDocument) {
1592
+ const points = originPointsForSpan(context.sourceAwareDocument, logicalSpan);
1593
+ return occurrences.filter((occurrence) => {
1594
+ const { relativePath, line } = occurrence.source ?? {};
1595
+ return relativePath !== void 0 && line !== void 0 && points.has(originPointKey(relativePath, line));
1596
+ });
1597
+ }
1547
1598
  if (!source?.relativePath || !source.span) return [];
1548
1599
  const relativePath = source.relativePath;
1549
1600
  const sourceSpan = source.span;
@@ -1611,61 +1662,12 @@ function locate(block, location) {
1611
1662
  if (location) block.sourceLocation = location;
1612
1663
  }
1613
1664
  //#endregion
1614
- //#region src/book-entry/source-scope-index.ts
1615
- const sectionSourceScopeBySection = /* @__PURE__ */ new WeakMap();
1616
- function registerSectionSourceScope(section, scope) {
1617
- sectionSourceScopeBySection.set(section, scope);
1618
- }
1619
- function buildSourceScopeIndex(sections) {
1620
- return { candidates: sections.flatMap((section) => {
1621
- const scope = sourceScopeForSection(section);
1622
- if (!scope) return [];
1623
- return [{
1624
- ...scope,
1625
- section
1626
- }];
1627
- }) };
1628
- }
1629
- function sourceScopeForSection(section) {
1630
- const registered = sectionSourceScopeBySection.get(section);
1631
- if (registered) return registered;
1632
- const relativePath = section.source?.relativePath;
1633
- const startLine = section.source?.span?.startLine ?? section.source?.line;
1634
- const endLine = section.source?.span?.endLine ?? section.source?.line ?? startLine;
1635
- if (!relativePath || startLine === void 0 || endLine === void 0) return;
1636
- return {
1637
- relativePath,
1638
- startLine,
1639
- endLine
1640
- };
1641
- }
1642
- function lookupContainingSection(index, lookup) {
1643
- const relativePath = lookup.relativePath ?? lookup.source?.relativePath;
1644
- const line = lookup.line ?? lookup.source?.line;
1645
- if (!relativePath || line === void 0) return;
1646
- let containingSection;
1647
- for (const candidate of index.candidates) {
1648
- if (candidate.relativePath !== relativePath) continue;
1649
- if (line < candidate.startLine || line > candidate.endLine) continue;
1650
- containingSection = candidate.section;
1651
- }
1652
- return containingSection;
1653
- }
1654
- function assignContainingSectionIdsFromSourceScope(xrefs, anchors, headingInlineMetadataOccurrences, index) {
1655
- for (const occurrence of [
1656
- ...xrefs,
1657
- ...anchors,
1658
- ...headingInlineMetadataOccurrences
1659
- ]) {
1660
- const sectionId = lookupContainingSection(index, occurrence)?.ids[0];
1661
- if (sectionId !== void 0) occurrence.containingSectionId = sectionId;
1662
- }
1663
- }
1664
- //#endregion
1665
1665
  //#region src/inline-occurrence-scanner.ts
1666
1666
  const shorthandXrefPattern = /<<([^>,]+)(?:,\s*([^>]+))?>>/gu;
1667
1667
  const macroXrefPattern = /xref:([^\s[]+)\[([^\]]*)\]/gu;
1668
- const anchorPattern = /\[\[([^,\]]+)(?:,([^\]]+))?\]\]|anchor:([^\s[]+)\[([^\]]*)\]/gu;
1668
+ const anchorId = String.raw`[\p{Alphabetic}_:][\p{Alphabetic}\p{N}\p{Pc}\-:.]*`;
1669
+ const anchorPattern = new RegExp(String.raw`(?<![\\\[])\[\[(${anchorId})(?:, *(.+?))? ?\]\]` + String.raw`|(?<!\\)anchor:(${anchorId})\[(?:\]|(.*?[^\\])\])`, "gu");
1670
+ const bibliographyAnchorPattern = new RegExp(String.raw`^(\s*[*-]+\s+)\[\[\[(${anchorId})(?:, *(.+?))?\]\]\]`, "u");
1669
1671
  const headingInlineMetadataPattern = /hmeta:([^\s[]+)\[([^\]]*)\]/gu;
1670
1672
  function scanInlineOccurrencesInOfficialBlocks(options) {
1671
1673
  const xrefOccurrences = [];
@@ -1683,8 +1685,9 @@ function scanInlineOccurrencesInOfficialBlocks(options) {
1683
1685
  if (officialBlockPolicy(surface.block) !== "scan") continue;
1684
1686
  if (!scansMacroSubstitutedContent(surface)) continue;
1685
1687
  if (surface.context === "table") scanTableInlineRange(options.lineTable, surface.block, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics);
1686
- else scanInlineRange(options.lineTable, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics, descendantUnscannableRanges(surface, options.intervalByBlock));
1688
+ else scanInlineRange(options.lineTable, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics, descendantUnscannableRanges(surface, options.intervalByBlock), isBibliographyList(surface));
1687
1689
  }
1690
+ assignContainingSectionIds(xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.sectionByLine);
1688
1691
  const sourceAwareDocument = sourceAwareDocumentForLineTable(options.lineTable);
1689
1692
  const logicalSource = logicalSourceForLineTable(options.lineTable);
1690
1693
  const recoveredXrefs = sourceAwareDocument ? recoverSourceAwareInlineOrigins(xrefOccurrences, sourceAwareDocument, options.toolDiagnostics) : logicalSource ? recoverInlineOrigins(xrefOccurrences, logicalSource, options.toolDiagnostics) : xrefOccurrences;
@@ -1801,12 +1804,13 @@ function parseMacroLabel(raw) {
1801
1804
  function compareSourceSpans(left, right) {
1802
1805
  return (left.sourceSpan?.start.line ?? 0) - (right.sourceSpan?.start.line ?? 0) || (left.sourceSpan?.start.column ?? 0) - (right.sourceSpan?.start.column ?? 0);
1803
1806
  }
1804
- function scanInlineRange(lineTable, startLine, endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, toolDiagnostics, skipRanges = []) {
1807
+ function scanInlineRange(lineTable, startLine, endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, toolDiagnostics, skipRanges = [], bibliography = false) {
1805
1808
  for (let lineNumber = startLine; lineNumber <= endLine; lineNumber += 1) {
1806
1809
  if (isLineInRanges(lineNumber, skipRanges)) continue;
1807
1810
  const line = lineTable.lines[lineNumber - 1];
1808
1811
  if (!line) continue;
1809
1812
  scanXrefMatches(lineTable, line, xrefOccurrences);
1813
+ if (bibliography) scanBibliographyAnchor(lineTable, line, anchorOccurrences);
1810
1814
  scanAnchorMatches(lineTable, line, anchorOccurrences);
1811
1815
  scanHeadingInlineMetadataMatches(lineTable, line, headingInlineMetadataOccurrences, toolDiagnostics);
1812
1816
  }
@@ -1965,7 +1969,7 @@ function scanAnchorMatches(lineTable, line, anchorOccurrences) {
1965
1969
  syntax: doubleBracketId ? "double-bracket" : "macro",
1966
1970
  raw,
1967
1971
  ids: [id],
1968
- reftext: match[2] ?? match[4] ?? void 0,
1972
+ reftext: doubleBracketId ? match[2] : match[4] ?? "",
1969
1973
  anchorScope: line.text.trim() === raw && doubleBracketId ? "block" : "inline",
1970
1974
  sourceSpan,
1971
1975
  source: {
@@ -1976,6 +1980,31 @@ function scanAnchorMatches(lineTable, line, anchorOccurrences) {
1976
1980
  }));
1977
1981
  }
1978
1982
  }
1983
+ function scanBibliographyAnchor(lineTable, line, anchorOccurrences) {
1984
+ const match = bibliographyAnchorPattern.exec(line.text);
1985
+ const [, marker, id, reftext] = match ?? [];
1986
+ if (marker === void 0 || id === void 0) return;
1987
+ const raw = match?.[0].slice(marker.length) ?? "";
1988
+ const sourceSpan = sourceSpanForRange(lineTable, line.number, marker.length, raw);
1989
+ anchorOccurrences.push(definedObject({
1990
+ kind: "anchor",
1991
+ syntax: "bibliography",
1992
+ raw,
1993
+ ids: [id],
1994
+ reftext,
1995
+ anchorScope: "inline",
1996
+ sourceSpan,
1997
+ source: {
1998
+ raw,
1999
+ line: line.number,
2000
+ sourceSpan
2001
+ }
2002
+ }));
2003
+ }
2004
+ function isBibliographyList(surface) {
2005
+ const list = surface.context === "list_item" ? surface.parent : surface;
2006
+ return list?.context === "ulist" && list.block.getStyle?.() === "bibliography";
2007
+ }
1979
2008
  function scanHeadingInlineMetadataMatches(lineTable, line, headingInlineMetadataOccurrences, toolDiagnostics) {
1980
2009
  for (const match of line.text.matchAll(headingInlineMetadataPattern)) {
1981
2010
  if (match.index === void 0 || !match[1]) continue;
@@ -2516,18 +2545,14 @@ function projectSourceSurfaces(options) {
2516
2545
  if (policy === "diagnostic") toolDiagnostics.push(unknownContextDiagnostic(surface, interval));
2517
2546
  }
2518
2547
  const { sections, sectionByBlock } = buildSectionSurfaces(blockSurfaces, intervalByBlock, options.lineTable, toolDiagnostics, logicalSource, sourceAwareDocument);
2519
- const sectionByLine = logicalSource || sourceAwareDocument ? /* @__PURE__ */ new Map() : mapSectionScope(sections, options.lineTable.lines.length);
2548
+ const sectionByLine = mapSectionScope(blockSurfaces, sectionByBlock, intervalByBlock);
2520
2549
  const { xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences } = scanInlineOccurrencesInOfficialBlocks({
2521
2550
  lineTable: options.lineTable,
2522
2551
  blockSurfaces,
2523
2552
  intervalByBlock,
2553
+ sectionByLine,
2524
2554
  toolDiagnostics
2525
2555
  });
2526
- let sectionScopeIndex;
2527
- if (logicalSource || sourceAwareDocument) {
2528
- sectionScopeIndex = buildSourceScopeIndex(sections);
2529
- assignContainingSectionIdsFromSourceScope(xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, sectionScopeIndex);
2530
- } else assignContainingSectionIds(xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, sectionByLine);
2531
2556
  return {
2532
2557
  blockSurfaces,
2533
2558
  intervalByBlock,
@@ -2538,7 +2563,6 @@ function projectSourceSurfaces(options) {
2538
2563
  anchorOccurrences,
2539
2564
  headingInlineMetadataOccurrences,
2540
2565
  sectionByLine,
2541
- ...sectionScopeIndex ? { sectionScopeIndex } : {},
2542
2566
  toolDiagnostics
2543
2567
  };
2544
2568
  }
@@ -2611,10 +2635,6 @@ function buildSectionSurfaces(blockSurfaces, intervalByBlock, lineTable, toolDia
2611
2635
  }),
2612
2636
  children: []
2613
2637
  });
2614
- if (logicalSource || sourceAwareDocument) {
2615
- const sourceScope = sourceAwareDocument ? sourceScopeFromSourceAwareInterval(sourceAwareDocument, interval.span, sourceLine) : logicalSource ? sectionSourceScopeFromLogicalInterval(logicalSource, interval.span, sourceLine) : void 0;
2616
- if (sourceScope) registerSectionSourceScope(section, sourceScope);
2617
- }
2618
2638
  sections.push(section);
2619
2639
  sectionByBlock.set(surface.block, section);
2620
2640
  }
@@ -2658,21 +2678,6 @@ function nextOriginHeadingBoundary(index, current) {
2658
2678
  if (!index || !current) return;
2659
2679
  return index.get(current.relativePath)?.find((candidate) => candidate.sliceStartLine > current.sliceStartLine);
2660
2680
  }
2661
- function sectionSourceScopeFromLogicalInterval(logicalSource, span, headingLogicalLine) {
2662
- const headingOrigin = logicalSource.lineOrigins[headingLogicalLine - 1];
2663
- if (!headingOrigin) return;
2664
- let endLine = headingOrigin.sourceLine;
2665
- for (let logicalLine = span.startLine; logicalLine <= span.endLine; logicalLine += 1) {
2666
- const origin = logicalSource.lineOrigins[logicalLine - 1];
2667
- if (!origin || origin.relativePath !== headingOrigin.relativePath) continue;
2668
- endLine = Math.max(endLine, origin.sourceLine);
2669
- }
2670
- return {
2671
- relativePath: headingOrigin.relativePath,
2672
- startLine: headingOrigin.sourceLine,
2673
- endLine
2674
- };
2675
- }
2676
2681
  function sourceAwareOriginForLine(document, logicalLine) {
2677
2682
  const record = document.lines[logicalLine - 1];
2678
2683
  if (!record || !("origin" in record)) return;
@@ -2686,12 +2691,13 @@ function hasDiagnosticPolicyAncestor(surface) {
2686
2691
  }
2687
2692
  return false;
2688
2693
  }
2689
- function mapSectionScope(sections, totalLines) {
2694
+ function mapSectionScope(blockSurfaces, sectionByBlock, intervalByBlock) {
2690
2695
  const result = /* @__PURE__ */ new Map();
2691
- for (const section of sections) {
2692
- const start = section.span?.startLine ?? section.line ?? 1;
2693
- const end = section.span?.endLine ?? totalLines;
2694
- for (let line = start; line <= end; line += 1) result.set(line, section);
2696
+ for (const surface of blockSurfaces) {
2697
+ const section = sectionByBlock.get(surface.block);
2698
+ const span = intervalByBlock.get(surface.block)?.span;
2699
+ if (!section || !span) continue;
2700
+ for (let line = span.startLine; line <= span.endLine; line += 1) result.set(line, section);
2695
2701
  }
2696
2702
  return result;
2697
2703
  }
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as parseAbundantTree, i as serializeAbundantTreeToJson, n as formatAbundantTree, t as rdf12 } from "./src-DMOLXZbF.mjs";
2
+ import { a as parseAbundantTree, i as serializeAbundantTreeToJson, n as formatAbundantTree, t as rdf12 } from "./src-DmT-MEPN.mjs";
3
3
  import { relative, resolve, sep } from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
  import process from "node:process";
package/dist/index.d.mts CHANGED
@@ -156,7 +156,7 @@ type XrefOccurrenceNode = NodeBase & {
156
156
  };
157
157
  type AnchorOccurrenceNode = NodeBase & {
158
158
  kind: "anchor";
159
- syntax: "double-bracket" | "macro";
159
+ syntax: "double-bracket" | "macro" | "bibliography";
160
160
  raw: string;
161
161
  ids: string[];
162
162
  reftext?: string;
@@ -181,6 +181,7 @@ type TargetNode = NodeBase & {
181
181
  title?: string;
182
182
  idOrigin: "source" | "asciidoctor-generated" | "unknown";
183
183
  sourceSpan?: SourceSpan;
184
+ containingSectionId?: string;
184
185
  };
185
186
  type TextNode = NodeBase & {
186
187
  kind: "text";
@@ -221,7 +222,7 @@ type IncludeResolver = {
221
222
  };
222
223
  type IncludeResolverRequest = {
223
224
  readonly target: string;
224
- readonly attributes: Readonly<Record<string, string>>;
225
+ readonly attributes: Readonly<Record<string, string | null>>;
225
226
  readonly sourcePath: string;
226
227
  };
227
228
  type IncludeResolution = {
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as parseAbundantTree, i as serializeAbundantTreeToJson, n as formatAbundantTree, o as exportAnimationYaml, r as serializeAbundantTree, t as rdf12 } from "./src-DMOLXZbF.mjs";
1
+ import { a as parseAbundantTree, i as serializeAbundantTreeToJson, n as formatAbundantTree, o as exportAnimationYaml, r as serializeAbundantTree, t as rdf12 } from "./src-DmT-MEPN.mjs";
2
2
  export { exportAnimationYaml, formatAbundantTree, parseAbundantTree, rdf12, serializeAbundantTree, serializeAbundantTreeToJson };
@@ -284,6 +284,18 @@ function addFieldTriple(graph, subject, fieldName, value) {
284
284
  }
285
285
  //#endregion
286
286
  //#region src/rdf12-projection/heading-ownership.ts
287
+ /**
288
+ * 出现处或目标所属的 section 对应的标题节点。
289
+ *
290
+ * 所属 section 已在逻辑文档上判定(10 号书「结构关系在 Logical Document 上判定」),标题可以和出现处不在
291
+ * 同一个源文件里:被引入的文件没有自己的标题时,它属于引入它的那个标题。ID 在一份文档里唯一,
292
+ * 仍多于一个候选时不选。
293
+ */
294
+ function findHeadingBySectionId(nodeIndex, sectionId) {
295
+ if (sectionId === void 0) return;
296
+ const candidates = nodeIndex.entries().filter((entry) => entry.kind === "section" && entry.node.ids.includes(sectionId));
297
+ return candidates.length === 1 ? candidates[0] : void 0;
298
+ }
287
299
  function findInnermostHeadingBySourceLine(input) {
288
300
  return input.nodeIndex.entries().filter((entry) => containsLine(entry, input)).map((entry, index) => ({
289
301
  entry,
@@ -377,14 +389,8 @@ function addHeadingLabels$1(catalog, entry) {
377
389
  for (const id of entry.node.ids) addCatalogEntry(catalog, entry, labelKind, id);
378
390
  }
379
391
  function addLocalTargetAlias(catalog, input, target) {
380
- if (target.targetType === "section" || !target.id || target.sourceSpan === void 0) return;
381
- const relativePath = sourceRelativePathOrFallback(target.source, input.relativePath, input.document.mode);
382
- if (relativePath === void 0) return;
383
- const owner = findInnermostHeadingBySourceLine({
384
- nodeIndex: input.nodeIndex,
385
- relativePath,
386
- line: target.sourceSpan.start.line
387
- });
392
+ if (target.targetType === "section" || !target.id || target.sourceSpan === void 0 && target.containingSectionId === void 0) return;
393
+ const owner = findHeadingBySectionId(input.nodeIndex, target.containingSectionId) ?? ownerBySourceLine(input, target);
388
394
  if (owner === void 0) return;
389
395
  addCatalogEntry(catalog, owner, "addressLabel", target.id);
390
396
  input.graph.add(rdf12Triple(owner.iri, iriTerm(`${namespaces.aat}addressLabel`), stringLiteral(target.id)));
@@ -397,6 +403,15 @@ function addCatalogEntry(catalog, entry, labelKind, value) {
397
403
  value
398
404
  });
399
405
  }
406
+ function ownerBySourceLine(input, target) {
407
+ const relativePath = sourceRelativePathOrFallback(target.source, input.relativePath, input.document.mode);
408
+ if (relativePath === void 0 || target.sourceSpan === void 0) return;
409
+ return findInnermostHeadingBySourceLine({
410
+ nodeIndex: input.nodeIndex,
411
+ relativePath,
412
+ line: target.sourceSpan.start.line
413
+ });
414
+ }
400
415
  //#endregion
401
416
  //#region src/rdf12-projection/path-coordinate.ts
402
417
  function computePathCoordinate(input) {
@@ -755,7 +770,8 @@ function recoverSourceAwareSourceLayer(document, logicalSpan, options) {
755
770
  const lastRecord = sourceRecords.at(-1);
756
771
  if (!firstRecord || !lastRecord) return recoveryFailure$1("source-coordinate.generated-line", `Parser core interval ${logicalSpan.startLine}-${logicalSpan.endLine} has no source-backed lines for ${options.diagnosticContext}.`, options.logicalSourceSpan);
757
772
  const relativePath = firstRecord.origin.relativePath;
758
- if (!relativePath || sourceRecords.some((record) => record.origin.relativePath !== relativePath)) return recoveryFailure$1("block-origin.multi-source-span", `Block span ${logicalSpan.startLine}-${logicalSpan.endLine} maps to more than one origin source file for ${options.diagnosticContext}.`, options.logicalSourceSpan);
773
+ if (firstRecord.origin.absolutePath !== lastRecord.origin.absolutePath) return recoveryFailure$1("block-origin.multi-source-span", `${options.diagnosticContext} starts at ${originLabel(firstRecord.origin)} and ends at ${originLabel(lastRecord.origin)}; no single origin source file holds its raw.`, options.logicalSourceSpan);
774
+ if (!authorLinesInSourceOrder(sourceRecords, firstRecord.origin)) return recoveryFailure$1("block-origin.multi-source-span", `${options.diagnosticContext} from ${originLabel(firstRecord.origin)} to ${originLabel(lastRecord.origin)} reads ${relativePath} out of source order; no contiguous raw exists.`, options.logicalSourceSpan);
759
775
  const sourceFile = sourceFileForOrigin$1(document, firstRecord.origin);
760
776
  if (!sourceFile) return recoveryFailure$1("line-origin.source-file-missing", `Origin file line table is missing for ${relativePath}.`, options.logicalSourceSpan);
761
777
  const lineSpan = {
@@ -807,6 +823,47 @@ function recoverSourceAwarePointSourceLayer(document, logicalSourceSpan, raw) {
807
823
  }
808
824
  };
809
825
  }
826
+ /**
827
+ * 块的 content span 在作者文件里的行区间。
828
+ *
829
+ * 内容的首行或尾行来自被引入的文件时,作者文件里与之对应的是那条 include 指令:从块里紧挨在内容之前的
830
+ * 作者行往后一行起,到紧挨在内容之后的作者行往前一行止。作者文件由块的首行决定,调用方应已用
831
+ * {@link recoverSourceAwareSourceLayer} 确认整块落在同一个作者文件里。
832
+ */
833
+ function recoverSourceAwareContentLineSpan(document, blockSpan, contentSpan) {
834
+ const blockRecords = recordsForSpan(document, blockSpan)?.filter(hasOrigin);
835
+ const author = blockRecords?.[0]?.origin.absolutePath;
836
+ if (!blockRecords || author === void 0) return;
837
+ const authored = blockRecords.filter((record) => record.origin.absolutePath === author);
838
+ const inside = (record) => record.logicalLine >= contentSpan.startLine && record.logicalLine <= contentSpan.endLine;
839
+ const firstInside = authored.find(inside);
840
+ const lastInside = authored.findLast(inside);
841
+ const before = authored.findLast((record) => record.logicalLine < contentSpan.startLine);
842
+ const after = authored.find((record) => record.logicalLine > contentSpan.endLine);
843
+ const startsWithAuthorLine = firstInside !== void 0 && firstInside.logicalLine === firstOriginLine(blockRecords, contentSpan);
844
+ const endsWithAuthorLine = lastInside !== void 0 && lastInside.logicalLine === lastOriginLine(blockRecords, contentSpan);
845
+ const startLine = startsWithAuthorLine ? firstInside.origin.sourceLine : before && before.origin.sourceLine + 1;
846
+ const endLine = endsWithAuthorLine ? lastInside.origin.sourceLine : after && after.origin.sourceLine - 1;
847
+ if (startLine === void 0 || endLine === void 0 || startLine > endLine) return;
848
+ return {
849
+ startLine,
850
+ endLine
851
+ };
852
+ }
853
+ /**
854
+ * 一段逻辑行在源文件里落在哪些行上:`relativePath` 与 `line` 的组合,用 {@link originPointKey} 编成键。
855
+ *
856
+ * 出现处回源后只剩来源坐标;判断它在不在一个块里,就看它的来源是不是这个块某条逻辑行的来源
857
+ * (10 号书「结构关系在 Logical Document 上判定」)。被引入文件里的行也在其中。
858
+ */
859
+ function originPointsForSpan(document, logicalSpan) {
860
+ const points = /* @__PURE__ */ new Set();
861
+ for (const record of recordsForSpan(document, logicalSpan) ?? []) if (hasOrigin(record)) points.add(originPointKey(record.origin.relativePath, record.origin.sourceLine));
862
+ return points;
863
+ }
864
+ function originPointKey(relativePath, line) {
865
+ return `${relativePath}\n${line}`;
866
+ }
810
867
  function recoverSourceAwareDocumentTitleSourceLayer(document, logicalTitleSpan) {
811
868
  if (!logicalTitleSpan) return;
812
869
  return recoverSourceAwarePointSourceLayer(document, logicalTitleSpan, document.lines[logicalTitleSpan.start.line - 1]?.text ?? "");
@@ -844,22 +901,6 @@ function recoverSourceAwareTitleSpan(document, logicalTitleSpan) {
844
901
  const recovered = recoverSourceAwareSpan(document, logicalTitleSpan, false);
845
902
  return recovered.ok ? recovered.sourceSpan : void 0;
846
903
  }
847
- function sourceScopeFromSourceAwareInterval(document, span, headingLogicalLine) {
848
- const headingRecord = document.lines[headingLogicalLine - 1];
849
- if (!headingRecord || !hasOrigin(headingRecord)) return;
850
- let endLine = headingRecord.origin.sourceLine;
851
- for (let logicalLine = span.startLine; logicalLine <= span.endLine; logicalLine += 1) {
852
- const record = document.lines[logicalLine - 1];
853
- if (!record || !hasOrigin(record)) continue;
854
- if (record.origin.relativePath !== headingRecord.origin.relativePath) continue;
855
- endLine = Math.max(endLine, record.origin.sourceLine);
856
- }
857
- return {
858
- relativePath: headingRecord.origin.relativePath,
859
- startLine: headingRecord.origin.sourceLine,
860
- endLine
861
- };
862
- }
863
904
  function recoverSourceAwareSpan(document, logicalSourceSpan, requireColumns) {
864
905
  const start = recoverSourceAwarePoint(document, logicalSourceSpan.start, requireColumns);
865
906
  if (!start.ok) return start;
@@ -871,7 +912,7 @@ function recoverSourceAwareSpan(document, logicalSourceSpan, requireColumns) {
871
912
  line: start.line
872
913
  }
873
914
  };
874
- if (start.relativePath !== end.relativePath) return recoveryFailure$1("block-origin.multi-source-span", `Source span ${logicalSourceSpan.start.line}-${logicalSourceSpan.end.line} maps to more than one origin source file.`, logicalSourceSpan);
915
+ if (start.relativePath !== end.relativePath) return recoveryFailure$1("block-origin.multi-source-span", `Source span starts at ${start.relativePath}:${start.line} and ends at ${end.relativePath}:${end.line}; no single origin source file holds it.`, logicalSourceSpan);
875
916
  return {
876
917
  ok: true,
877
918
  relativePath: start.relativePath,
@@ -941,6 +982,24 @@ function recordsForSpan(document, span) {
941
982
  function hasOrigin(record) {
942
983
  return "origin" in record;
943
984
  }
985
+ function originLabel(origin) {
986
+ return `${origin.relativePath}:${origin.sourceLine}`;
987
+ }
988
+ function authorLinesInSourceOrder(records, author) {
989
+ let previous = 0;
990
+ for (const record of records) {
991
+ if (record.origin.absolutePath !== author.absolutePath) continue;
992
+ if (record.origin.sourceLine <= previous) return false;
993
+ previous = record.origin.sourceLine;
994
+ }
995
+ return true;
996
+ }
997
+ function firstOriginLine(records, span) {
998
+ return records.find((record) => record.logicalLine >= span.startLine && record.logicalLine <= span.endLine)?.logicalLine;
999
+ }
1000
+ function lastOriginLine(records, span) {
1001
+ return records.findLast((record) => record.logicalLine >= span.startLine && record.logicalLine <= span.endLine)?.logicalLine;
1002
+ }
944
1003
  function sourceFileForOrigin$1(document, origin) {
945
1004
  return document.sourceFiles.find((sourceFile) => sourceFile.absolutePath === origin.absolutePath);
946
1005
  }
@@ -1623,10 +1682,10 @@ function writeSourceBinding(context, xrefIri, xref) {
1623
1682
  }
1624
1683
  function sourceHeadingForXref(context, xref) {
1625
1684
  if (xref.sourceSpan === void 0) return;
1685
+ const selectorHeading = findHeadingBySectionId(context.nodeIndex, xref.containingSectionId)?.iri;
1686
+ if (selectorHeading !== void 0) return selectorHeading;
1626
1687
  const relativePath = sourceRelativePathOrFallback(xref.source, context.relativePath, context.document.mode);
1627
1688
  if (relativePath === void 0) return;
1628
- const selectorHeading = sourceHeadingBySelector(context.nodeIndex, relativePath, xref.containingSectionId);
1629
- if (selectorHeading !== void 0) return selectorHeading;
1630
1689
  const line = xref.sourceSpan.start.line;
1631
1690
  return findInnermostHeadingBySourceLine({
1632
1691
  nodeIndex: context.nodeIndex,
@@ -1634,13 +1693,6 @@ function sourceHeadingForXref(context, xref) {
1634
1693
  line
1635
1694
  })?.iri;
1636
1695
  }
1637
- function sourceHeadingBySelector(nodeIndex, relativePath, selector) {
1638
- if (selector === void 0) return;
1639
- const candidates = nodeIndex.entries().filter((entry) => {
1640
- return entry.relativePath === relativePath && entry.kind === "section" && entry.node.ids.includes(selector);
1641
- });
1642
- return candidates.length === 1 ? candidates[0]?.iri : void 0;
1643
- }
1644
1696
  function writeTargetBinding(context, xrefIri, xref) {
1645
1697
  const result = bindSelector(context.labelCatalog, xref.target);
1646
1698
  if (result.status === "bound") {
@@ -3531,6 +3583,7 @@ function addAnchorTargets(targets, anchorOccurrences) {
3531
3583
  title: anchor.reftext,
3532
3584
  idOrigin: "source",
3533
3585
  sourceSpan: anchor.sourceSpan,
3586
+ containingSectionId: anchor.containingSectionId,
3534
3587
  asciidoctor: definedObject({
3535
3588
  resolvedId: id,
3536
3589
  resolvedType: targetType,
@@ -4095,7 +4148,7 @@ function buildDescriptionList(block, startLine, context, interval) {
4095
4148
  delimiter: descriptionListDelimiterMatch(context.lineTable, startLine)?.raw,
4096
4149
  metadata: metadata.map((surface) => surface.node),
4097
4150
  metadataSpan: interval?.metadataSpan,
4098
- contentSpan: source ? originLineSpan(context, contentSpan) : void 0,
4151
+ contentSpan: source ? originLineSpan(context, contentSpan, span) : void 0,
4099
4152
  span: source?.span,
4100
4153
  source,
4101
4154
  asciidoctor: definedObject({
@@ -4374,7 +4427,7 @@ function buildListing(block, delimiterLine, context, interval) {
4374
4427
  metadata: metadata.map((surface) => surface.node),
4375
4428
  content: block.getSource?.() ?? (contentSpan ? sourceLines(context.lineTable, contentSpan.startLine, contentSpan.endLine).join("\n") : void 0),
4376
4429
  metadataSpan: interval?.metadataSpan,
4377
- contentSpan: source ? originLineSpan(context, contentSpan) : void 0,
4430
+ contentSpan: source ? originLineSpan(context, contentSpan, span) : void 0,
4378
4431
  span: source?.span,
4379
4432
  source,
4380
4433
  asciidoctor: definedObject({
@@ -4478,10 +4531,15 @@ function registerOfficialBlockTarget(context, block, options) {
4478
4531
  title: options.title ?? block.getTitle?.(),
4479
4532
  idOrigin: options.idOrigin ?? (options.targetType === "section" ? "asciidoctor-generated" : "source"),
4480
4533
  sourceSpan: options.sourceSpan,
4534
+ containingSectionId: options.targetType === "section" ? void 0 : blockSectionId(context, block),
4481
4535
  source: options.source,
4482
4536
  asciidoctor
4483
4537
  }));
4484
4538
  }
4539
+ function blockSectionId(context, block) {
4540
+ const line = context.intervalByBlock.get(block)?.span.startLine;
4541
+ return line === void 0 ? void 0 : context.sectionByLine.get(line)?.ids[0];
4542
+ }
4485
4543
  function blockSourceLayer(context, interval, fallbackSpan, diagnosticContext) {
4486
4544
  if (!context.logicalSource && !context.sourceAwareDocument) {
4487
4545
  const sourceSpan = sourceSpanFromLineSpan(context.lineTable, fallbackSpan);
@@ -4511,15 +4569,9 @@ function blockSourceLayer(context, interval, fallbackSpan, diagnosticContext) {
4511
4569
  function sameLineSpan(left, right) {
4512
4570
  return left.startLine === right.startLine && left.endLine === right.endLine;
4513
4571
  }
4514
- function originLineSpan(context, span) {
4572
+ function originLineSpan(context, span, blockSpan) {
4515
4573
  if (!span || !context.logicalSource && !context.sourceAwareDocument) return span;
4516
- if (context.sourceAwareDocument) {
4517
- const recovered = recoverSourceAwareSourceLayer(context.sourceAwareDocument, span, {
4518
- raw: false,
4519
- diagnosticContext: "block content"
4520
- });
4521
- return recovered.ok ? recovered.lineSpan : void 0;
4522
- }
4574
+ if (context.sourceAwareDocument) return recoverSourceAwareContentLineSpan(context.sourceAwareDocument, blockSpan, span);
4523
4575
  if (!context.logicalSource) return;
4524
4576
  const recovered = recoverOriginSourceLayer(context.logicalSource, span, {
4525
4577
  raw: false,
@@ -4538,6 +4590,13 @@ function collectHeadingInlineMetadataInSpan(context, logicalSpan, source) {
4538
4590
  }
4539
4591
  function collectOccurrencesInSpan(context, groupedByLine, occurrences, logicalSpan, source) {
4540
4592
  if (!context.logicalSource && !context.sourceAwareDocument) return collectOccurrencesInLineRange(groupedByLine, logicalSpan.startLine, logicalSpan.endLine);
4593
+ if (context.sourceAwareDocument) {
4594
+ const points = originPointsForSpan(context.sourceAwareDocument, logicalSpan);
4595
+ return occurrences.filter((occurrence) => {
4596
+ const { relativePath, line } = occurrence.source ?? {};
4597
+ return relativePath !== void 0 && line !== void 0 && points.has(originPointKey(relativePath, line));
4598
+ });
4599
+ }
4541
4600
  if (!source?.relativePath || !source.span) return [];
4542
4601
  const relativePath = source.relativePath;
4543
4602
  const sourceSpan = source.span;
@@ -4605,61 +4664,12 @@ function locate(block, location) {
4605
4664
  if (location) block.sourceLocation = location;
4606
4665
  }
4607
4666
  //#endregion
4608
- //#region src/book-entry/source-scope-index.ts
4609
- const sectionSourceScopeBySection = /* @__PURE__ */ new WeakMap();
4610
- function registerSectionSourceScope(section, scope) {
4611
- sectionSourceScopeBySection.set(section, scope);
4612
- }
4613
- function buildSourceScopeIndex(sections) {
4614
- return { candidates: sections.flatMap((section) => {
4615
- const scope = sourceScopeForSection(section);
4616
- if (!scope) return [];
4617
- return [{
4618
- ...scope,
4619
- section
4620
- }];
4621
- }) };
4622
- }
4623
- function sourceScopeForSection(section) {
4624
- const registered = sectionSourceScopeBySection.get(section);
4625
- if (registered) return registered;
4626
- const relativePath = section.source?.relativePath;
4627
- const startLine = section.source?.span?.startLine ?? section.source?.line;
4628
- const endLine = section.source?.span?.endLine ?? section.source?.line ?? startLine;
4629
- if (!relativePath || startLine === void 0 || endLine === void 0) return;
4630
- return {
4631
- relativePath,
4632
- startLine,
4633
- endLine
4634
- };
4635
- }
4636
- function lookupContainingSection(index, lookup) {
4637
- const relativePath = lookup.relativePath ?? lookup.source?.relativePath;
4638
- const line = lookup.line ?? lookup.source?.line;
4639
- if (!relativePath || line === void 0) return;
4640
- let containingSection;
4641
- for (const candidate of index.candidates) {
4642
- if (candidate.relativePath !== relativePath) continue;
4643
- if (line < candidate.startLine || line > candidate.endLine) continue;
4644
- containingSection = candidate.section;
4645
- }
4646
- return containingSection;
4647
- }
4648
- function assignContainingSectionIdsFromSourceScope(xrefs, anchors, headingInlineMetadataOccurrences, index) {
4649
- for (const occurrence of [
4650
- ...xrefs,
4651
- ...anchors,
4652
- ...headingInlineMetadataOccurrences
4653
- ]) {
4654
- const sectionId = lookupContainingSection(index, occurrence)?.ids[0];
4655
- if (sectionId !== void 0) occurrence.containingSectionId = sectionId;
4656
- }
4657
- }
4658
- //#endregion
4659
4667
  //#region src/inline-occurrence-scanner.ts
4660
4668
  const shorthandXrefPattern = /<<([^>,]+)(?:,\s*([^>]+))?>>/gu;
4661
4669
  const macroXrefPattern = /xref:([^\s[]+)\[([^\]]*)\]/gu;
4662
- const anchorPattern = /\[\[([^,\]]+)(?:,([^\]]+))?\]\]|anchor:([^\s[]+)\[([^\]]*)\]/gu;
4670
+ const anchorId = String.raw`[\p{Alphabetic}_:][\p{Alphabetic}\p{N}\p{Pc}\-:.]*`;
4671
+ const anchorPattern = new RegExp(String.raw`(?<![\\\[])\[\[(${anchorId})(?:, *(.+?))? ?\]\]` + String.raw`|(?<!\\)anchor:(${anchorId})\[(?:\]|(.*?[^\\])\])`, "gu");
4672
+ const bibliographyAnchorPattern = new RegExp(String.raw`^(\s*[*-]+\s+)\[\[\[(${anchorId})(?:, *(.+?))?\]\]\]`, "u");
4663
4673
  const headingInlineMetadataPattern = /hmeta:([^\s[]+)\[([^\]]*)\]/gu;
4664
4674
  function scanInlineOccurrencesInOfficialBlocks(options) {
4665
4675
  const xrefOccurrences = [];
@@ -4677,8 +4687,9 @@ function scanInlineOccurrencesInOfficialBlocks(options) {
4677
4687
  if (officialBlockPolicy(surface.block) !== "scan") continue;
4678
4688
  if (!scansMacroSubstitutedContent(surface)) continue;
4679
4689
  if (surface.context === "table") scanTableInlineRange(options.lineTable, surface.block, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics);
4680
- else scanInlineRange(options.lineTable, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics, descendantUnscannableRanges(surface, options.intervalByBlock));
4690
+ else scanInlineRange(options.lineTable, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics, descendantUnscannableRanges(surface, options.intervalByBlock), isBibliographyList(surface));
4681
4691
  }
4692
+ assignContainingSectionIds(xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.sectionByLine);
4682
4693
  const sourceAwareDocument = sourceAwareDocumentForLineTable(options.lineTable);
4683
4694
  const logicalSource = logicalSourceForLineTable(options.lineTable);
4684
4695
  const recoveredXrefs = sourceAwareDocument ? recoverSourceAwareInlineOrigins(xrefOccurrences, sourceAwareDocument, options.toolDiagnostics) : logicalSource ? recoverInlineOrigins(xrefOccurrences, logicalSource, options.toolDiagnostics) : xrefOccurrences;
@@ -4795,12 +4806,13 @@ function parseMacroLabel(raw) {
4795
4806
  function compareSourceSpans(left, right) {
4796
4807
  return (left.sourceSpan?.start.line ?? 0) - (right.sourceSpan?.start.line ?? 0) || (left.sourceSpan?.start.column ?? 0) - (right.sourceSpan?.start.column ?? 0);
4797
4808
  }
4798
- function scanInlineRange(lineTable, startLine, endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, toolDiagnostics, skipRanges = []) {
4809
+ function scanInlineRange(lineTable, startLine, endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, toolDiagnostics, skipRanges = [], bibliography = false) {
4799
4810
  for (let lineNumber = startLine; lineNumber <= endLine; lineNumber += 1) {
4800
4811
  if (isLineInRanges(lineNumber, skipRanges)) continue;
4801
4812
  const line = lineTable.lines[lineNumber - 1];
4802
4813
  if (!line) continue;
4803
4814
  scanXrefMatches(lineTable, line, xrefOccurrences);
4815
+ if (bibliography) scanBibliographyAnchor(lineTable, line, anchorOccurrences);
4804
4816
  scanAnchorMatches(lineTable, line, anchorOccurrences);
4805
4817
  scanHeadingInlineMetadataMatches(lineTable, line, headingInlineMetadataOccurrences, toolDiagnostics);
4806
4818
  }
@@ -4959,7 +4971,7 @@ function scanAnchorMatches(lineTable, line, anchorOccurrences) {
4959
4971
  syntax: doubleBracketId ? "double-bracket" : "macro",
4960
4972
  raw,
4961
4973
  ids: [id],
4962
- reftext: match[2] ?? match[4] ?? void 0,
4974
+ reftext: doubleBracketId ? match[2] : match[4] ?? "",
4963
4975
  anchorScope: line.text.trim() === raw && doubleBracketId ? "block" : "inline",
4964
4976
  sourceSpan,
4965
4977
  source: {
@@ -4970,6 +4982,31 @@ function scanAnchorMatches(lineTable, line, anchorOccurrences) {
4970
4982
  }));
4971
4983
  }
4972
4984
  }
4985
+ function scanBibliographyAnchor(lineTable, line, anchorOccurrences) {
4986
+ const match = bibliographyAnchorPattern.exec(line.text);
4987
+ const [, marker, id, reftext] = match ?? [];
4988
+ if (marker === void 0 || id === void 0) return;
4989
+ const raw = match?.[0].slice(marker.length) ?? "";
4990
+ const sourceSpan = sourceSpanForRange(lineTable, line.number, marker.length, raw);
4991
+ anchorOccurrences.push(definedObject({
4992
+ kind: "anchor",
4993
+ syntax: "bibliography",
4994
+ raw,
4995
+ ids: [id],
4996
+ reftext,
4997
+ anchorScope: "inline",
4998
+ sourceSpan,
4999
+ source: {
5000
+ raw,
5001
+ line: line.number,
5002
+ sourceSpan
5003
+ }
5004
+ }));
5005
+ }
5006
+ function isBibliographyList(surface) {
5007
+ const list = surface.context === "list_item" ? surface.parent : surface;
5008
+ return list?.context === "ulist" && list.block.getStyle?.() === "bibliography";
5009
+ }
4973
5010
  function scanHeadingInlineMetadataMatches(lineTable, line, headingInlineMetadataOccurrences, toolDiagnostics) {
4974
5011
  for (const match of line.text.matchAll(headingInlineMetadataPattern)) {
4975
5012
  if (match.index === void 0 || !match[1]) continue;
@@ -5510,18 +5547,14 @@ function projectSourceSurfaces(options) {
5510
5547
  if (policy === "diagnostic") toolDiagnostics.push(unknownContextDiagnostic(surface, interval));
5511
5548
  }
5512
5549
  const { sections, sectionByBlock } = buildSectionSurfaces(blockSurfaces, intervalByBlock, options.lineTable, toolDiagnostics, logicalSource, sourceAwareDocument);
5513
- const sectionByLine = logicalSource || sourceAwareDocument ? /* @__PURE__ */ new Map() : mapSectionScope(sections, options.lineTable.lines.length);
5550
+ const sectionByLine = mapSectionScope(blockSurfaces, sectionByBlock, intervalByBlock);
5514
5551
  const { xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences } = scanInlineOccurrencesInOfficialBlocks({
5515
5552
  lineTable: options.lineTable,
5516
5553
  blockSurfaces,
5517
5554
  intervalByBlock,
5555
+ sectionByLine,
5518
5556
  toolDiagnostics
5519
5557
  });
5520
- let sectionScopeIndex;
5521
- if (logicalSource || sourceAwareDocument) {
5522
- sectionScopeIndex = buildSourceScopeIndex(sections);
5523
- assignContainingSectionIdsFromSourceScope(xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, sectionScopeIndex);
5524
- } else assignContainingSectionIds(xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, sectionByLine);
5525
5558
  return {
5526
5559
  blockSurfaces,
5527
5560
  intervalByBlock,
@@ -5532,7 +5565,6 @@ function projectSourceSurfaces(options) {
5532
5565
  anchorOccurrences,
5533
5566
  headingInlineMetadataOccurrences,
5534
5567
  sectionByLine,
5535
- ...sectionScopeIndex ? { sectionScopeIndex } : {},
5536
5568
  toolDiagnostics
5537
5569
  };
5538
5570
  }
@@ -5605,10 +5637,6 @@ function buildSectionSurfaces(blockSurfaces, intervalByBlock, lineTable, toolDia
5605
5637
  }),
5606
5638
  children: []
5607
5639
  });
5608
- if (logicalSource || sourceAwareDocument) {
5609
- const sourceScope = sourceAwareDocument ? sourceScopeFromSourceAwareInterval(sourceAwareDocument, interval.span, sourceLine) : logicalSource ? sectionSourceScopeFromLogicalInterval(logicalSource, interval.span, sourceLine) : void 0;
5610
- if (sourceScope) registerSectionSourceScope(section, sourceScope);
5611
- }
5612
5640
  sections.push(section);
5613
5641
  sectionByBlock.set(surface.block, section);
5614
5642
  }
@@ -5652,21 +5680,6 @@ function nextOriginHeadingBoundary(index, current) {
5652
5680
  if (!index || !current) return;
5653
5681
  return index.get(current.relativePath)?.find((candidate) => candidate.sliceStartLine > current.sliceStartLine);
5654
5682
  }
5655
- function sectionSourceScopeFromLogicalInterval(logicalSource, span, headingLogicalLine) {
5656
- const headingOrigin = logicalSource.lineOrigins[headingLogicalLine - 1];
5657
- if (!headingOrigin) return;
5658
- let endLine = headingOrigin.sourceLine;
5659
- for (let logicalLine = span.startLine; logicalLine <= span.endLine; logicalLine += 1) {
5660
- const origin = logicalSource.lineOrigins[logicalLine - 1];
5661
- if (!origin || origin.relativePath !== headingOrigin.relativePath) continue;
5662
- endLine = Math.max(endLine, origin.sourceLine);
5663
- }
5664
- return {
5665
- relativePath: headingOrigin.relativePath,
5666
- startLine: headingOrigin.sourceLine,
5667
- endLine
5668
- };
5669
- }
5670
5683
  function sourceAwareOriginForLine(document, logicalLine) {
5671
5684
  const record = document.lines[logicalLine - 1];
5672
5685
  if (!record || !("origin" in record)) return;
@@ -5680,12 +5693,13 @@ function hasDiagnosticPolicyAncestor(surface) {
5680
5693
  }
5681
5694
  return false;
5682
5695
  }
5683
- function mapSectionScope(sections, totalLines) {
5696
+ function mapSectionScope(blockSurfaces, sectionByBlock, intervalByBlock) {
5684
5697
  const result = /* @__PURE__ */ new Map();
5685
- for (const section of sections) {
5686
- const start = section.span?.startLine ?? section.line ?? 1;
5687
- const end = section.span?.endLine ?? totalLines;
5688
- for (let line = start; line <= end; line += 1) result.set(line, section);
5698
+ for (const surface of blockSurfaces) {
5699
+ const section = sectionByBlock.get(surface.block);
5700
+ const span = intervalByBlock.get(surface.block)?.span;
5701
+ if (!section || !span) continue;
5702
+ for (let line = span.startLine; line <= span.endLine; line += 1) result.set(line, section);
5689
5703
  }
5690
5704
  return result;
5691
5705
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asciidoc-abundant-tree",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Recover a source-aware, analysis-ready tree from AsciiDoc files and explicit book-entry include graphs.",
5
5
  "license": "Apache-2.0",
6
6
  "author": {