@ansonlai/docx-redline-js 0.5.4 → 0.6.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/AGENTS.md +82 -697
- package/ARCHITECTURE.md +13 -1
- package/CHANGELOG.md +8 -0
- package/README.md +177 -45
- package/core/paragraph-targeting.js +14 -2
- package/dist/docx-redline-js.esm.js +184 -51
- package/dist/docx-redline-js.esm.js.map +3 -3
- package/dist/docx-redline-js.esm.min.js +77 -77
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/AGENT_FAST_START.md +59 -0
- package/docs/AGENT_KNOWLEDGE_BASE.md +878 -0
- package/docs/SKILL_AUTHORING.md +126 -0
- package/docs/TESTING.md +35 -1
- package/docs/schemas/document-operations.schema.json +5 -1
- package/docs/validation-reports/2026-09-12-agent-cli-discovery-baseline.md +56 -0
- package/docs/validation-reports/2026-09-12-agent-protocol-rollout.md +86 -0
- package/docs/validation-reports/2026-09-13-agent-cli-efficiency-rollout.md +86 -0
- package/engine/oxml-engine.js +80 -13
- package/engine/run-builders.js +5 -15
- package/index.d.ts +28 -3
- package/node/cli-help.js +209 -0
- package/node/cli.js +323 -65
- package/node/docx-document.js +120 -69
- package/node/index.d.ts +6 -2
- package/package.json +15 -3
- package/scripts/generate-cross-author-slicing-fixtures.ps1 +25 -25
- package/services/batch-operation-orchestrator.js +215 -120
- package/services/document-inspection.js +89 -11
- package/services/document-operation-applier.js +52 -34
- package/services/document-operation-contract.js +10 -6
- package/services/document-operation-mutations.js +51 -5
- package/services/document-operation-session.js +4 -0
- package/services/error-recovery.js +174 -0
- package/services/operation-batch-compiler.js +394 -0
- package/services/operation-preflight.js +91 -72
- package/services/standalone-operation-runner.d.ts +17 -1
- package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +0 -1669
- package/docs/plans/2026-09-08-cross-author-revision-slicing.md +0 -1399
- package/docs/plans/completed/2026-03-01-release-0.1.4-design.md +0 -33
- package/docs/plans/completed/2026-03-01-release-0.1.4.md +0 -110
- package/docs/plans/completed/2026-05-31-architectural changes.md +0 -593
- package/docs/plans/completed/2026-08-02-reliability-improvements.md +0 -1155
- package/docs/plans/completed/2026-08-30-reliability-testing-improvements.md +0 -488
- package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +0 -669
- package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +0 -427
- package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +0 -519
- package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +0 -69
- package/docs/plans/completed/structural-revision-capability-matrix.md +0 -115
- package/docs/test-comparison-dashboard.html +0 -4338
- package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +0 -22
- package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +0 -24
- package/docs/validation-reports/2026-08-30-phase-3-coverage.md +0 -73
- package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +0 -82
- package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +0 -114
- package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +0 -79
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js v0.
|
|
1
|
+
// @ansonlai/docx-redline-js v0.6.1 — https://github.com/AnsonLai/docx-redline-js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -4890,6 +4890,47 @@ function extractFormattingFromOoxml(xmlDoc) {
|
|
|
4890
4890
|
return { existingFormatHints, textSpans, paragraphs };
|
|
4891
4891
|
}
|
|
4892
4892
|
|
|
4893
|
+
// core/revision-cloning.js
|
|
4894
|
+
var REVISION_HISTORY_ELEMENTS = /* @__PURE__ */ new Set([
|
|
4895
|
+
"ins",
|
|
4896
|
+
"del",
|
|
4897
|
+
"moveFrom",
|
|
4898
|
+
"moveTo",
|
|
4899
|
+
"pPrChange",
|
|
4900
|
+
"rPrChange",
|
|
4901
|
+
"tblPrChange",
|
|
4902
|
+
"trPrChange",
|
|
4903
|
+
"tcPrChange",
|
|
4904
|
+
"sectPrChange"
|
|
4905
|
+
]);
|
|
4906
|
+
function clonePropertiesWithoutRevisionHistory(root) {
|
|
4907
|
+
if (!root) return null;
|
|
4908
|
+
const clone = root.cloneNode(true);
|
|
4909
|
+
const candidates = [clone, ...Array.from(clone.getElementsByTagName?.("*") || [])];
|
|
4910
|
+
for (const node of candidates.reverse()) {
|
|
4911
|
+
if (!REVISION_HISTORY_ELEMENTS.has(String(node.localName || node.nodeName || "").replace(/^.*:/, ""))) continue;
|
|
4912
|
+
node.parentNode?.removeChild(node);
|
|
4913
|
+
}
|
|
4914
|
+
return clone;
|
|
4915
|
+
}
|
|
4916
|
+
function refreshRunPropertyChangeIds(root, allocator = null) {
|
|
4917
|
+
if (!root) return root;
|
|
4918
|
+
const xmlDoc = root.nodeType === 9 ? root : root.ownerDocument;
|
|
4919
|
+
const resolvedAllocator = allocator instanceof RevisionIdAllocator ? allocator : getRevisionIdAllocatorForDocument(xmlDoc) || createRevisionIdAllocator(xmlDoc);
|
|
4920
|
+
const candidates = [root, ...Array.from(root.getElementsByTagName?.("*") || [])];
|
|
4921
|
+
for (const node of candidates) {
|
|
4922
|
+
if (!isWordElement(node, "rPrChange")) continue;
|
|
4923
|
+
const nextId = String(resolvedAllocator.next());
|
|
4924
|
+
if (typeof node.setAttributeNS === "function") {
|
|
4925
|
+
node.setAttributeNS(NS_W, "w:id", nextId);
|
|
4926
|
+
} else {
|
|
4927
|
+
node.setAttribute("w:id", nextId);
|
|
4928
|
+
}
|
|
4929
|
+
resolvedAllocator._receiptCollector?.recordRevision(Number(nextId), "rPrChange");
|
|
4930
|
+
}
|
|
4931
|
+
return root;
|
|
4932
|
+
}
|
|
4933
|
+
|
|
4893
4934
|
// engine/run-builders.js
|
|
4894
4935
|
function createTrackChange(xmlDoc, type, run, author, revisionMetadata = null) {
|
|
4895
4936
|
const wrapper = createWordElement(xmlDoc, type === "ins" ? "w:ins" : "w:del");
|
|
@@ -5078,13 +5119,8 @@ function snapshotAndAttachRPrChange(xmlDoc, rPr, author, dateStr, sourceNode) {
|
|
|
5078
5119
|
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
5079
5120
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
5080
5121
|
rPrChange.setAttribute("w:date", dateStr || metadata.date);
|
|
5081
|
-
const previousRPr = createWordElement(xmlDoc, "w:rPr");
|
|
5082
5122
|
const source = sourceNode || rPr;
|
|
5083
|
-
|
|
5084
|
-
if (child.nodeName !== "w:rPrChange") {
|
|
5085
|
-
previousRPr.appendChild(child.cloneNode(true));
|
|
5086
|
-
}
|
|
5087
|
-
});
|
|
5123
|
+
const previousRPr = clonePropertiesWithoutRevisionHistory(source) || createWordElement(xmlDoc, "w:rPr");
|
|
5088
5124
|
rPrChange.appendChild(previousRPr);
|
|
5089
5125
|
const existing = getFirstElementByTag(rPr, "w:rPrChange");
|
|
5090
5126
|
if (existing) {
|
|
@@ -5894,25 +5930,6 @@ function describeInsertionBoundary(spanIndex, pos, fallbackParagraph = null) {
|
|
|
5894
5930
|
};
|
|
5895
5931
|
}
|
|
5896
5932
|
|
|
5897
|
-
// core/revision-cloning.js
|
|
5898
|
-
function refreshRunPropertyChangeIds(root, allocator = null) {
|
|
5899
|
-
if (!root) return root;
|
|
5900
|
-
const xmlDoc = root.nodeType === 9 ? root : root.ownerDocument;
|
|
5901
|
-
const resolvedAllocator = allocator instanceof RevisionIdAllocator ? allocator : getRevisionIdAllocatorForDocument(xmlDoc) || createRevisionIdAllocator(xmlDoc);
|
|
5902
|
-
const candidates = [root, ...Array.from(root.getElementsByTagName?.("*") || [])];
|
|
5903
|
-
for (const node of candidates) {
|
|
5904
|
-
if (!isWordElement(node, "rPrChange")) continue;
|
|
5905
|
-
const nextId = String(resolvedAllocator.next());
|
|
5906
|
-
if (typeof node.setAttributeNS === "function") {
|
|
5907
|
-
node.setAttributeNS(NS_W, "w:id", nextId);
|
|
5908
|
-
} else {
|
|
5909
|
-
node.setAttribute("w:id", nextId);
|
|
5910
|
-
}
|
|
5911
|
-
resolvedAllocator._receiptCollector?.recordRevision(Number(nextId), "rPrChange");
|
|
5912
|
-
}
|
|
5913
|
-
return root;
|
|
5914
|
-
}
|
|
5915
|
-
|
|
5916
5933
|
// engine/surgical-run-splitting.js
|
|
5917
5934
|
var TRACK_CHANGE_CARRIERS = /* @__PURE__ */ new Set(["ins", "del"]);
|
|
5918
5935
|
function getRunContentPieces(runElement) {
|
|
@@ -8415,6 +8432,51 @@ function getCommentIdsInOoxml(node) {
|
|
|
8415
8432
|
}
|
|
8416
8433
|
return [...ids].sort((a, b) => Number(a) - Number(b) || a.localeCompare(b));
|
|
8417
8434
|
}
|
|
8435
|
+
function directWordChild(node, localName2) {
|
|
8436
|
+
return Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.namespaceURI === NS_W && child.localName === localName2) || null;
|
|
8437
|
+
}
|
|
8438
|
+
function insertedParagraphMarkMetadata(paragraph, author) {
|
|
8439
|
+
const marker = directWordChild(directWordChild(directWordChild(paragraph, "pPr"), "rPr"), "ins");
|
|
8440
|
+
if (!marker) return null;
|
|
8441
|
+
const markerAuthor = marker.getAttribute("w:author") || marker.getAttributeNS(NS_W, "author") || "";
|
|
8442
|
+
if (markerAuthor.trim().toLowerCase() !== String(author || "").trim().toLowerCase()) return null;
|
|
8443
|
+
return {
|
|
8444
|
+
id: marker.getAttribute("w:id") || marker.getAttributeNS(NS_W, "id") || "",
|
|
8445
|
+
author: markerAuthor,
|
|
8446
|
+
date: marker.getAttribute("w:date") || marker.getAttributeNS(NS_W, "date") || ""
|
|
8447
|
+
};
|
|
8448
|
+
}
|
|
8449
|
+
function emptyParagraphBaseline(paragraph, serializer) {
|
|
8450
|
+
const clone = paragraph.cloneNode(false);
|
|
8451
|
+
const pPr = directWordChild(paragraph, "pPr");
|
|
8452
|
+
if (pPr) clone.appendChild(clonePropertiesWithoutRevisionHistory(pPr));
|
|
8453
|
+
return serializer.serializeToString(clone);
|
|
8454
|
+
}
|
|
8455
|
+
function restoreInsertedParagraphMark(oxml, metadata) {
|
|
8456
|
+
if (!metadata || typeof oxml !== "string" || !oxml.trim()) return oxml;
|
|
8457
|
+
const parsed = parseOoxmlSafe(oxml, "text/xml");
|
|
8458
|
+
if (!parsed.doc || parsed.error) return oxml;
|
|
8459
|
+
const paragraph = parsed.doc.documentElement?.localName === "p" ? parsed.doc.documentElement : getDocumentParagraphs(parsed.doc)[0];
|
|
8460
|
+
if (!paragraph) return oxml;
|
|
8461
|
+
let pPr = directWordChild(paragraph, "pPr");
|
|
8462
|
+
if (!pPr) {
|
|
8463
|
+
pPr = createWordElement(parsed.doc, "w:pPr");
|
|
8464
|
+
paragraph.insertBefore(pPr, paragraph.firstChild);
|
|
8465
|
+
}
|
|
8466
|
+
let rPr = directWordChild(pPr, "rPr");
|
|
8467
|
+
if (!rPr) {
|
|
8468
|
+
rPr = createWordElement(parsed.doc, "w:rPr");
|
|
8469
|
+
pPr.appendChild(rPr);
|
|
8470
|
+
}
|
|
8471
|
+
if (!directWordChild(rPr, "ins")) {
|
|
8472
|
+
const marker = createWordElement(parsed.doc, "w:ins");
|
|
8473
|
+
if (metadata.id) marker.setAttribute("w:id", metadata.id);
|
|
8474
|
+
marker.setAttribute("w:author", metadata.author);
|
|
8475
|
+
if (metadata.date) marker.setAttribute("w:date", metadata.date);
|
|
8476
|
+
rPr.appendChild(marker);
|
|
8477
|
+
}
|
|
8478
|
+
return serializeXml(parsed.doc);
|
|
8479
|
+
}
|
|
8418
8480
|
async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}) {
|
|
8419
8481
|
const inputOoxml = oxml;
|
|
8420
8482
|
let workingOoxml = oxml;
|
|
@@ -8426,6 +8488,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
8426
8488
|
let parseWarnings = [];
|
|
8427
8489
|
const operationWarnings = [];
|
|
8428
8490
|
let normalizedExistingRevisions = false;
|
|
8491
|
+
let preservedInsertedParagraphMark = null;
|
|
8429
8492
|
const existingRevisionsPolicy = options.existingRevisions || "merge-same-author";
|
|
8430
8493
|
const keepNormalizedNoOp = existingRevisionsPolicy === "accept-all-first-keep-normalized";
|
|
8431
8494
|
const finalize = (result) => {
|
|
@@ -8456,6 +8519,9 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
8456
8519
|
if (!withStatus.status) {
|
|
8457
8520
|
withStatus.status = withStatus.hasChanges ? "ok" : "no-op";
|
|
8458
8521
|
}
|
|
8522
|
+
if (preservedInsertedParagraphMark && withStatus.hasChanges && typeof withStatus.oxml === "string") {
|
|
8523
|
+
withStatus.oxml = restoreInsertedParagraphMark(withStatus.oxml, preservedInsertedParagraphMark);
|
|
8524
|
+
}
|
|
8459
8525
|
return withOoxmlSourceType(withStatus);
|
|
8460
8526
|
};
|
|
8461
8527
|
const finalizeUnchanged = () => {
|
|
@@ -8521,9 +8587,11 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
8521
8587
|
});
|
|
8522
8588
|
}
|
|
8523
8589
|
log("[OxmlEngine] Existing revisions from same author detected; rejecting previous changes to merge against baseline");
|
|
8590
|
+
const soleParagraph = inputParagraphs.length === 1 ? inputParagraphs[0] : null;
|
|
8591
|
+
preservedInsertedParagraphMark = insertedParagraphMarkMetadata(soleParagraph, author);
|
|
8524
8592
|
const rejected = rejectTrackedChangesInOoxml(inputOoxml, { author });
|
|
8525
8593
|
if (rejected.status === "error") return finalize(rejected);
|
|
8526
|
-
workingOoxml = rejected.oxml;
|
|
8594
|
+
workingOoxml = preservedInsertedParagraphMark && !String(rejected.oxml || "").trim() ? emptyParagraphBaseline(soleParagraph, serializer) : rejected.oxml;
|
|
8527
8595
|
normalizedExistingRevisions = true;
|
|
8528
8596
|
const rejectedParsed = parseOoxmlSafe(workingOoxml, "text/xml");
|
|
8529
8597
|
parseWarnings.push(...rejectedParsed.warnings);
|
|
@@ -8565,7 +8633,9 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
8565
8633
|
status: "error",
|
|
8566
8634
|
error: {
|
|
8567
8635
|
code: "EXISTING_REVISIONS",
|
|
8568
|
-
message: `Input OOXML contains tracked changes from another author (${authors.length ? authors.join(", ") : "unattributed"}).
|
|
8636
|
+
message: `Input OOXML contains tracked changes from another author (${authors.length ? authors.join(", ") : "unattributed"}). Use existingRevisions: "slice-cross-author" for a surgical edit that preserves reviewer history; accepting or rejecting revisions requires separate authorization.`,
|
|
8637
|
+
revisionAuthors: authors,
|
|
8638
|
+
currentPolicy: existingRevisionsPolicy
|
|
8569
8639
|
}
|
|
8570
8640
|
});
|
|
8571
8641
|
} else {
|
|
@@ -9173,9 +9243,12 @@ function resolveTargetParagraph(xmlDoc, options = {}) {
|
|
|
9173
9243
|
);
|
|
9174
9244
|
}
|
|
9175
9245
|
if (descriptor.fingerprint && descriptor.fingerprint !== actualFingerprint) {
|
|
9246
|
+
const alternateView = revisionView === "rejected" ? "accepted" : "rejected";
|
|
9247
|
+
const alternateFingerprint = createParagraphFingerprint(byId, { revisionView: alternateView });
|
|
9248
|
+
const viewHint = descriptor.fingerprint === alternateFingerprint ? ` The supplied fingerprint matches the ${alternateView} view; set target.revisionView to "${alternateView}" or use a fingerprint extracted from the ${revisionView} view.` : "";
|
|
9176
9249
|
throw createTargetError(
|
|
9177
9250
|
"TARGET_FINGERPRINT_MISMATCH",
|
|
9178
|
-
`Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint
|
|
9251
|
+
`Target paragraphId "${descriptor.paragraphId}" no longer matches its source fingerprint.${viewHint}`,
|
|
9179
9252
|
cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
|
|
9180
9253
|
);
|
|
9181
9254
|
}
|
|
@@ -9187,9 +9260,14 @@ function resolveTargetParagraph(xmlDoc, options = {}) {
|
|
|
9187
9260
|
);
|
|
9188
9261
|
}
|
|
9189
9262
|
if (cleanTargetText && actualText !== normalizeWhitespaceForTargeting(cleanTargetText)) {
|
|
9263
|
+
const alternateView = revisionView === "rejected" ? "accepted" : "rejected";
|
|
9264
|
+
const alternateText = normalizeWhitespaceForTargeting(
|
|
9265
|
+
extractCanonicalParagraphText(byId, { revisionView: alternateView })
|
|
9266
|
+
);
|
|
9267
|
+
const viewHint = alternateText === normalizeWhitespaceForTargeting(cleanTargetText) ? ` The supplied text matches the ${alternateView} view; set target.revisionView to "${alternateView}".` : "";
|
|
9190
9268
|
throw createTargetError(
|
|
9191
9269
|
"TARGET_TEXT_MISMATCH",
|
|
9192
|
-
`Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text
|
|
9270
|
+
`Target paragraphId "${descriptor.paragraphId}" no longer matches the supplied text.${viewHint}`,
|
|
9193
9271
|
cachedEntry ? [serializeTargetCandidate(cachedEntry)] : null
|
|
9194
9272
|
);
|
|
9195
9273
|
}
|
|
@@ -11388,6 +11466,70 @@ function collectDocumentCommentAnchors(paragraphNodes, revisionView) {
|
|
|
11388
11466
|
});
|
|
11389
11467
|
return anchors;
|
|
11390
11468
|
}
|
|
11469
|
+
function selectInspectionParagraphs(allParagraphs, options) {
|
|
11470
|
+
let matches = allParagraphs;
|
|
11471
|
+
if (options.revisedOnly) matches = matches.filter((item) => item.hasRevisions);
|
|
11472
|
+
if (options.inTable != null) matches = matches.filter((item) => item.inTable === !!options.inTable);
|
|
11473
|
+
if (options.skipEmpty) matches = matches.filter((item) => item.text.length > 0);
|
|
11474
|
+
if (options.search) {
|
|
11475
|
+
const needle = String(options.search).toLowerCase();
|
|
11476
|
+
matches = matches.filter((item) => item.text.toLowerCase().includes(needle));
|
|
11477
|
+
}
|
|
11478
|
+
if (Array.isArray(options.indexes)) {
|
|
11479
|
+
const indexes = new Set(options.indexes);
|
|
11480
|
+
matches = matches.filter((item) => indexes.has(item.index));
|
|
11481
|
+
}
|
|
11482
|
+
let rangeStart = 1;
|
|
11483
|
+
let rangeEnd = allParagraphs.length;
|
|
11484
|
+
if (options.range) {
|
|
11485
|
+
rangeStart = Number(options.range.start ?? options.range[0]);
|
|
11486
|
+
rangeEnd = Number(options.range.end ?? options.range[1]);
|
|
11487
|
+
matches = matches.filter((item) => item.index >= rangeStart && item.index <= rangeEnd);
|
|
11488
|
+
}
|
|
11489
|
+
const totalMatches = matches.length;
|
|
11490
|
+
const after = Number.isInteger(options.after) && options.after > 0 ? options.after : null;
|
|
11491
|
+
const remaining = after == null ? matches : matches.filter((item) => item.index > after);
|
|
11492
|
+
const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : null;
|
|
11493
|
+
const selectedMatches = limit == null ? remaining : remaining.slice(0, limit);
|
|
11494
|
+
const truncated = selectedMatches.length < remaining.length;
|
|
11495
|
+
const around = Number.isInteger(options.around) && options.around > 0 ? options.around : 0;
|
|
11496
|
+
const exposeSelection = !!options.search || around > 0 || limit != null || after != null;
|
|
11497
|
+
let paragraphs = selectedMatches;
|
|
11498
|
+
if (around > 0 && options.search) {
|
|
11499
|
+
const directIndexes = new Set(selectedMatches.map((item) => item.index));
|
|
11500
|
+
const contextFor = /* @__PURE__ */ new Map();
|
|
11501
|
+
for (const match of selectedMatches) {
|
|
11502
|
+
const start = Math.max(rangeStart, match.index - around);
|
|
11503
|
+
const end = Math.min(rangeEnd, match.index + around);
|
|
11504
|
+
for (let index = start; index <= end; index++) {
|
|
11505
|
+
if (directIndexes.has(index)) continue;
|
|
11506
|
+
const owners = contextFor.get(index) || [];
|
|
11507
|
+
owners.push(match.index);
|
|
11508
|
+
contextFor.set(index, owners);
|
|
11509
|
+
}
|
|
11510
|
+
}
|
|
11511
|
+
const returnedIndexes = /* @__PURE__ */ new Set([...directIndexes, ...contextFor.keys()]);
|
|
11512
|
+
paragraphs = allParagraphs.filter((item) => returnedIndexes.has(item.index)).map((item) => directIndexes.has(item.index) ? { ...item, selectionRole: "match" } : { ...item, selectionRole: "context", contextFor: contextFor.get(item.index) || [] });
|
|
11513
|
+
} else if (exposeSelection) {
|
|
11514
|
+
paragraphs = selectedMatches.map((item) => ({ ...item, selectionRole: "match" }));
|
|
11515
|
+
}
|
|
11516
|
+
return {
|
|
11517
|
+
paragraphs,
|
|
11518
|
+
...exposeSelection ? {
|
|
11519
|
+
selection: {
|
|
11520
|
+
...options.search ? { search: String(options.search), caseSensitive: false } : {},
|
|
11521
|
+
totalMatches,
|
|
11522
|
+
returnedMatches: selectedMatches.length,
|
|
11523
|
+
returnedParagraphs: paragraphs.length,
|
|
11524
|
+
truncated,
|
|
11525
|
+
nextAfter: truncated && selectedMatches.length > 0 ? selectedMatches[selectedMatches.length - 1].index : null,
|
|
11526
|
+
...limit != null ? { limit } : {},
|
|
11527
|
+
...after != null ? { after } : {},
|
|
11528
|
+
...around > 0 ? { around } : {}
|
|
11529
|
+
}
|
|
11530
|
+
} : {}
|
|
11531
|
+
};
|
|
11532
|
+
}
|
|
11391
11533
|
function inspectDocumentParts(parts, options = {}) {
|
|
11392
11534
|
const documentPart = parseXml2(parts?.documentXml, "word/document.xml", true);
|
|
11393
11535
|
if (documentPart.error) return { status: "error", error: documentPart.error, paragraphs: [], comments: [], warnings: [] };
|
|
@@ -11403,9 +11545,10 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
11403
11545
|
const resolveNumbering = createNumberingResolver(numberingPart.doc);
|
|
11404
11546
|
let nearestHeading = null;
|
|
11405
11547
|
const paragraphNodes = getDocumentParagraphNodes(documentPart.doc);
|
|
11406
|
-
const
|
|
11548
|
+
const revisionView = options.revisionView === "rejected" ? "rejected" : "accepted";
|
|
11549
|
+
const commentAnchors = collectDocumentCommentAnchors(paragraphNodes, revisionView);
|
|
11407
11550
|
let paragraphs = paragraphNodes.map((paragraph, zeroIndex) => {
|
|
11408
|
-
const text = extractCanonicalParagraphText(paragraph, { revisionView
|
|
11551
|
+
const text = extractCanonicalParagraphText(paragraph, { revisionView });
|
|
11409
11552
|
const level = headingLevel(paragraph);
|
|
11410
11553
|
if (level) nearestHeading = { level, text };
|
|
11411
11554
|
const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
|
|
@@ -11416,17 +11559,20 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
11416
11559
|
const index = zeroIndex + 1;
|
|
11417
11560
|
const provision = list?.label && list.format !== "bullet" ? list.label : null;
|
|
11418
11561
|
const headingText = nearestHeading?.text || null;
|
|
11419
|
-
const
|
|
11562
|
+
const excerpt = text.slice(0, options.excerptLength || 120);
|
|
11563
|
+
const humanReference = level ? text : provision ? [provision, headingText].filter(Boolean).join(" \u2014 ") : headingText ? [headingText, excerpt].filter(Boolean).join(" \u2014 ") : excerpt;
|
|
11420
11564
|
const segments = extractParagraphRevisionSegments(paragraph);
|
|
11421
11565
|
return {
|
|
11422
11566
|
index,
|
|
11423
11567
|
ref: `P${index}`,
|
|
11424
11568
|
paragraphId: getParagraphId2(paragraph),
|
|
11425
|
-
fingerprint: createParagraphFingerprint(paragraph),
|
|
11569
|
+
fingerprint: createParagraphFingerprint(paragraph, { text, index, revisionView }),
|
|
11570
|
+
revisionView,
|
|
11426
11571
|
text,
|
|
11427
11572
|
exactText: text,
|
|
11428
|
-
excerpt
|
|
11573
|
+
excerpt,
|
|
11429
11574
|
humanReference,
|
|
11575
|
+
provision,
|
|
11430
11576
|
inTable: hasAncestor(paragraph, "tc"),
|
|
11431
11577
|
table: structure.table,
|
|
11432
11578
|
styleId,
|
|
@@ -11447,22 +11593,8 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
11447
11593
|
definition.anchoredText ?? (definition.anchoredText = commentAnchors.get(id) || paragraph.text);
|
|
11448
11594
|
comments.set(id, definition);
|
|
11449
11595
|
}
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
if (options.skipEmpty) paragraphs = paragraphs.filter((item) => item.text.length > 0);
|
|
11453
|
-
if (options.search) {
|
|
11454
|
-
const needle = String(options.search).toLowerCase();
|
|
11455
|
-
paragraphs = paragraphs.filter((item) => item.text.toLowerCase().includes(needle));
|
|
11456
|
-
}
|
|
11457
|
-
if (Array.isArray(options.indexes)) {
|
|
11458
|
-
const indexes = new Set(options.indexes);
|
|
11459
|
-
paragraphs = paragraphs.filter((item) => indexes.has(item.index));
|
|
11460
|
-
}
|
|
11461
|
-
if (options.range) {
|
|
11462
|
-
const start = Number(options.range.start ?? options.range[0]);
|
|
11463
|
-
const end = Number(options.range.end ?? options.range[1]);
|
|
11464
|
-
paragraphs = paragraphs.filter((item) => item.index >= start && item.index <= end);
|
|
11465
|
-
}
|
|
11596
|
+
const selected = selectInspectionParagraphs(paragraphs, options);
|
|
11597
|
+
paragraphs = selected.paragraphs;
|
|
11466
11598
|
const allRevisionAuthors = [...new Set(paragraphs.flatMap((item) => item.revisionAuthors))].sort();
|
|
11467
11599
|
const coveredEntries = extractDocumentPartsEntries(parts);
|
|
11468
11600
|
const coveredParts = coveredEntries.map((e) => e.name).sort();
|
|
@@ -11479,6 +11611,7 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
11479
11611
|
revisionToken,
|
|
11480
11612
|
coveredParts,
|
|
11481
11613
|
paragraphs,
|
|
11614
|
+
...selected.selection ? { selection: selected.selection } : {},
|
|
11482
11615
|
comments: [...comments.values()],
|
|
11483
11616
|
revisionAuthors: allRevisionAuthors,
|
|
11484
11617
|
commentAuthors: [...new Set([...comments.values()].map((item) => item.author).filter(Boolean))].sort(),
|