@ansonlai/docx-redline-js 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +691 -669
- package/CHANGELOG.md +67 -1
- package/README.md +39 -8
- package/core/word-xml.js +160 -118
- package/dist/docx-redline-js.esm.js +528 -312
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +94 -94
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +528 -312
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +528 -312
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +64 -5
- package/docs/SKILL_AUTHORING.md +5 -1
- package/docs/TESTING.md +10 -1
- package/docs/schemas/document-operations.schema.json +1 -1
- package/engine/oxml-engine.js +748 -736
- package/engine/table-mode.js +7 -4
- package/index.d.ts +29 -2
- package/index.js +3 -0
- package/node/cli.js +993 -992
- package/package.json +1 -1
- package/pipeline/content-analysis.js +2 -1
- package/pipeline/markdown-processor.js +5 -2
- package/pipeline/structured-content.js +2 -1
- package/services/comment-parts-merge.js +124 -0
- package/services/comment-replies.js +1 -1
- package/services/document-operation-applier.js +1 -0
- package/services/document-operation-contract.js +1 -0
- package/services/document-operation-mutations.js +3362 -3346
- package/services/inspection-comparison.js +82 -0
- package/services/operation-heuristics.js +1 -1
- package/services/operation-preflight.js +42 -33
- package/services/revision-fidelity-verifier.js +5 -19
- package/services/standalone-docx-plumbing.js +38 -22
- package/services/table-reconciliation.js +11 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js standalone CommonJS bundle v0.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone CommonJS bundle v0.11.0 — 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;
|
|
@@ -8009,6 +8009,7 @@ __export(index_exports, {
|
|
|
8009
8009
|
canonicalNumberingDefinition: () => canonicalNumberingDefinition,
|
|
8010
8010
|
clearSingleLineListFallbackExplicitSequence: () => clearSingleLineListFallbackExplicitSequence,
|
|
8011
8011
|
collectContiguousListParagraphBlock: () => collectContiguousListParagraphBlock,
|
|
8012
|
+
compareDocumentInspections: () => compareDocumentInspections,
|
|
8012
8013
|
computeDocumentPartsRevisionToken: () => computeDocumentPartsRevisionToken,
|
|
8013
8014
|
computePackageRevisionToken: () => computePackageRevisionToken,
|
|
8014
8015
|
computeRevisionToken: () => computeRevisionToken,
|
|
@@ -8060,9 +8061,11 @@ __export(index_exports, {
|
|
|
8060
8061
|
injectCommentsIntoPackage: () => injectCommentsIntoPackage2,
|
|
8061
8062
|
insertBodyElementBeforeSectPr: () => insertBodyElementBeforeSectPr,
|
|
8062
8063
|
inspectDocumentParts: () => inspectDocumentParts,
|
|
8064
|
+
inspectionReviewShape: () => inspectionReviewShape,
|
|
8063
8065
|
isLikelyStructuredTableSourceParagraph: () => isLikelyStructuredTableSourceParagraph,
|
|
8064
8066
|
isMarkdownTableText: () => isMarkdownTableText,
|
|
8065
8067
|
isNodeVisibleInRevisionView: () => isNodeVisibleInRevisionView,
|
|
8068
|
+
mergeCommentParts: () => mergeCommentParts,
|
|
8066
8069
|
mergeNumberingParts: () => mergeNumberingParts,
|
|
8067
8070
|
mergeNumberingXmlBySchemaOrder: () => mergeNumberingXmlBySchemaOrder,
|
|
8068
8071
|
normalizeBodySectionOrderStandalone: () => normalizeBodySectionOrderStandalone,
|
|
@@ -8089,6 +8092,7 @@ __export(index_exports, {
|
|
|
8089
8092
|
repairKnownContentTypes: () => repairKnownContentTypes,
|
|
8090
8093
|
reserveNextNumberingId: () => reserveNextNumberingId,
|
|
8091
8094
|
reserveNextNumberingIdPair: () => reserveNextNumberingIdPair,
|
|
8095
|
+
resolveExistingRevisionsPolicy: () => resolveExistingRevisionsPolicy,
|
|
8092
8096
|
resolveParagraphRangeByRefs: () => resolveParagraphRangeByRefs,
|
|
8093
8097
|
resolveSingleLineListFallbackNumberingAction: () => resolveSingleLineListFallbackNumberingAction,
|
|
8094
8098
|
resolveTargetParagraph: () => resolveTargetParagraph,
|
|
@@ -8287,7 +8291,10 @@ var MARKDOWN_PATTERNS = [
|
|
|
8287
8291
|
{ regex: /\*\*\+\+(.+?)\+\+\*\*/, format: { bold: true, underline: true } },
|
|
8288
8292
|
// Bold: **text** or __text__
|
|
8289
8293
|
{ regex: /\*\*(.+?)\*\*/, format: { bold: true } },
|
|
8290
|
-
|
|
8294
|
+
// Underscore delimiters follow CommonMark flanking rules: not intraword, not part of a
|
|
8295
|
+
// longer underscore run, and never around whitespace. So signature lines ("_____")
|
|
8296
|
+
// and snake_case identifiers stay literal text.
|
|
8297
|
+
{ regex: /(?<![A-Za-z0-9_])__(?=[^_\s])(.+?)(?<=[^_\s])__(?![A-Za-z0-9_])/, format: { bold: true } },
|
|
8291
8298
|
// Underline: ++text++
|
|
8292
8299
|
{ regex: /\+\+(.+?)\+\+/, format: { underline: true } },
|
|
8293
8300
|
// Strikethrough: ~~text~~ or ~text~
|
|
@@ -8295,7 +8302,7 @@ var MARKDOWN_PATTERNS = [
|
|
|
8295
8302
|
{ regex: /~(.+?)~/, format: { strikethrough: true } },
|
|
8296
8303
|
// Italic: *text* or _text_ (using lookahead only for compatibility)
|
|
8297
8304
|
{ regex: /\*(?!\*)(.+?)\*(?!\*)/, format: { italic: true } },
|
|
8298
|
-
{ regex: /_(
|
|
8305
|
+
{ regex: /(?<![A-Za-z0-9_])_(?=[^_\s])(.+?)(?<=[^_\s])_(?![A-Za-z0-9_])/, format: { italic: true } }
|
|
8299
8306
|
];
|
|
8300
8307
|
function preprocessMarkdown(text2) {
|
|
8301
8308
|
if (!text2) {
|
|
@@ -10466,6 +10473,23 @@ function withOoxmlSourceType(result) {
|
|
|
10466
10473
|
}
|
|
10467
10474
|
return { ...result, sourceType: classifyOoxmlSourceType(result.oxml) };
|
|
10468
10475
|
}
|
|
10476
|
+
function hasUnattributedTrackedChange(node) {
|
|
10477
|
+
if (!node) return false;
|
|
10478
|
+
for (const localName4 of TRACKED_CHANGE_NAMES) {
|
|
10479
|
+
for (const change of wordElementsByLocalName(node, localName4)) {
|
|
10480
|
+
const author = change.getAttribute?.("w:author") || change.getAttribute?.("author") || (typeof change.getAttributeNS === "function" ? change.getAttributeNS(NS_W, "author") : null);
|
|
10481
|
+
if (!author || !String(author).trim()) return true;
|
|
10482
|
+
}
|
|
10483
|
+
}
|
|
10484
|
+
return false;
|
|
10485
|
+
}
|
|
10486
|
+
function resolveExistingRevisionsPolicy(policy, node, author) {
|
|
10487
|
+
if (policy !== "auto") return policy;
|
|
10488
|
+
const nodes = (Array.isArray(node) ? node : [node]).filter(Boolean);
|
|
10489
|
+
const own = String(author || "").trim().toLowerCase();
|
|
10490
|
+
const foreign = nodes.some((item) => containsTrackedChanges(item) && (hasUnattributedTrackedChange(item) || getTrackedChangeAuthors(item).some((name) => name.trim().toLowerCase() !== own)));
|
|
10491
|
+
return foreign ? "slice-cross-author" : "merge-same-author";
|
|
10492
|
+
}
|
|
10469
10493
|
|
|
10470
10494
|
// engine/paragraph-mark-revisions.js
|
|
10471
10495
|
function localNameOf(node) {
|
|
@@ -10572,6 +10596,7 @@ function paragraphMarkPPrXml(kind, ctx = {}) {
|
|
|
10572
10596
|
}
|
|
10573
10597
|
|
|
10574
10598
|
// services/table-reconciliation.js
|
|
10599
|
+
var cellMarkdown = (text2, options) => options?.plainTextContent === true ? { cleanText: String(text2 ?? ""), formatHints: [] } : preprocessMarkdown(text2);
|
|
10575
10600
|
function generateTableOoxml(tableData, options = {}) {
|
|
10576
10601
|
const {
|
|
10577
10602
|
generateRedlines = false,
|
|
@@ -10605,7 +10630,7 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10605
10630
|
let cellsXml = "";
|
|
10606
10631
|
for (let c = 0; c < numCols; c++) {
|
|
10607
10632
|
const cellText = row[c] || "";
|
|
10608
|
-
const { cleanText, formatHints } =
|
|
10633
|
+
const { cleanText, formatHints } = cellMarkdown(cellText, options);
|
|
10609
10634
|
const runModel = [{
|
|
10610
10635
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10611
10636
|
text: cleanText,
|
|
@@ -10720,7 +10745,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
10720
10745
|
let cellsXml = "";
|
|
10721
10746
|
const rowInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
10722
10747
|
for (const cellText of op.cells) {
|
|
10723
|
-
const { cleanText, formatHints } =
|
|
10748
|
+
const { cleanText, formatHints } = cellMarkdown(cellText, options);
|
|
10724
10749
|
const runModel = [{
|
|
10725
10750
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10726
10751
|
text: cleanText,
|
|
@@ -10794,7 +10819,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
|
|
|
10794
10819
|
}
|
|
10795
10820
|
return;
|
|
10796
10821
|
}
|
|
10797
|
-
const { cleanText, formatHints } =
|
|
10822
|
+
const { cleanText, formatHints } = cellMarkdown(line, options);
|
|
10798
10823
|
const diffOps = computeWordLevelDiffOps(block.acceptedText, cleanText);
|
|
10799
10824
|
const model = applyPatches(splitRunsAtDiffBoundaries(block.runModel, diffOps), diffOps, {
|
|
10800
10825
|
generateRedlines,
|
|
@@ -10804,7 +10829,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
|
|
|
10804
10829
|
out += serializeToOoxml(model, block.pPr, formatHints, { author, generateRedlines, revisionIdAllocator });
|
|
10805
10830
|
});
|
|
10806
10831
|
for (let i2 = cell.blocks.length; i2 < lines.length; i2++) {
|
|
10807
|
-
const { cleanText, formatHints } =
|
|
10832
|
+
const { cleanText, formatHints } = cellMarkdown(lines[i2], options);
|
|
10808
10833
|
const runModel = [{
|
|
10809
10834
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10810
10835
|
text: cleanText,
|
|
@@ -10823,7 +10848,7 @@ function reconcileCellContent(cell, newText, options) {
|
|
|
10823
10848
|
return reconcileMultiBlockCellContent(cell, newText, options);
|
|
10824
10849
|
}
|
|
10825
10850
|
const { generateRedlines, author, revisionIdAllocator = null } = options;
|
|
10826
|
-
const { cleanText, formatHints } =
|
|
10851
|
+
const { cleanText, formatHints } = cellMarkdown(newText, options);
|
|
10827
10852
|
const oldText = cell.getText();
|
|
10828
10853
|
const diffOps = computeWordLevelDiffOps(oldText, cleanText);
|
|
10829
10854
|
const baseBlock = cell.blocks[0] || { runModel: [], pPr: null };
|
|
@@ -10869,7 +10894,7 @@ function parseTable(text2) {
|
|
|
10869
10894
|
}
|
|
10870
10895
|
const isSeparatorLine = (line) => {
|
|
10871
10896
|
const normalized = line.replace(/\s+/g, "");
|
|
10872
|
-
return /^\|:?-{3,}:?(\|:?-{3,}:?)
|
|
10897
|
+
return /^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(normalized);
|
|
10873
10898
|
};
|
|
10874
10899
|
const hasHeader = lines.some(isSeparatorLine);
|
|
10875
10900
|
const dataLines = lines.filter((line) => !isSeparatorLine(line));
|
|
@@ -11453,7 +11478,7 @@ var ReconciliationPipeline = class {
|
|
|
11453
11478
|
};
|
|
11454
11479
|
|
|
11455
11480
|
// pipeline/structured-content.js
|
|
11456
|
-
var TABLE_SEPARATOR = /^\s
|
|
11481
|
+
var TABLE_SEPARATOR = /^\s*(?:\|\s*:?-{3,}:?\s*\||\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?)\s*$/;
|
|
11457
11482
|
function isBlank(line) {
|
|
11458
11483
|
return !line || line.trim().length === 0;
|
|
11459
11484
|
}
|
|
@@ -15110,7 +15135,7 @@ function keepUnchangedTextOutsideWrappers(diffs, mapping) {
|
|
|
15110
15135
|
function noChanges(serializer, xmlDoc) {
|
|
15111
15136
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
15112
15137
|
}
|
|
15113
|
-
function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
|
|
15138
|
+
function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true, extra = {}) {
|
|
15114
15139
|
const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
15115
15140
|
const newTableData = parseTable(modifiedText);
|
|
15116
15141
|
const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
|
|
@@ -15126,7 +15151,8 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
15126
15151
|
const options = {
|
|
15127
15152
|
generateRedlines,
|
|
15128
15153
|
author,
|
|
15129
|
-
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
|
|
15154
|
+
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc),
|
|
15155
|
+
plainTextContent: extra.plainTextContent === true
|
|
15130
15156
|
};
|
|
15131
15157
|
const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
|
|
15132
15158
|
const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
|
|
@@ -15146,14 +15172,14 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
15146
15172
|
targetTable.parentNode.replaceChild(importedTable, targetTable);
|
|
15147
15173
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
|
|
15148
15174
|
}
|
|
15149
|
-
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
|
|
15175
|
+
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines, extra = {}) {
|
|
15150
15176
|
const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
|
|
15151
15177
|
const tableData = parseTable(modifiedText);
|
|
15152
15178
|
if (!tableData || tableData.rows.length === 0 && tableData.headers.length === 0) {
|
|
15153
15179
|
log("[OxmlEngine] Failed to parse table data from Markdown");
|
|
15154
15180
|
return noChanges(serializer, xmlDoc);
|
|
15155
15181
|
}
|
|
15156
|
-
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true });
|
|
15182
|
+
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true, plainTextContent: extra.plainTextContent === true });
|
|
15157
15183
|
const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, "application/xml").doc;
|
|
15158
15184
|
if (!tableDoc) return noChanges(serializer, xmlDoc);
|
|
15159
15185
|
const tableParseError = getXmlParseError(tableDoc);
|
|
@@ -15976,7 +16002,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15976
16002
|
const operationWarnings = [];
|
|
15977
16003
|
let normalizedExistingRevisions = false;
|
|
15978
16004
|
let preservedInsertedParagraphMark = null;
|
|
15979
|
-
|
|
16005
|
+
let existingRevisionsPolicy = options.existingRevisions || "merge-same-author";
|
|
15980
16006
|
const keepNormalizedNoOp = existingRevisionsPolicy === "accept-all-first-keep-normalized";
|
|
15981
16007
|
const finalize = (result) => {
|
|
15982
16008
|
const withStatus = { ...result };
|
|
@@ -16024,6 +16050,10 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16024
16050
|
const parsed = parseOoxmlSafe(inputOoxml, "text/xml");
|
|
16025
16051
|
parseWarnings = parsed.warnings;
|
|
16026
16052
|
let xmlDoc = parsed.doc;
|
|
16053
|
+
if (existingRevisionsPolicy === "auto") {
|
|
16054
|
+
existingRevisionsPolicy = resolveExistingRevisionsPolicy("auto", xmlDoc, author);
|
|
16055
|
+
options = { ...options, existingRevisions: existingRevisionsPolicy };
|
|
16056
|
+
}
|
|
16027
16057
|
const parseError = xmlDoc ? getXmlParseError(xmlDoc) : null;
|
|
16028
16058
|
if (parsed.error || parseError) {
|
|
16029
16059
|
const message = parsed.error?.message || parseError?.textContent || "Could not parse OOXML input.";
|
|
@@ -16058,7 +16088,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16058
16088
|
if (existingRevisionsPolicy === "merge-same-author" || existingRevisionsPolicy === "slice-cross-author") {
|
|
16059
16089
|
const authors = getTrackedChangeAuthors(xmlDoc);
|
|
16060
16090
|
const currentAuthor = String(author || "").trim().toLowerCase();
|
|
16061
|
-
const isSameAuthor = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === currentAuthor);
|
|
16091
|
+
const isSameAuthor = authors.length > 0 && !hasUnattributedTrackedChange(xmlDoc) && authors.every((a) => a.trim().toLowerCase() === currentAuthor);
|
|
16062
16092
|
if (isSameAuthor) {
|
|
16063
16093
|
const commentIds = getCommentIdsInOoxml(xmlDoc);
|
|
16064
16094
|
if (commentIds.length > 0) {
|
|
@@ -16215,7 +16245,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16215
16245
|
sanitizedText = structuredAnalysis.normalizedMarkdown;
|
|
16216
16246
|
}
|
|
16217
16247
|
}
|
|
16218
|
-
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
16248
|
+
const { cleanText: cleanModifiedText, formatHints } = options.plainTextContent === true ? { cleanText: sanitizedText, formatHints: [] } : preprocessMarkdown(sanitizedText);
|
|
16219
16249
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
16220
16250
|
const hasLiteralTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
16221
16251
|
const hasListStructureChange = !hasLiteralTextChanges && options.explicitStructuredContent === true && isListTargetLoose(cleanModifiedText) && !sourceMatchesListShape(paragraphs, cleanModifiedText);
|
|
@@ -16348,11 +16378,11 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16348
16378
|
if (isMarkdownTable && !hasTables) {
|
|
16349
16379
|
recordRouteSelection(options, "table", { transformation: "text-to-table" });
|
|
16350
16380
|
log("[OxmlEngine] Text-to-table transformation: generating new table from Markdown");
|
|
16351
|
-
return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
|
|
16381
|
+
return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
|
|
16352
16382
|
}
|
|
16353
16383
|
if (hasTables && isMarkdownTable) {
|
|
16354
16384
|
recordRouteSelection(options, "table", { transformation: "table-reconciliation" });
|
|
16355
|
-
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
|
|
16385
|
+
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
|
|
16356
16386
|
}
|
|
16357
16387
|
if (usesSurgicalTextMode) {
|
|
16358
16388
|
recordRouteSelection(options, "surgical", { tableScoped: hasTables });
|
|
@@ -20047,6 +20077,70 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
20047
20077
|
};
|
|
20048
20078
|
}
|
|
20049
20079
|
|
|
20080
|
+
// services/inspection-comparison.js
|
|
20081
|
+
var commentIdentity = (comment) => [comment.author ?? null, comment.text ?? "", comment.anchoredText || ""];
|
|
20082
|
+
function reviewSegments(segments = []) {
|
|
20083
|
+
return segments.filter((segment) => segment.kind !== "baseline").reduce((result, segment) => {
|
|
20084
|
+
const next = {
|
|
20085
|
+
kind: segment.kind,
|
|
20086
|
+
author: segment.author || null,
|
|
20087
|
+
text: segment.text,
|
|
20088
|
+
acceptedStart: segment.acceptedStart ?? null,
|
|
20089
|
+
rejectedStart: segment.rejectedStart ?? null
|
|
20090
|
+
};
|
|
20091
|
+
const previous = result.at(-1);
|
|
20092
|
+
const adjacent = (key) => previous[key] == null && next[key] == null || previous[key] != null && previous[key] + previous.text.length === next[key];
|
|
20093
|
+
if (previous && previous.kind === next.kind && previous.author === next.author && adjacent("acceptedStart") && adjacent("rejectedStart")) previous.text += next.text;
|
|
20094
|
+
else result.push(next);
|
|
20095
|
+
return result;
|
|
20096
|
+
}, []);
|
|
20097
|
+
}
|
|
20098
|
+
function inspectionReviewShape(inspection) {
|
|
20099
|
+
const comments = inspection?.comments || [];
|
|
20100
|
+
return {
|
|
20101
|
+
paragraphs: (inspection?.paragraphs || []).map((paragraph) => ({
|
|
20102
|
+
text: paragraph.exactText,
|
|
20103
|
+
inTable: paragraph.inTable === true,
|
|
20104
|
+
table: paragraph.table ? [paragraph.table.rowIndex, paragraph.table.cellIndex] : null,
|
|
20105
|
+
list: paragraph.list ? [paragraph.list.format, paragraph.list.level, paragraph.list.label] : null,
|
|
20106
|
+
review: {
|
|
20107
|
+
present: paragraph.hasRevisions === true,
|
|
20108
|
+
authors: [...paragraph.revisionAuthors || []].sort(),
|
|
20109
|
+
segments: reviewSegments(paragraph.segments)
|
|
20110
|
+
}
|
|
20111
|
+
})),
|
|
20112
|
+
comments: comments.map((comment) => {
|
|
20113
|
+
const parent = comment.parentCommentId != null ? comments.find((item) => String(item.id) === String(comment.parentCommentId)) : null;
|
|
20114
|
+
return [
|
|
20115
|
+
...commentIdentity(comment),
|
|
20116
|
+
comment.done === true,
|
|
20117
|
+
parent ? commentIdentity(parent) : comment.parentCommentId != null || comment.parentParaId ? "unresolved parent" : null
|
|
20118
|
+
];
|
|
20119
|
+
}).map((item) => JSON.stringify(item)).sort()
|
|
20120
|
+
};
|
|
20121
|
+
}
|
|
20122
|
+
function compareDocumentInspections(expected, actual) {
|
|
20123
|
+
const left = inspectionReviewShape(expected);
|
|
20124
|
+
const right = inspectionReviewShape(actual);
|
|
20125
|
+
const differences = [];
|
|
20126
|
+
const note = (path, a, b) => {
|
|
20127
|
+
if (differences.length < 10 && JSON.stringify(a) !== JSON.stringify(b)) differences.push({ path, expected: a, actual: b });
|
|
20128
|
+
};
|
|
20129
|
+
note("paragraphs.length", left.paragraphs.length, right.paragraphs.length);
|
|
20130
|
+
const count = Math.max(left.paragraphs.length, right.paragraphs.length);
|
|
20131
|
+
for (let index = 0; index < count; index++) {
|
|
20132
|
+
const a = left.paragraphs[index] ?? null;
|
|
20133
|
+
const b = right.paragraphs[index] ?? null;
|
|
20134
|
+
if (!a || !b) {
|
|
20135
|
+
note(`paragraphs[${index}]`, a, b);
|
|
20136
|
+
continue;
|
|
20137
|
+
}
|
|
20138
|
+
for (const key of ["text", "inTable", "table", "list", "review"]) note(`paragraphs[${index}].${key}`, a[key], b[key]);
|
|
20139
|
+
}
|
|
20140
|
+
note("comments", left.comments, right.comments);
|
|
20141
|
+
return { matches: differences.length === 0, differences };
|
|
20142
|
+
}
|
|
20143
|
+
|
|
20050
20144
|
// services/comment-locator.js
|
|
20051
20145
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
20052
20146
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -20521,7 +20615,7 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
|
|
|
20521
20615
|
extendedDoc = parsed.doc;
|
|
20522
20616
|
}
|
|
20523
20617
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
20524
|
-
if (documentXml) for (const match of String(documentXml).matchAll(
|
|
20618
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/\bw14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
20525
20619
|
const parentParagraph = commentThreadParagraph(parent);
|
|
20526
20620
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
20527
20621
|
let parentParaId = commentParaId(parent);
|
|
@@ -20584,232 +20678,6 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
|
|
|
20584
20678
|
};
|
|
20585
20679
|
}
|
|
20586
20680
|
|
|
20587
|
-
// engine/formatting-removal.js
|
|
20588
|
-
function removeNode2(node) {
|
|
20589
|
-
if (node?.parentNode) {
|
|
20590
|
-
node.parentNode.removeChild(node);
|
|
20591
|
-
}
|
|
20592
|
-
}
|
|
20593
|
-
function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
20594
|
-
if (!rPr) return null;
|
|
20595
|
-
const rPrClone = rPr.cloneNode(true);
|
|
20596
|
-
if (formatTypes.includes("all")) {
|
|
20597
|
-
const toRemove = [
|
|
20598
|
-
"w:b",
|
|
20599
|
-
"w:i",
|
|
20600
|
-
"w:u",
|
|
20601
|
-
"w:strike",
|
|
20602
|
-
"w:dstrike",
|
|
20603
|
-
"w:color",
|
|
20604
|
-
"w:sz",
|
|
20605
|
-
"w:szCs",
|
|
20606
|
-
"w:rFonts",
|
|
20607
|
-
"w:highlight",
|
|
20608
|
-
"w:vertAlign",
|
|
20609
|
-
"w:spacing",
|
|
20610
|
-
"w:w",
|
|
20611
|
-
"w:kern",
|
|
20612
|
-
"w:position"
|
|
20613
|
-
];
|
|
20614
|
-
toRemove.forEach((tag) => {
|
|
20615
|
-
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
20616
|
-
elements.forEach(removeNode2);
|
|
20617
|
-
});
|
|
20618
|
-
} else {
|
|
20619
|
-
const tagMap = {
|
|
20620
|
-
"bold": "w:b",
|
|
20621
|
-
"italic": "w:i",
|
|
20622
|
-
"underline": "w:u",
|
|
20623
|
-
"strikethrough": "w:strike",
|
|
20624
|
-
"doubleStrike": "w:dstrike",
|
|
20625
|
-
"color": "w:color",
|
|
20626
|
-
"highlight": "w:highlight",
|
|
20627
|
-
"fontSize": "w:sz",
|
|
20628
|
-
"fontSizeCs": "w:szCs",
|
|
20629
|
-
// Complex script font size
|
|
20630
|
-
"fontFamily": "w:rFonts",
|
|
20631
|
-
"superscript": "w:vertAlign",
|
|
20632
|
-
"subscript": "w:vertAlign"
|
|
20633
|
-
};
|
|
20634
|
-
formatTypes.forEach((type) => {
|
|
20635
|
-
const tag = tagMap[type];
|
|
20636
|
-
if (tag) {
|
|
20637
|
-
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
20638
|
-
elements.forEach(removeNode2);
|
|
20639
|
-
}
|
|
20640
|
-
});
|
|
20641
|
-
}
|
|
20642
|
-
return rPrClone.children.length > 0 ? rPrClone : null;
|
|
20643
|
-
}
|
|
20644
|
-
function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
20645
|
-
if (!targetText || !ooxmlString) return ooxmlString;
|
|
20646
|
-
const doc = parseOoxml(ooxmlString);
|
|
20647
|
-
if (!doc) return ooxmlString;
|
|
20648
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20649
|
-
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
20650
|
-
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
20651
|
-
const allRuns = [...Array.from(runs)];
|
|
20652
|
-
for (const ins of insertions) {
|
|
20653
|
-
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
20654
|
-
allRuns.push(...Array.from(insideRuns));
|
|
20655
|
-
}
|
|
20656
|
-
for (const run of allRuns) {
|
|
20657
|
-
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
20658
|
-
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
20659
|
-
if (runText.includes(targetText) || runText === targetText) {
|
|
20660
|
-
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
20661
|
-
if (rPrElements.length > 0) {
|
|
20662
|
-
const rPr = rPrElements[0];
|
|
20663
|
-
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
20664
|
-
if (newRPr) {
|
|
20665
|
-
if (rPr.parentNode) {
|
|
20666
|
-
rPr.parentNode.replaceChild(newRPr, rPr);
|
|
20667
|
-
}
|
|
20668
|
-
} else {
|
|
20669
|
-
removeNode2(rPr);
|
|
20670
|
-
}
|
|
20671
|
-
}
|
|
20672
|
-
}
|
|
20673
|
-
}
|
|
20674
|
-
return serializeOoxml(doc);
|
|
20675
|
-
}
|
|
20676
|
-
var HIGHLIGHT_COLOR_MAP = {
|
|
20677
|
-
"yellow": "yellow",
|
|
20678
|
-
"green": "green",
|
|
20679
|
-
"cyan": "cyan",
|
|
20680
|
-
"magenta": "magenta",
|
|
20681
|
-
"blue": "blue",
|
|
20682
|
-
"red": "red",
|
|
20683
|
-
"darkblue": "darkBlue",
|
|
20684
|
-
"darkcyan": "darkCyan",
|
|
20685
|
-
"darkgreen": "darkGreen",
|
|
20686
|
-
"darkmagenta": "darkMagenta",
|
|
20687
|
-
"darkred": "darkRed",
|
|
20688
|
-
"darkyellow": "darkYellow",
|
|
20689
|
-
"gray25": "lightGray",
|
|
20690
|
-
"gray50": "darkGray",
|
|
20691
|
-
"black": "black",
|
|
20692
|
-
"white": "white"
|
|
20693
|
-
};
|
|
20694
|
-
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
20695
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20696
|
-
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
20697
|
-
const generateRedlines = options?.generateRedlines ?? false;
|
|
20698
|
-
const author = options?.author || getDefaultAuthor();
|
|
20699
|
-
let rPrElement = rPr;
|
|
20700
|
-
if (!rPrElement) {
|
|
20701
|
-
rPrElement = createWordElement(doc, "w:rPr");
|
|
20702
|
-
} else {
|
|
20703
|
-
rPrElement = rPr.cloneNode(true);
|
|
20704
|
-
}
|
|
20705
|
-
let previousRPrState = null;
|
|
20706
|
-
if (generateRedlines) {
|
|
20707
|
-
previousRPrState = createWordElement(doc, "w:rPr");
|
|
20708
|
-
Array.from(rPrElement.childNodes).forEach((child) => {
|
|
20709
|
-
if (child.nodeName !== "w:rPrChange") {
|
|
20710
|
-
previousRPrState.appendChild(child.cloneNode(true));
|
|
20711
|
-
}
|
|
20712
|
-
});
|
|
20713
|
-
}
|
|
20714
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
20715
|
-
Array.from(existingHighlight).forEach(removeNode2);
|
|
20716
|
-
const highlightEl = createWordElement(doc, "w:highlight");
|
|
20717
|
-
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
20718
|
-
rPrElement.appendChild(highlightEl);
|
|
20719
|
-
if (generateRedlines && previousRPrState) {
|
|
20720
|
-
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
20721
|
-
const metadata = createRevisionMetadata(author, doc);
|
|
20722
|
-
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
20723
|
-
rPrChange.setAttribute("w:author", metadata.author);
|
|
20724
|
-
rPrChange.setAttribute("w:date", metadata.date);
|
|
20725
|
-
rPrChange.appendChild(previousRPrState);
|
|
20726
|
-
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
20727
|
-
Array.from(existingChange).forEach(removeNode2);
|
|
20728
|
-
rPrElement.appendChild(rPrChange);
|
|
20729
|
-
}
|
|
20730
|
-
return rPrElement;
|
|
20731
|
-
}
|
|
20732
|
-
function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
|
|
20733
|
-
if (!targetText || !ooxmlString) return ooxmlString;
|
|
20734
|
-
const doc = parseOoxml(ooxmlString);
|
|
20735
|
-
if (!doc) return ooxmlString;
|
|
20736
|
-
let revisionIdAllocator;
|
|
20737
|
-
if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
|
|
20738
|
-
revisionIdAllocator = options._revisionIdAllocator;
|
|
20739
|
-
seedRevisionIdsFromDocument(doc, revisionIdAllocator);
|
|
20740
|
-
} else {
|
|
20741
|
-
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
20742
|
-
}
|
|
20743
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20744
|
-
const getRunText = (run) => {
|
|
20745
|
-
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
20746
|
-
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
20747
|
-
};
|
|
20748
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
20749
|
-
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
20750
|
-
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
20751
|
-
const clonedRun = sourceRun.cloneNode(true);
|
|
20752
|
-
if (sourceRunsWithClaimedRevisionIds.has(sourceRun)) {
|
|
20753
|
-
refreshRunPropertyChangeIds(clonedRun, revisionIdAllocator);
|
|
20754
|
-
} else {
|
|
20755
|
-
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
20756
|
-
}
|
|
20757
|
-
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
20758
|
-
Array.from(textNodes).forEach(removeNode2);
|
|
20759
|
-
const newText = createWordElement(doc, "w:t");
|
|
20760
|
-
newText.setAttribute("xml:space", "preserve");
|
|
20761
|
-
newText.textContent = text2;
|
|
20762
|
-
clonedRun.appendChild(newText);
|
|
20763
|
-
if (shouldHighlight) {
|
|
20764
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
20765
|
-
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
20766
|
-
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
20767
|
-
if (existingRPr) {
|
|
20768
|
-
clonedRun.replaceChild(newRPr, existingRPr);
|
|
20769
|
-
} else {
|
|
20770
|
-
clonedRun.insertBefore(newRPr, clonedRun.firstChild);
|
|
20771
|
-
}
|
|
20772
|
-
}
|
|
20773
|
-
return clonedRun;
|
|
20774
|
-
};
|
|
20775
|
-
for (const run of allRuns) {
|
|
20776
|
-
const runText = getRunText(run);
|
|
20777
|
-
if (!runText) continue;
|
|
20778
|
-
const matchIndexes = [];
|
|
20779
|
-
let searchOffset = 0;
|
|
20780
|
-
while (searchOffset <= runText.length - targetText.length) {
|
|
20781
|
-
const matchIndex = runText.indexOf(targetText, searchOffset);
|
|
20782
|
-
if (matchIndex === -1) break;
|
|
20783
|
-
matchIndexes.push(matchIndex);
|
|
20784
|
-
searchOffset = matchIndex + targetText.length;
|
|
20785
|
-
}
|
|
20786
|
-
if (matchIndexes.length === 0) continue;
|
|
20787
|
-
const parent = run.parentNode;
|
|
20788
|
-
if (!parent) {
|
|
20789
|
-
console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
|
|
20790
|
-
continue;
|
|
20791
|
-
}
|
|
20792
|
-
const fragment = doc.createDocumentFragment();
|
|
20793
|
-
let cursor = 0;
|
|
20794
|
-
for (const matchIndex of matchIndexes) {
|
|
20795
|
-
if (matchIndex > cursor) {
|
|
20796
|
-
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
|
|
20797
|
-
}
|
|
20798
|
-
fragment.appendChild(cloneRunWithText2(
|
|
20799
|
-
run,
|
|
20800
|
-
runText.slice(matchIndex, matchIndex + targetText.length),
|
|
20801
|
-
true
|
|
20802
|
-
));
|
|
20803
|
-
cursor = matchIndex + targetText.length;
|
|
20804
|
-
}
|
|
20805
|
-
if (cursor < runText.length) {
|
|
20806
|
-
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
|
|
20807
|
-
}
|
|
20808
|
-
parent.replaceChild(fragment, run);
|
|
20809
|
-
}
|
|
20810
|
-
return serializeOoxml(doc);
|
|
20811
|
-
}
|
|
20812
|
-
|
|
20813
20681
|
// services/package-parts.js
|
|
20814
20682
|
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
20815
20683
|
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
@@ -21097,31 +20965,34 @@ async function ensureNumberingArtifactsInZip(zip, numberingXmlList, options = {}
|
|
|
21097
20965
|
}
|
|
21098
20966
|
}
|
|
21099
20967
|
}
|
|
20968
|
+
function mergeCommentsPartXml(existingXml, commentsXml, options = {}) {
|
|
20969
|
+
if (!existingXml || options.replaceExisting === true) return commentsXml;
|
|
20970
|
+
const existingDoc = parseXmlStrictStandalone(existingXml, "word/comments.xml (existing)");
|
|
20971
|
+
const incomingDoc = parseXmlStrictStandalone(commentsXml, "word/comments.xml (incoming)");
|
|
20972
|
+
const incomingComments = Array.from(incomingDoc.documentElement.getElementsByTagNameNS(NS_W7, "comment"));
|
|
20973
|
+
if (incomingComments.length === 0) return existingXml;
|
|
20974
|
+
const existingRoot = existingDoc.documentElement;
|
|
20975
|
+
const usedIds = new Set(
|
|
20976
|
+
Array.from(existingRoot.getElementsByTagNameNS(NS_W7, "comment")).map((comment) => comment.getAttribute("w:id") || comment.getAttribute("id")).filter(Boolean)
|
|
20977
|
+
);
|
|
20978
|
+
for (const incomingComment of incomingComments) {
|
|
20979
|
+
const id = incomingComment.getAttribute("w:id") || incomingComment.getAttribute("id");
|
|
20980
|
+
if (id && usedIds.has(id)) {
|
|
20981
|
+
throw Object.assign(new Error(`Duplicate comment id: ${id}`), { code: "DUPLICATE_COMMENT_ID", commentId: id });
|
|
20982
|
+
}
|
|
20983
|
+
if (id) usedIds.add(id);
|
|
20984
|
+
existingRoot.appendChild(existingDoc.importNode(incomingComment, true));
|
|
20985
|
+
}
|
|
20986
|
+
return createSerializer().serializeToString(existingDoc);
|
|
20987
|
+
}
|
|
21100
20988
|
async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
|
|
21101
20989
|
const onInfo = typeof options?.onInfo === "function" ? options.onInfo : () => {
|
|
21102
20990
|
};
|
|
21103
20991
|
if (!commentsXml) return;
|
|
21104
20992
|
const serializer = createSerializer();
|
|
21105
20993
|
const existingText = await readZipText(zip, COMMENTS_PATH);
|
|
21106
|
-
if (!existingText || options.replaceExisting === true)
|
|
21107
|
-
|
|
21108
|
-
zip.file(COMMENTS_PATH, commentsXml);
|
|
21109
|
-
} else {
|
|
21110
|
-
const existingDoc = parseXmlStrictStandalone(existingText, "word/comments.xml (existing)");
|
|
21111
|
-
const incomingDoc = parseXmlStrictStandalone(commentsXml, "word/comments.xml (incoming)");
|
|
21112
|
-
const existingRoot = existingDoc.documentElement;
|
|
21113
|
-
const existingIds = new Set(
|
|
21114
|
-
Array.from(existingRoot.getElementsByTagNameNS(NS_W7, "comment")).map((comment) => comment.getAttribute("w:id") || comment.getAttribute("id")).filter(Boolean)
|
|
21115
|
-
);
|
|
21116
|
-
for (const incomingComment of Array.from(incomingDoc.documentElement.getElementsByTagNameNS(NS_W7, "comment"))) {
|
|
21117
|
-
const id = incomingComment.getAttribute("w:id") || incomingComment.getAttribute("id");
|
|
21118
|
-
if (id && existingIds.has(id)) {
|
|
21119
|
-
throw new Error(`Duplicate comment id: ${id}`);
|
|
21120
|
-
}
|
|
21121
|
-
existingRoot.appendChild(existingDoc.importNode(incomingComment, true));
|
|
21122
|
-
}
|
|
21123
|
-
zip.file(COMMENTS_PATH, serializer.serializeToString(existingDoc));
|
|
21124
|
-
}
|
|
20994
|
+
if (!existingText || options.replaceExisting === true) onInfo("[Demo] Adding comments.xml");
|
|
20995
|
+
zip.file(COMMENTS_PATH, mergeCommentsPartXml(existingText, commentsXml, options));
|
|
21125
20996
|
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
21126
20997
|
if (ctText) {
|
|
21127
20998
|
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
@@ -21357,6 +21228,318 @@ async function validateDocxPackage(zip) {
|
|
|
21357
21228
|
}
|
|
21358
21229
|
}
|
|
21359
21230
|
|
|
21231
|
+
// services/comment-parts-merge.js
|
|
21232
|
+
var PART_KEYS = Object.freeze({
|
|
21233
|
+
comments: "commentsXml",
|
|
21234
|
+
commentsExtended: "commentsExtendedXml",
|
|
21235
|
+
commentsIds: "commentsIdsXml",
|
|
21236
|
+
commentsExtensible: "commentsExtensibleXml"
|
|
21237
|
+
});
|
|
21238
|
+
var partsOf = (parts) => Object.fromEntries(Object.values(PART_KEYS).map((key) => [key, parts?.[key] || null]));
|
|
21239
|
+
function mergeCommentParts(parts = {}, output = {}) {
|
|
21240
|
+
const before = partsOf(parts);
|
|
21241
|
+
const after = { ...before };
|
|
21242
|
+
const fail = (error2) => ({ status: "error", hasChanges: false, parts: before, changedParts: [], addedParts: [], error: error2 });
|
|
21243
|
+
const commentsMode = output?.commentsXmlMode === "replace" ? "replace" : "merge";
|
|
21244
|
+
const extendedMode = output?.commentsExtendedXmlMode === "replace" ? "replace" : "merge";
|
|
21245
|
+
for (const [label, value] of [
|
|
21246
|
+
...Object.values(PART_KEYS).map((key) => [`parts.${key}`, before[key]]),
|
|
21247
|
+
["output.commentsXml", output?.commentsXml],
|
|
21248
|
+
["output.commentsExtendedXml", output?.commentsExtendedXml]
|
|
21249
|
+
]) {
|
|
21250
|
+
if (value != null && typeof value !== "string") {
|
|
21251
|
+
return fail({ code: "INVALID_COMMENT_PARTS", message: `${label} must be an XML string, null or omitted (got ${typeof value}).` });
|
|
21252
|
+
}
|
|
21253
|
+
}
|
|
21254
|
+
const requirePart = (xml, part, rootName, label) => {
|
|
21255
|
+
let doc;
|
|
21256
|
+
try {
|
|
21257
|
+
doc = parseXmlStrictStandalone(xml, label);
|
|
21258
|
+
} catch (error2) {
|
|
21259
|
+
throw Object.assign(error2, { part });
|
|
21260
|
+
}
|
|
21261
|
+
if (doc.documentElement?.localName !== rootName) {
|
|
21262
|
+
throw Object.assign(new Error(`${label}: expected a <${rootName}> root element, found <${doc.documentElement?.localName || "none"}>.`), { code: "INVALID_COMMENT_PARTS", part });
|
|
21263
|
+
}
|
|
21264
|
+
return doc;
|
|
21265
|
+
};
|
|
21266
|
+
try {
|
|
21267
|
+
if (output?.commentsXml) {
|
|
21268
|
+
const incomingDoc = requirePart(output.commentsXml, "comments", "comments", "word/comments.xml (incoming)");
|
|
21269
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21270
|
+
for (const node of Array.from(incomingDoc.getElementsByTagNameNS("*", "comment"))) {
|
|
21271
|
+
const id = node.getAttribute("w:id") || node.getAttribute("id");
|
|
21272
|
+
if (!id) continue;
|
|
21273
|
+
if (seen.has(id)) {
|
|
21274
|
+
throw Object.assign(new Error(`Duplicate comment id: ${id}`), { code: "DUPLICATE_COMMENT_ID", commentId: id, part: "comments" });
|
|
21275
|
+
}
|
|
21276
|
+
seen.add(id);
|
|
21277
|
+
}
|
|
21278
|
+
if (before.commentsXml) requirePart(before.commentsXml, "comments", "comments", "word/comments.xml (existing)");
|
|
21279
|
+
if (before.commentsIdsXml) requirePart(before.commentsIdsXml, "commentsIds", "commentsIds", "word/commentsIds.xml (existing)");
|
|
21280
|
+
if (before.commentsExtensibleXml) requirePart(before.commentsExtensibleXml, "commentsExtensible", "commentsExtensible", "word/commentsExtensible.xml (existing)");
|
|
21281
|
+
after.commentsXml = mergeCommentsPartXml(before.commentsXml, output.commentsXml, { replaceExisting: commentsMode === "replace" });
|
|
21282
|
+
Object.assign(after, reconcileCommentSiblingParts({
|
|
21283
|
+
commentsXml: after.commentsXml,
|
|
21284
|
+
commentsIdsXml: after.commentsIdsXml,
|
|
21285
|
+
commentsExtensibleXml: after.commentsExtensibleXml
|
|
21286
|
+
}));
|
|
21287
|
+
}
|
|
21288
|
+
if (output?.commentsExtendedXml) {
|
|
21289
|
+
if (extendedMode !== "replace" && before.commentsExtendedXml) {
|
|
21290
|
+
return fail({
|
|
21291
|
+
code: "UNSUPPORTED_EXISTING_COMMENT_THREAD_METADATA",
|
|
21292
|
+
part: "commentsExtended",
|
|
21293
|
+
message: 'The output carries a commentsExtended delta (mode "merge") for parts that already have thread metadata; a delta cannot be merged into an existing commentsExtended.xml. Nothing was merged.'
|
|
21294
|
+
});
|
|
21295
|
+
}
|
|
21296
|
+
requirePart(output.commentsExtendedXml, "commentsExtended", "commentsEx", "word/commentsExtended.xml (incoming)");
|
|
21297
|
+
after.commentsExtendedXml = output.commentsExtendedXml;
|
|
21298
|
+
}
|
|
21299
|
+
} catch (error2) {
|
|
21300
|
+
return fail({
|
|
21301
|
+
code: error2?.code || "COMMENT_PARTS_MERGE_FAILED",
|
|
21302
|
+
message: error2?.message || String(error2),
|
|
21303
|
+
...error2?.part || error2?.code === "DUPLICATE_COMMENT_ID" ? { part: error2.part || "comments" } : {},
|
|
21304
|
+
...error2?.commentId ? { commentId: error2.commentId } : {}
|
|
21305
|
+
});
|
|
21306
|
+
}
|
|
21307
|
+
const changedParts = [];
|
|
21308
|
+
const addedParts = [];
|
|
21309
|
+
for (const [name, key] of Object.entries(PART_KEYS)) {
|
|
21310
|
+
if (after[key] === before[key]) continue;
|
|
21311
|
+
changedParts.push(name);
|
|
21312
|
+
if (!before[key]) addedParts.push(name);
|
|
21313
|
+
}
|
|
21314
|
+
return { status: "ok", hasChanges: changedParts.length > 0, parts: after, changedParts, addedParts };
|
|
21315
|
+
}
|
|
21316
|
+
|
|
21317
|
+
// engine/formatting-removal.js
|
|
21318
|
+
function removeNode2(node) {
|
|
21319
|
+
if (node?.parentNode) {
|
|
21320
|
+
node.parentNode.removeChild(node);
|
|
21321
|
+
}
|
|
21322
|
+
}
|
|
21323
|
+
function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
21324
|
+
if (!rPr) return null;
|
|
21325
|
+
const rPrClone = rPr.cloneNode(true);
|
|
21326
|
+
if (formatTypes.includes("all")) {
|
|
21327
|
+
const toRemove = [
|
|
21328
|
+
"w:b",
|
|
21329
|
+
"w:i",
|
|
21330
|
+
"w:u",
|
|
21331
|
+
"w:strike",
|
|
21332
|
+
"w:dstrike",
|
|
21333
|
+
"w:color",
|
|
21334
|
+
"w:sz",
|
|
21335
|
+
"w:szCs",
|
|
21336
|
+
"w:rFonts",
|
|
21337
|
+
"w:highlight",
|
|
21338
|
+
"w:vertAlign",
|
|
21339
|
+
"w:spacing",
|
|
21340
|
+
"w:w",
|
|
21341
|
+
"w:kern",
|
|
21342
|
+
"w:position"
|
|
21343
|
+
];
|
|
21344
|
+
toRemove.forEach((tag) => {
|
|
21345
|
+
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
21346
|
+
elements.forEach(removeNode2);
|
|
21347
|
+
});
|
|
21348
|
+
} else {
|
|
21349
|
+
const tagMap = {
|
|
21350
|
+
"bold": "w:b",
|
|
21351
|
+
"italic": "w:i",
|
|
21352
|
+
"underline": "w:u",
|
|
21353
|
+
"strikethrough": "w:strike",
|
|
21354
|
+
"doubleStrike": "w:dstrike",
|
|
21355
|
+
"color": "w:color",
|
|
21356
|
+
"highlight": "w:highlight",
|
|
21357
|
+
"fontSize": "w:sz",
|
|
21358
|
+
"fontSizeCs": "w:szCs",
|
|
21359
|
+
// Complex script font size
|
|
21360
|
+
"fontFamily": "w:rFonts",
|
|
21361
|
+
"superscript": "w:vertAlign",
|
|
21362
|
+
"subscript": "w:vertAlign"
|
|
21363
|
+
};
|
|
21364
|
+
formatTypes.forEach((type) => {
|
|
21365
|
+
const tag = tagMap[type];
|
|
21366
|
+
if (tag) {
|
|
21367
|
+
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
21368
|
+
elements.forEach(removeNode2);
|
|
21369
|
+
}
|
|
21370
|
+
});
|
|
21371
|
+
}
|
|
21372
|
+
return rPrClone.children.length > 0 ? rPrClone : null;
|
|
21373
|
+
}
|
|
21374
|
+
function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
21375
|
+
if (!targetText || !ooxmlString) return ooxmlString;
|
|
21376
|
+
const doc = parseOoxml(ooxmlString);
|
|
21377
|
+
if (!doc) return ooxmlString;
|
|
21378
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
21379
|
+
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
21380
|
+
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
21381
|
+
const allRuns = [...Array.from(runs)];
|
|
21382
|
+
for (const ins of insertions) {
|
|
21383
|
+
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
21384
|
+
allRuns.push(...Array.from(insideRuns));
|
|
21385
|
+
}
|
|
21386
|
+
for (const run of allRuns) {
|
|
21387
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
21388
|
+
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
21389
|
+
if (runText.includes(targetText) || runText === targetText) {
|
|
21390
|
+
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
21391
|
+
if (rPrElements.length > 0) {
|
|
21392
|
+
const rPr = rPrElements[0];
|
|
21393
|
+
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
21394
|
+
if (newRPr) {
|
|
21395
|
+
if (rPr.parentNode) {
|
|
21396
|
+
rPr.parentNode.replaceChild(newRPr, rPr);
|
|
21397
|
+
}
|
|
21398
|
+
} else {
|
|
21399
|
+
removeNode2(rPr);
|
|
21400
|
+
}
|
|
21401
|
+
}
|
|
21402
|
+
}
|
|
21403
|
+
}
|
|
21404
|
+
return serializeOoxml(doc);
|
|
21405
|
+
}
|
|
21406
|
+
var HIGHLIGHT_COLOR_MAP = {
|
|
21407
|
+
"yellow": "yellow",
|
|
21408
|
+
"green": "green",
|
|
21409
|
+
"cyan": "cyan",
|
|
21410
|
+
"magenta": "magenta",
|
|
21411
|
+
"blue": "blue",
|
|
21412
|
+
"red": "red",
|
|
21413
|
+
"darkblue": "darkBlue",
|
|
21414
|
+
"darkcyan": "darkCyan",
|
|
21415
|
+
"darkgreen": "darkGreen",
|
|
21416
|
+
"darkmagenta": "darkMagenta",
|
|
21417
|
+
"darkred": "darkRed",
|
|
21418
|
+
"darkyellow": "darkYellow",
|
|
21419
|
+
"gray25": "lightGray",
|
|
21420
|
+
"gray50": "darkGray",
|
|
21421
|
+
"black": "black",
|
|
21422
|
+
"white": "white"
|
|
21423
|
+
};
|
|
21424
|
+
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
21425
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
21426
|
+
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
21427
|
+
const generateRedlines = options?.generateRedlines ?? false;
|
|
21428
|
+
const author = options?.author || getDefaultAuthor();
|
|
21429
|
+
let rPrElement = rPr;
|
|
21430
|
+
if (!rPrElement) {
|
|
21431
|
+
rPrElement = createWordElement(doc, "w:rPr");
|
|
21432
|
+
} else {
|
|
21433
|
+
rPrElement = rPr.cloneNode(true);
|
|
21434
|
+
}
|
|
21435
|
+
let previousRPrState = null;
|
|
21436
|
+
if (generateRedlines) {
|
|
21437
|
+
previousRPrState = createWordElement(doc, "w:rPr");
|
|
21438
|
+
Array.from(rPrElement.childNodes).forEach((child) => {
|
|
21439
|
+
if (child.nodeName !== "w:rPrChange") {
|
|
21440
|
+
previousRPrState.appendChild(child.cloneNode(true));
|
|
21441
|
+
}
|
|
21442
|
+
});
|
|
21443
|
+
}
|
|
21444
|
+
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
21445
|
+
Array.from(existingHighlight).forEach(removeNode2);
|
|
21446
|
+
const highlightEl = createWordElement(doc, "w:highlight");
|
|
21447
|
+
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
21448
|
+
rPrElement.appendChild(highlightEl);
|
|
21449
|
+
if (generateRedlines && previousRPrState) {
|
|
21450
|
+
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
21451
|
+
const metadata = createRevisionMetadata(author, doc);
|
|
21452
|
+
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
21453
|
+
rPrChange.setAttribute("w:author", metadata.author);
|
|
21454
|
+
rPrChange.setAttribute("w:date", metadata.date);
|
|
21455
|
+
rPrChange.appendChild(previousRPrState);
|
|
21456
|
+
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
21457
|
+
Array.from(existingChange).forEach(removeNode2);
|
|
21458
|
+
rPrElement.appendChild(rPrChange);
|
|
21459
|
+
}
|
|
21460
|
+
return rPrElement;
|
|
21461
|
+
}
|
|
21462
|
+
function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
|
|
21463
|
+
if (!targetText || !ooxmlString) return ooxmlString;
|
|
21464
|
+
const doc = parseOoxml(ooxmlString);
|
|
21465
|
+
if (!doc) return ooxmlString;
|
|
21466
|
+
let revisionIdAllocator;
|
|
21467
|
+
if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
|
|
21468
|
+
revisionIdAllocator = options._revisionIdAllocator;
|
|
21469
|
+
seedRevisionIdsFromDocument(doc, revisionIdAllocator);
|
|
21470
|
+
} else {
|
|
21471
|
+
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
21472
|
+
}
|
|
21473
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
21474
|
+
const getRunText = (run) => {
|
|
21475
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
21476
|
+
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
21477
|
+
};
|
|
21478
|
+
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
21479
|
+
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
21480
|
+
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
21481
|
+
const clonedRun = sourceRun.cloneNode(true);
|
|
21482
|
+
if (sourceRunsWithClaimedRevisionIds.has(sourceRun)) {
|
|
21483
|
+
refreshRunPropertyChangeIds(clonedRun, revisionIdAllocator);
|
|
21484
|
+
} else {
|
|
21485
|
+
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
21486
|
+
}
|
|
21487
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
21488
|
+
Array.from(textNodes).forEach(removeNode2);
|
|
21489
|
+
const newText = createWordElement(doc, "w:t");
|
|
21490
|
+
newText.setAttribute("xml:space", "preserve");
|
|
21491
|
+
newText.textContent = text2;
|
|
21492
|
+
clonedRun.appendChild(newText);
|
|
21493
|
+
if (shouldHighlight) {
|
|
21494
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
21495
|
+
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
21496
|
+
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
21497
|
+
if (existingRPr) {
|
|
21498
|
+
clonedRun.replaceChild(newRPr, existingRPr);
|
|
21499
|
+
} else {
|
|
21500
|
+
clonedRun.insertBefore(newRPr, clonedRun.firstChild);
|
|
21501
|
+
}
|
|
21502
|
+
}
|
|
21503
|
+
return clonedRun;
|
|
21504
|
+
};
|
|
21505
|
+
for (const run of allRuns) {
|
|
21506
|
+
const runText = getRunText(run);
|
|
21507
|
+
if (!runText) continue;
|
|
21508
|
+
const matchIndexes = [];
|
|
21509
|
+
let searchOffset = 0;
|
|
21510
|
+
while (searchOffset <= runText.length - targetText.length) {
|
|
21511
|
+
const matchIndex = runText.indexOf(targetText, searchOffset);
|
|
21512
|
+
if (matchIndex === -1) break;
|
|
21513
|
+
matchIndexes.push(matchIndex);
|
|
21514
|
+
searchOffset = matchIndex + targetText.length;
|
|
21515
|
+
}
|
|
21516
|
+
if (matchIndexes.length === 0) continue;
|
|
21517
|
+
const parent = run.parentNode;
|
|
21518
|
+
if (!parent) {
|
|
21519
|
+
console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
|
|
21520
|
+
continue;
|
|
21521
|
+
}
|
|
21522
|
+
const fragment = doc.createDocumentFragment();
|
|
21523
|
+
let cursor = 0;
|
|
21524
|
+
for (const matchIndex of matchIndexes) {
|
|
21525
|
+
if (matchIndex > cursor) {
|
|
21526
|
+
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
|
|
21527
|
+
}
|
|
21528
|
+
fragment.appendChild(cloneRunWithText2(
|
|
21529
|
+
run,
|
|
21530
|
+
runText.slice(matchIndex, matchIndex + targetText.length),
|
|
21531
|
+
true
|
|
21532
|
+
));
|
|
21533
|
+
cursor = matchIndex + targetText.length;
|
|
21534
|
+
}
|
|
21535
|
+
if (cursor < runText.length) {
|
|
21536
|
+
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
|
|
21537
|
+
}
|
|
21538
|
+
parent.replaceChild(fragment, run);
|
|
21539
|
+
}
|
|
21540
|
+
return serializeOoxml(doc);
|
|
21541
|
+
}
|
|
21542
|
+
|
|
21360
21543
|
// orchestration/route-plan.js
|
|
21361
21544
|
var RoutePlanKind = Object.freeze({
|
|
21362
21545
|
STRUCTURED_LIST_DIRECT: "structured_list_direct",
|
|
@@ -22823,6 +23006,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
22823
23006
|
"highlight"
|
|
22824
23007
|
]);
|
|
22825
23008
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
23009
|
+
"auto",
|
|
22826
23010
|
"merge-same-author",
|
|
22827
23011
|
"slice-cross-author",
|
|
22828
23012
|
"reject-input",
|
|
@@ -25060,7 +25244,7 @@ function deriveParagraphThenTableAppend(modifiedText) {
|
|
|
25060
25244
|
const tableLines = lines.slice(1);
|
|
25061
25245
|
if (!tableLines.every((line) => line.trim().startsWith("|"))) return null;
|
|
25062
25246
|
const separator = tableLines[1].replace(/\s+/g, "");
|
|
25063
|
-
if (!/^\|:?-{3,}:?(\|:?-{3,}:?)
|
|
25247
|
+
if (!/^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(separator)) return null;
|
|
25064
25248
|
return { paragraphMarkdown: lines[0].trim(), tableText: tableLines.join("\n") };
|
|
25065
25249
|
}
|
|
25066
25250
|
|
|
@@ -25690,7 +25874,10 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
25690
25874
|
String(text2 || ""),
|
|
25691
25875
|
{
|
|
25692
25876
|
author,
|
|
25693
|
-
generateRedlines: false
|
|
25877
|
+
generateRedlines: false,
|
|
25878
|
+
// A caller's structuredContent: false keeps inserted paragraphs plain too: no headings,
|
|
25879
|
+
// and inline Markdown markers stay literal.
|
|
25880
|
+
...options.plainTextContent === true ? { structuredContent: false, plainTextContent: true } : {}
|
|
25694
25881
|
}
|
|
25695
25882
|
);
|
|
25696
25883
|
let sourceParagraph = null;
|
|
@@ -26877,13 +27064,17 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
26877
27064
|
}
|
|
26878
27065
|
};
|
|
26879
27066
|
}
|
|
27067
|
+
const autoExistingRevisions = options.existingRevisions === "auto";
|
|
27068
|
+
if (autoExistingRevisions) {
|
|
27069
|
+
options = { ...options, existingRevisions: resolveExistingRevisionsPolicy("auto", targetParagraph, author) };
|
|
27070
|
+
}
|
|
26880
27071
|
const hasRevisions = containsTrackedChanges(targetParagraph);
|
|
26881
27072
|
const existingPolicy = options.existingRevisions || "merge-same-author";
|
|
26882
27073
|
if (hasRevisions && existingPolicy !== "accept-all-first" && existingPolicy !== "accept-all-first-keep-normalized") {
|
|
26883
27074
|
if (existingPolicy === "merge-same-author" || existingPolicy === "slice-cross-author") {
|
|
26884
27075
|
const authors = getTrackedChangeAuthors(targetParagraph);
|
|
26885
27076
|
const opAuthor = String(author || "").trim().toLowerCase();
|
|
26886
|
-
const allSame = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
27077
|
+
const allSame = authors.length > 0 && !hasUnattributedTrackedChange(targetParagraph) && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
26887
27078
|
if (!allSame && existingPolicy === "merge-same-author") {
|
|
26888
27079
|
return {
|
|
26889
27080
|
documentXml,
|
|
@@ -26892,7 +27083,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
26892
27083
|
status: "error",
|
|
26893
27084
|
error: {
|
|
26894
27085
|
code: "EXISTING_REVISIONS",
|
|
26895
|
-
message: `Target paragraph contains tracked changes from another author (${authors
|
|
27086
|
+
message: `Target paragraph contains tracked changes from another author (${[...authors, ...hasUnattributedTrackedChange(targetParagraph) ? ["unattributed"] : []].join(", ")}). Use existingRevisions: "slice-cross-author" for a surgical edit that preserves reviewer history; accepting or rejecting revisions requires separate authorization.`,
|
|
26896
27087
|
revisionAuthors: authors,
|
|
26897
27088
|
currentPolicy: existingPolicy
|
|
26898
27089
|
}
|
|
@@ -27034,6 +27225,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27034
27225
|
onWarn
|
|
27035
27226
|
}) : typeof targetText === "string" && /\r?\n/.test(targetText) ? findReconstructionParagraphRange(xmlDoc, targetText) : null;
|
|
27036
27227
|
const hasExplicitRangeScope = Array.isArray(explicitRangeParagraphs) && explicitRangeParagraphs.length > 0;
|
|
27228
|
+
if (autoExistingRevisions && hasExplicitRangeScope) {
|
|
27229
|
+
options = { ...options, existingRevisions: resolveExistingRevisionsPolicy("auto", explicitRangeParagraphs, author) };
|
|
27230
|
+
}
|
|
27037
27231
|
if (!useTableScope && hasExplicitRangeScope) {
|
|
27038
27232
|
const insertionEntries = buildExplicitRangeInsertionEntries(explicitRangeParagraphs, effectiveModifiedText);
|
|
27039
27233
|
if (insertionEntries && insertionEntries.length > 0) {
|
|
@@ -27209,7 +27403,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27209
27403
|
author,
|
|
27210
27404
|
// A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
|
|
27211
27405
|
// Reject All removes the text but leaves an empty paragraph behind.
|
|
27212
|
-
{ generateRedlines, trackParagraphMark: true }
|
|
27406
|
+
{ generateRedlines, trackParagraphMark: true, plainTextContent: options.plainTextContent === true }
|
|
27213
27407
|
);
|
|
27214
27408
|
const imported = xmlDoc.importNode(plainParagraph, true);
|
|
27215
27409
|
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
@@ -27348,6 +27542,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27348
27542
|
existingRevisions: options.existingRevisions || "merge-same-author",
|
|
27349
27543
|
structuredContent: options.structuredContent !== false,
|
|
27350
27544
|
explicitStructuredContent: options.explicitStructuredContent === true,
|
|
27545
|
+
plainTextContent: options.plainTextContent === true,
|
|
27351
27546
|
_revisionIdAllocator: revisionIdAllocator,
|
|
27352
27547
|
_isolatedTableCell: useTableScope
|
|
27353
27548
|
}) : await applyRedlineToOxml2(scopedXml, originalTextForApply, effectiveModifiedText, {
|
|
@@ -27359,6 +27554,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27359
27554
|
pairReplacements: options.pairReplacements === true,
|
|
27360
27555
|
insertionAffinity: options.insertionAffinity || null,
|
|
27361
27556
|
literalTextEdit: options.literalTextEdit === true,
|
|
27557
|
+
plainTextContent: options.plainTextContent === true,
|
|
27362
27558
|
_revisionIdAllocator: revisionIdAllocator,
|
|
27363
27559
|
_isolatedTableCell: useTableScope
|
|
27364
27560
|
});
|
|
@@ -28369,7 +28565,31 @@ function verifyUntrackedLocality({ before, after, targetSourceIndex, beforeViews
|
|
|
28369
28565
|
}
|
|
28370
28566
|
return violations.length ? { ok: false, violations } : { ok: true };
|
|
28371
28567
|
}
|
|
28372
|
-
|
|
28568
|
+
var numberingDefinitionCache = /* @__PURE__ */ new Map();
|
|
28569
|
+
var NUMBERING_DEFINITION_CACHE_SIZE = 8;
|
|
28570
|
+
function parseNumberingDefinitions(text2) {
|
|
28571
|
+
const cached = numberingDefinitionCache.get(text2);
|
|
28572
|
+
if (cached) return cached;
|
|
28573
|
+
const abstractLevels = /* @__PURE__ */ new Map();
|
|
28574
|
+
for (const match of text2.matchAll(/<(?:\w+:)?abstractNum\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?abstractNum>/g)) {
|
|
28575
|
+
const id = /abstractNumId="([^"]*)"/.exec(match[1])?.[1];
|
|
28576
|
+
if (id == null) continue;
|
|
28577
|
+
const levels = abstractLevels.get(id) || /* @__PURE__ */ new Set();
|
|
28578
|
+
for (const lvl of match[2].matchAll(/<(?:\w+:)?lvl\b[^>]*?ilvl="([^"]*)"/g)) levels.add(lvl[1]);
|
|
28579
|
+
abstractLevels.set(id, levels);
|
|
28580
|
+
}
|
|
28581
|
+
const numToAbstract = /* @__PURE__ */ new Map();
|
|
28582
|
+
for (const match of text2.matchAll(/<(?:\w+:)?num\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?num>/g)) {
|
|
28583
|
+
const id = /numId="([^"]*)"/.exec(match[1])?.[1];
|
|
28584
|
+
const abstractId = /abstractNumId[^>]*?val="([^"]*)"/.exec(match[2])?.[1];
|
|
28585
|
+
if (id != null && abstractId != null) numToAbstract.set(id, abstractId);
|
|
28586
|
+
}
|
|
28587
|
+
const definitions = { abstractLevels, numToAbstract };
|
|
28588
|
+
if (numberingDefinitionCache.size >= NUMBERING_DEFINITION_CACHE_SIZE) numberingDefinitionCache.delete(numberingDefinitionCache.keys().next().value);
|
|
28589
|
+
numberingDefinitionCache.set(text2, definitions);
|
|
28590
|
+
return definitions;
|
|
28591
|
+
}
|
|
28592
|
+
function verifyNumberingReferences({ before, after, numberingXmls }) {
|
|
28373
28593
|
const references = (documentNode) => {
|
|
28374
28594
|
const found = /* @__PURE__ */ new Map();
|
|
28375
28595
|
for (const numIdNode of getAllReferencedNumIdElements(documentNode)) {
|
|
@@ -28385,21 +28605,7 @@ function verifyNumberingReferences({ before, after, numberingXmls, beforeViews =
|
|
|
28385
28605
|
const used = references(after);
|
|
28386
28606
|
if (used.size === 0) return { ok: true };
|
|
28387
28607
|
const known = references(before);
|
|
28388
|
-
const
|
|
28389
|
-
const abstractLevels = /* @__PURE__ */ new Map();
|
|
28390
|
-
for (const match of text2.matchAll(/<(?:\w+:)?abstractNum\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?abstractNum>/g)) {
|
|
28391
|
-
const id = /abstractNumId="([^"]*)"/.exec(match[1])?.[1];
|
|
28392
|
-
if (id == null) continue;
|
|
28393
|
-
const levels = abstractLevels.get(id) || /* @__PURE__ */ new Set();
|
|
28394
|
-
for (const lvl of match[2].matchAll(/<(?:\w+:)?lvl\b[^>]*?ilvl="([^"]*)"/g)) levels.add(lvl[1]);
|
|
28395
|
-
abstractLevels.set(id, levels);
|
|
28396
|
-
}
|
|
28397
|
-
const numToAbstract = /* @__PURE__ */ new Map();
|
|
28398
|
-
for (const match of text2.matchAll(/<(?:\w+:)?num\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?num>/g)) {
|
|
28399
|
-
const id = /numId="([^"]*)"/.exec(match[1])?.[1];
|
|
28400
|
-
const abstractId = /abstractNumId[^>]*?val="([^"]*)"/.exec(match[2])?.[1];
|
|
28401
|
-
if (id != null && abstractId != null) numToAbstract.set(id, abstractId);
|
|
28402
|
-
}
|
|
28608
|
+
const { abstractLevels, numToAbstract } = parseNumberingDefinitions((numberingXmls || []).filter(Boolean).join("\n"));
|
|
28403
28609
|
const violations = [];
|
|
28404
28610
|
for (const [numId, levels] of used) {
|
|
28405
28611
|
if (known.has(numId)) continue;
|
|
@@ -28748,6 +28954,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
28748
28954
|
...operation.existingRevisions ? { existingRevisions: operation.existingRevisions } : {},
|
|
28749
28955
|
structuredContent: typeof operation.structuredContent === "boolean" ? operation.structuredContent : options.structuredContent !== false,
|
|
28750
28956
|
explicitStructuredContent: operation.structuredContent === true,
|
|
28957
|
+
plainTextContent: operation.structuredContent === false || operation.structuredContent == null && options.structuredContent === false,
|
|
28751
28958
|
...operation._localizedReplacementCompilation && operation.structuredContent !== true ? { literalTextEdit: true } : {},
|
|
28752
28959
|
pairReplacements: typeof operation.pairReplacements === "boolean" ? operation.pairReplacements : options.pairReplacements !== false,
|
|
28753
28960
|
...operation.insertionAffinity ? { insertionAffinity: operation.insertionAffinity } : {},
|
|
@@ -30002,35 +30209,44 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
30002
30209
|
const anchor = operation.operationKind === "comment" ? operation.textToComment || paragraphText : operation.operationKind === "highlight" ? operation.textToHighlight : operation.operationKind === "format" ? operation.textToFormat : null;
|
|
30003
30210
|
const anchorResolution = operation.operationKind === "comment" && anchor != null ? resolveTextInParagraphIndex(createParagraphTextIndex(paragraph, { revisionView: targetView }), anchor) : null;
|
|
30004
30211
|
const anchorFound = anchor == null || (anchorResolution ? anchorResolution.found : paragraphText.includes(anchor));
|
|
30212
|
+
const rangeParagraphs = (opType) => {
|
|
30213
|
+
if (operation.targetEndRef) {
|
|
30214
|
+
return resolveParagraphRangeByRefs(xmlDoc, operation.targetRef, operation.targetEndRef, {
|
|
30215
|
+
opType,
|
|
30216
|
+
targetRefSnapshot: options.targetRefSnapshot || null,
|
|
30217
|
+
onInfo: options.onInfo,
|
|
30218
|
+
onWarn: options.onWarn
|
|
30219
|
+
});
|
|
30220
|
+
}
|
|
30221
|
+
if (!operation.targetEndDescriptor) return [paragraph];
|
|
30222
|
+
const endResolved = resolveTargetParagraph(xmlDoc, {
|
|
30223
|
+
targetText: operation.targetEndDescriptor.text,
|
|
30224
|
+
targetRef: operation.targetEndDescriptor.index,
|
|
30225
|
+
targetDescriptor: operation.targetEndDescriptor,
|
|
30226
|
+
opType,
|
|
30227
|
+
strictAmbiguity: strictTargets,
|
|
30228
|
+
paragraphMetadataIndex: currentMetadataIndex,
|
|
30229
|
+
metadataIndices,
|
|
30230
|
+
onInfo: options.onInfo,
|
|
30231
|
+
onWarn: options.onWarn
|
|
30232
|
+
});
|
|
30233
|
+
const allParagraphs = getDocumentParagraphNodes(xmlDoc);
|
|
30234
|
+
const startIndex = allParagraphs.indexOf(paragraph);
|
|
30235
|
+
const endIndex = allParagraphs.indexOf(endResolved?.paragraph);
|
|
30236
|
+
return startIndex >= 0 && endIndex >= startIndex ? allParagraphs.slice(startIndex, endIndex + 1) : null;
|
|
30237
|
+
};
|
|
30005
30238
|
const hasRevisions = containsTrackedChanges(paragraph);
|
|
30006
|
-
const
|
|
30239
|
+
const requestedPolicy = operation.existingRevisions || options.existingRevisions || "merge-same-author";
|
|
30240
|
+
const existingPolicy = resolveExistingRevisionsPolicy(
|
|
30241
|
+
requestedPolicy,
|
|
30242
|
+
requestedPolicy === "auto" && operation.operationKind === "redline" && (operation.targetEndRef || operation.targetEndDescriptor) ? rangeParagraphs("redline") || paragraph : paragraph,
|
|
30243
|
+
authorUsed
|
|
30244
|
+
);
|
|
30007
30245
|
const deletingWholeParagraph = operation.operationKind === "redline" && operation.modified === "";
|
|
30008
30246
|
const commentIds = deletingWholeParagraph ? getCommentIdsInParagraph(paragraph) : [];
|
|
30009
30247
|
let error2 = null;
|
|
30010
30248
|
if (operation.operationKind === "restore") {
|
|
30011
|
-
|
|
30012
|
-
opType: "restore",
|
|
30013
|
-
targetRefSnapshot: options.targetRefSnapshot || null,
|
|
30014
|
-
onInfo: options.onInfo,
|
|
30015
|
-
onWarn: options.onWarn
|
|
30016
|
-
}) : [paragraph];
|
|
30017
|
-
if (!operation.targetEndRef && operation.targetEndDescriptor) {
|
|
30018
|
-
const endResolved = resolveTargetParagraph(xmlDoc, {
|
|
30019
|
-
targetText: operation.targetEndDescriptor.text,
|
|
30020
|
-
targetRef: operation.targetEndDescriptor.index,
|
|
30021
|
-
targetDescriptor: operation.targetEndDescriptor,
|
|
30022
|
-
opType: "restore",
|
|
30023
|
-
strictAmbiguity: strictTargets,
|
|
30024
|
-
paragraphMetadataIndex: currentMetadataIndex,
|
|
30025
|
-
metadataIndices,
|
|
30026
|
-
onInfo: options.onInfo,
|
|
30027
|
-
onWarn: options.onWarn
|
|
30028
|
-
});
|
|
30029
|
-
const allParagraphs = getDocumentParagraphNodes(xmlDoc);
|
|
30030
|
-
const startIndex = allParagraphs.indexOf(paragraph);
|
|
30031
|
-
const endIndex = allParagraphs.indexOf(endResolved?.paragraph);
|
|
30032
|
-
restorationParagraphs = startIndex >= 0 && endIndex >= startIndex ? allParagraphs.slice(startIndex, endIndex + 1) : null;
|
|
30033
|
-
}
|
|
30249
|
+
const restorationParagraphs = rangeParagraphs("restore");
|
|
30034
30250
|
const replacements = Array.isArray(operation.modified) ? operation.modified : [operation.modified];
|
|
30035
30251
|
if (!restorationParagraphs || restorationParagraphs[0] !== paragraph || replacements.length !== restorationParagraphs.length) {
|
|
30036
30252
|
error2 = {
|
|
@@ -30077,11 +30293,11 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
30077
30293
|
if (existingPolicy === "merge-same-author" || existingPolicy === "slice-cross-author") {
|
|
30078
30294
|
const authors = getTrackedChangeAuthors(paragraph);
|
|
30079
30295
|
const opAuthor = String(authorUsed || "").trim().toLowerCase();
|
|
30080
|
-
const allSame = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
30296
|
+
const allSame = authors.length > 0 && !hasUnattributedTrackedChange(paragraph) && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
30081
30297
|
if (!allSame && existingPolicy === "merge-same-author") {
|
|
30082
30298
|
error2 = {
|
|
30083
30299
|
code: "EXISTING_REVISIONS",
|
|
30084
|
-
message: `Target paragraph contains tracked changes from another author (${authors
|
|
30300
|
+
message: `Target paragraph contains tracked changes from another author (${[...authors, ...hasUnattributedTrackedChange(paragraph) ? ["unattributed"] : []].join(", ")}). Use existingRevisions: "slice-cross-author" for a surgical edit that preserves reviewer history; accepting or rejecting revisions requires separate authorization.`,
|
|
30085
30301
|
revisionAuthors: authors,
|
|
30086
30302
|
currentPolicy: existingPolicy
|
|
30087
30303
|
};
|