@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 bundle v0.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone 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;
|
|
@@ -8142,7 +8142,10 @@ var MARKDOWN_PATTERNS = [
|
|
|
8142
8142
|
{ regex: /\*\*\+\+(.+?)\+\+\*\*/, format: { bold: true, underline: true } },
|
|
8143
8143
|
// Bold: **text** or __text__
|
|
8144
8144
|
{ regex: /\*\*(.+?)\*\*/, format: { bold: true } },
|
|
8145
|
-
|
|
8145
|
+
// Underscore delimiters follow CommonMark flanking rules: not intraword, not part of a
|
|
8146
|
+
// longer underscore run, and never around whitespace. So signature lines ("_____")
|
|
8147
|
+
// and snake_case identifiers stay literal text.
|
|
8148
|
+
{ regex: /(?<![A-Za-z0-9_])__(?=[^_\s])(.+?)(?<=[^_\s])__(?![A-Za-z0-9_])/, format: { bold: true } },
|
|
8146
8149
|
// Underline: ++text++
|
|
8147
8150
|
{ regex: /\+\+(.+?)\+\+/, format: { underline: true } },
|
|
8148
8151
|
// Strikethrough: ~~text~~ or ~text~
|
|
@@ -8150,7 +8153,7 @@ var MARKDOWN_PATTERNS = [
|
|
|
8150
8153
|
{ regex: /~(.+?)~/, format: { strikethrough: true } },
|
|
8151
8154
|
// Italic: *text* or _text_ (using lookahead only for compatibility)
|
|
8152
8155
|
{ regex: /\*(?!\*)(.+?)\*(?!\*)/, format: { italic: true } },
|
|
8153
|
-
{ regex: /_(
|
|
8156
|
+
{ regex: /(?<![A-Za-z0-9_])_(?=[^_\s])(.+?)(?<=[^_\s])_(?![A-Za-z0-9_])/, format: { italic: true } }
|
|
8154
8157
|
];
|
|
8155
8158
|
function preprocessMarkdown(text2) {
|
|
8156
8159
|
if (!text2) {
|
|
@@ -10321,6 +10324,23 @@ function withOoxmlSourceType(result) {
|
|
|
10321
10324
|
}
|
|
10322
10325
|
return { ...result, sourceType: classifyOoxmlSourceType(result.oxml) };
|
|
10323
10326
|
}
|
|
10327
|
+
function hasUnattributedTrackedChange(node) {
|
|
10328
|
+
if (!node) return false;
|
|
10329
|
+
for (const localName4 of TRACKED_CHANGE_NAMES) {
|
|
10330
|
+
for (const change of wordElementsByLocalName(node, localName4)) {
|
|
10331
|
+
const author = change.getAttribute?.("w:author") || change.getAttribute?.("author") || (typeof change.getAttributeNS === "function" ? change.getAttributeNS(NS_W, "author") : null);
|
|
10332
|
+
if (!author || !String(author).trim()) return true;
|
|
10333
|
+
}
|
|
10334
|
+
}
|
|
10335
|
+
return false;
|
|
10336
|
+
}
|
|
10337
|
+
function resolveExistingRevisionsPolicy(policy, node, author) {
|
|
10338
|
+
if (policy !== "auto") return policy;
|
|
10339
|
+
const nodes = (Array.isArray(node) ? node : [node]).filter(Boolean);
|
|
10340
|
+
const own = String(author || "").trim().toLowerCase();
|
|
10341
|
+
const foreign = nodes.some((item) => containsTrackedChanges(item) && (hasUnattributedTrackedChange(item) || getTrackedChangeAuthors(item).some((name) => name.trim().toLowerCase() !== own)));
|
|
10342
|
+
return foreign ? "slice-cross-author" : "merge-same-author";
|
|
10343
|
+
}
|
|
10324
10344
|
|
|
10325
10345
|
// engine/paragraph-mark-revisions.js
|
|
10326
10346
|
function localNameOf(node) {
|
|
@@ -10427,6 +10447,7 @@ function paragraphMarkPPrXml(kind, ctx = {}) {
|
|
|
10427
10447
|
}
|
|
10428
10448
|
|
|
10429
10449
|
// services/table-reconciliation.js
|
|
10450
|
+
var cellMarkdown = (text2, options) => options?.plainTextContent === true ? { cleanText: String(text2 ?? ""), formatHints: [] } : preprocessMarkdown(text2);
|
|
10430
10451
|
function generateTableOoxml(tableData, options = {}) {
|
|
10431
10452
|
const {
|
|
10432
10453
|
generateRedlines = false,
|
|
@@ -10460,7 +10481,7 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10460
10481
|
let cellsXml = "";
|
|
10461
10482
|
for (let c = 0; c < numCols; c++) {
|
|
10462
10483
|
const cellText = row[c] || "";
|
|
10463
|
-
const { cleanText, formatHints } =
|
|
10484
|
+
const { cleanText, formatHints } = cellMarkdown(cellText, options);
|
|
10464
10485
|
const runModel = [{
|
|
10465
10486
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10466
10487
|
text: cleanText,
|
|
@@ -10575,7 +10596,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
10575
10596
|
let cellsXml = "";
|
|
10576
10597
|
const rowInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
10577
10598
|
for (const cellText of op.cells) {
|
|
10578
|
-
const { cleanText, formatHints } =
|
|
10599
|
+
const { cleanText, formatHints } = cellMarkdown(cellText, options);
|
|
10579
10600
|
const runModel = [{
|
|
10580
10601
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10581
10602
|
text: cleanText,
|
|
@@ -10649,7 +10670,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
|
|
|
10649
10670
|
}
|
|
10650
10671
|
return;
|
|
10651
10672
|
}
|
|
10652
|
-
const { cleanText, formatHints } =
|
|
10673
|
+
const { cleanText, formatHints } = cellMarkdown(line, options);
|
|
10653
10674
|
const diffOps = computeWordLevelDiffOps(block.acceptedText, cleanText);
|
|
10654
10675
|
const model = applyPatches(splitRunsAtDiffBoundaries(block.runModel, diffOps), diffOps, {
|
|
10655
10676
|
generateRedlines,
|
|
@@ -10659,7 +10680,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
|
|
|
10659
10680
|
out += serializeToOoxml(model, block.pPr, formatHints, { author, generateRedlines, revisionIdAllocator });
|
|
10660
10681
|
});
|
|
10661
10682
|
for (let i2 = cell.blocks.length; i2 < lines.length; i2++) {
|
|
10662
|
-
const { cleanText, formatHints } =
|
|
10683
|
+
const { cleanText, formatHints } = cellMarkdown(lines[i2], options);
|
|
10663
10684
|
const runModel = [{
|
|
10664
10685
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10665
10686
|
text: cleanText,
|
|
@@ -10678,7 +10699,7 @@ function reconcileCellContent(cell, newText, options) {
|
|
|
10678
10699
|
return reconcileMultiBlockCellContent(cell, newText, options);
|
|
10679
10700
|
}
|
|
10680
10701
|
const { generateRedlines, author, revisionIdAllocator = null } = options;
|
|
10681
|
-
const { cleanText, formatHints } =
|
|
10702
|
+
const { cleanText, formatHints } = cellMarkdown(newText, options);
|
|
10682
10703
|
const oldText = cell.getText();
|
|
10683
10704
|
const diffOps = computeWordLevelDiffOps(oldText, cleanText);
|
|
10684
10705
|
const baseBlock = cell.blocks[0] || { runModel: [], pPr: null };
|
|
@@ -10724,7 +10745,7 @@ function parseTable(text2) {
|
|
|
10724
10745
|
}
|
|
10725
10746
|
const isSeparatorLine = (line) => {
|
|
10726
10747
|
const normalized = line.replace(/\s+/g, "");
|
|
10727
|
-
return /^\|:?-{3,}:?(\|:?-{3,}:?)
|
|
10748
|
+
return /^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(normalized);
|
|
10728
10749
|
};
|
|
10729
10750
|
const hasHeader = lines.some(isSeparatorLine);
|
|
10730
10751
|
const dataLines = lines.filter((line) => !isSeparatorLine(line));
|
|
@@ -11308,7 +11329,7 @@ var ReconciliationPipeline = class {
|
|
|
11308
11329
|
};
|
|
11309
11330
|
|
|
11310
11331
|
// pipeline/structured-content.js
|
|
11311
|
-
var TABLE_SEPARATOR = /^\s
|
|
11332
|
+
var TABLE_SEPARATOR = /^\s*(?:\|\s*:?-{3,}:?\s*\||\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?)\s*$/;
|
|
11312
11333
|
function isBlank(line) {
|
|
11313
11334
|
return !line || line.trim().length === 0;
|
|
11314
11335
|
}
|
|
@@ -14965,7 +14986,7 @@ function keepUnchangedTextOutsideWrappers(diffs, mapping) {
|
|
|
14965
14986
|
function noChanges(serializer, xmlDoc) {
|
|
14966
14987
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
14967
14988
|
}
|
|
14968
|
-
function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
|
|
14989
|
+
function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true, extra = {}) {
|
|
14969
14990
|
const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
14970
14991
|
const newTableData = parseTable(modifiedText);
|
|
14971
14992
|
const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
|
|
@@ -14981,7 +15002,8 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
14981
15002
|
const options = {
|
|
14982
15003
|
generateRedlines,
|
|
14983
15004
|
author,
|
|
14984
|
-
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
|
|
15005
|
+
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc),
|
|
15006
|
+
plainTextContent: extra.plainTextContent === true
|
|
14985
15007
|
};
|
|
14986
15008
|
const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
|
|
14987
15009
|
const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
|
|
@@ -15001,14 +15023,14 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
15001
15023
|
targetTable.parentNode.replaceChild(importedTable, targetTable);
|
|
15002
15024
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
|
|
15003
15025
|
}
|
|
15004
|
-
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
|
|
15026
|
+
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines, extra = {}) {
|
|
15005
15027
|
const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
|
|
15006
15028
|
const tableData = parseTable(modifiedText);
|
|
15007
15029
|
if (!tableData || tableData.rows.length === 0 && tableData.headers.length === 0) {
|
|
15008
15030
|
log("[OxmlEngine] Failed to parse table data from Markdown");
|
|
15009
15031
|
return noChanges(serializer, xmlDoc);
|
|
15010
15032
|
}
|
|
15011
|
-
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true });
|
|
15033
|
+
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true, plainTextContent: extra.plainTextContent === true });
|
|
15012
15034
|
const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, "application/xml").doc;
|
|
15013
15035
|
if (!tableDoc) return noChanges(serializer, xmlDoc);
|
|
15014
15036
|
const tableParseError = getXmlParseError(tableDoc);
|
|
@@ -15831,7 +15853,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15831
15853
|
const operationWarnings = [];
|
|
15832
15854
|
let normalizedExistingRevisions = false;
|
|
15833
15855
|
let preservedInsertedParagraphMark = null;
|
|
15834
|
-
|
|
15856
|
+
let existingRevisionsPolicy = options.existingRevisions || "merge-same-author";
|
|
15835
15857
|
const keepNormalizedNoOp = existingRevisionsPolicy === "accept-all-first-keep-normalized";
|
|
15836
15858
|
const finalize = (result) => {
|
|
15837
15859
|
const withStatus = { ...result };
|
|
@@ -15879,6 +15901,10 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15879
15901
|
const parsed = parseOoxmlSafe(inputOoxml, "text/xml");
|
|
15880
15902
|
parseWarnings = parsed.warnings;
|
|
15881
15903
|
let xmlDoc = parsed.doc;
|
|
15904
|
+
if (existingRevisionsPolicy === "auto") {
|
|
15905
|
+
existingRevisionsPolicy = resolveExistingRevisionsPolicy("auto", xmlDoc, author);
|
|
15906
|
+
options = { ...options, existingRevisions: existingRevisionsPolicy };
|
|
15907
|
+
}
|
|
15882
15908
|
const parseError = xmlDoc ? getXmlParseError(xmlDoc) : null;
|
|
15883
15909
|
if (parsed.error || parseError) {
|
|
15884
15910
|
const message = parsed.error?.message || parseError?.textContent || "Could not parse OOXML input.";
|
|
@@ -15913,7 +15939,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15913
15939
|
if (existingRevisionsPolicy === "merge-same-author" || existingRevisionsPolicy === "slice-cross-author") {
|
|
15914
15940
|
const authors = getTrackedChangeAuthors(xmlDoc);
|
|
15915
15941
|
const currentAuthor = String(author || "").trim().toLowerCase();
|
|
15916
|
-
const isSameAuthor = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === currentAuthor);
|
|
15942
|
+
const isSameAuthor = authors.length > 0 && !hasUnattributedTrackedChange(xmlDoc) && authors.every((a) => a.trim().toLowerCase() === currentAuthor);
|
|
15917
15943
|
if (isSameAuthor) {
|
|
15918
15944
|
const commentIds = getCommentIdsInOoxml(xmlDoc);
|
|
15919
15945
|
if (commentIds.length > 0) {
|
|
@@ -16070,7 +16096,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16070
16096
|
sanitizedText = structuredAnalysis.normalizedMarkdown;
|
|
16071
16097
|
}
|
|
16072
16098
|
}
|
|
16073
|
-
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
16099
|
+
const { cleanText: cleanModifiedText, formatHints } = options.plainTextContent === true ? { cleanText: sanitizedText, formatHints: [] } : preprocessMarkdown(sanitizedText);
|
|
16074
16100
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
16075
16101
|
const hasLiteralTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
16076
16102
|
const hasListStructureChange = !hasLiteralTextChanges && options.explicitStructuredContent === true && isListTargetLoose(cleanModifiedText) && !sourceMatchesListShape(paragraphs, cleanModifiedText);
|
|
@@ -16203,11 +16229,11 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16203
16229
|
if (isMarkdownTable && !hasTables) {
|
|
16204
16230
|
recordRouteSelection(options, "table", { transformation: "text-to-table" });
|
|
16205
16231
|
log("[OxmlEngine] Text-to-table transformation: generating new table from Markdown");
|
|
16206
|
-
return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
|
|
16232
|
+
return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
|
|
16207
16233
|
}
|
|
16208
16234
|
if (hasTables && isMarkdownTable) {
|
|
16209
16235
|
recordRouteSelection(options, "table", { transformation: "table-reconciliation" });
|
|
16210
|
-
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
|
|
16236
|
+
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
|
|
16211
16237
|
}
|
|
16212
16238
|
if (usesSurgicalTextMode) {
|
|
16213
16239
|
recordRouteSelection(options, "surgical", { tableScoped: hasTables });
|
|
@@ -19902,6 +19928,70 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
19902
19928
|
};
|
|
19903
19929
|
}
|
|
19904
19930
|
|
|
19931
|
+
// services/inspection-comparison.js
|
|
19932
|
+
var commentIdentity = (comment) => [comment.author ?? null, comment.text ?? "", comment.anchoredText || ""];
|
|
19933
|
+
function reviewSegments(segments = []) {
|
|
19934
|
+
return segments.filter((segment) => segment.kind !== "baseline").reduce((result, segment) => {
|
|
19935
|
+
const next = {
|
|
19936
|
+
kind: segment.kind,
|
|
19937
|
+
author: segment.author || null,
|
|
19938
|
+
text: segment.text,
|
|
19939
|
+
acceptedStart: segment.acceptedStart ?? null,
|
|
19940
|
+
rejectedStart: segment.rejectedStart ?? null
|
|
19941
|
+
};
|
|
19942
|
+
const previous = result.at(-1);
|
|
19943
|
+
const adjacent = (key) => previous[key] == null && next[key] == null || previous[key] != null && previous[key] + previous.text.length === next[key];
|
|
19944
|
+
if (previous && previous.kind === next.kind && previous.author === next.author && adjacent("acceptedStart") && adjacent("rejectedStart")) previous.text += next.text;
|
|
19945
|
+
else result.push(next);
|
|
19946
|
+
return result;
|
|
19947
|
+
}, []);
|
|
19948
|
+
}
|
|
19949
|
+
function inspectionReviewShape(inspection) {
|
|
19950
|
+
const comments = inspection?.comments || [];
|
|
19951
|
+
return {
|
|
19952
|
+
paragraphs: (inspection?.paragraphs || []).map((paragraph) => ({
|
|
19953
|
+
text: paragraph.exactText,
|
|
19954
|
+
inTable: paragraph.inTable === true,
|
|
19955
|
+
table: paragraph.table ? [paragraph.table.rowIndex, paragraph.table.cellIndex] : null,
|
|
19956
|
+
list: paragraph.list ? [paragraph.list.format, paragraph.list.level, paragraph.list.label] : null,
|
|
19957
|
+
review: {
|
|
19958
|
+
present: paragraph.hasRevisions === true,
|
|
19959
|
+
authors: [...paragraph.revisionAuthors || []].sort(),
|
|
19960
|
+
segments: reviewSegments(paragraph.segments)
|
|
19961
|
+
}
|
|
19962
|
+
})),
|
|
19963
|
+
comments: comments.map((comment) => {
|
|
19964
|
+
const parent = comment.parentCommentId != null ? comments.find((item) => String(item.id) === String(comment.parentCommentId)) : null;
|
|
19965
|
+
return [
|
|
19966
|
+
...commentIdentity(comment),
|
|
19967
|
+
comment.done === true,
|
|
19968
|
+
parent ? commentIdentity(parent) : comment.parentCommentId != null || comment.parentParaId ? "unresolved parent" : null
|
|
19969
|
+
];
|
|
19970
|
+
}).map((item) => JSON.stringify(item)).sort()
|
|
19971
|
+
};
|
|
19972
|
+
}
|
|
19973
|
+
function compareDocumentInspections(expected, actual) {
|
|
19974
|
+
const left = inspectionReviewShape(expected);
|
|
19975
|
+
const right = inspectionReviewShape(actual);
|
|
19976
|
+
const differences = [];
|
|
19977
|
+
const note = (path, a, b) => {
|
|
19978
|
+
if (differences.length < 10 && JSON.stringify(a) !== JSON.stringify(b)) differences.push({ path, expected: a, actual: b });
|
|
19979
|
+
};
|
|
19980
|
+
note("paragraphs.length", left.paragraphs.length, right.paragraphs.length);
|
|
19981
|
+
const count = Math.max(left.paragraphs.length, right.paragraphs.length);
|
|
19982
|
+
for (let index = 0; index < count; index++) {
|
|
19983
|
+
const a = left.paragraphs[index] ?? null;
|
|
19984
|
+
const b = right.paragraphs[index] ?? null;
|
|
19985
|
+
if (!a || !b) {
|
|
19986
|
+
note(`paragraphs[${index}]`, a, b);
|
|
19987
|
+
continue;
|
|
19988
|
+
}
|
|
19989
|
+
for (const key of ["text", "inTable", "table", "list", "review"]) note(`paragraphs[${index}].${key}`, a[key], b[key]);
|
|
19990
|
+
}
|
|
19991
|
+
note("comments", left.comments, right.comments);
|
|
19992
|
+
return { matches: differences.length === 0, differences };
|
|
19993
|
+
}
|
|
19994
|
+
|
|
19905
19995
|
// services/comment-locator.js
|
|
19906
19996
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
19907
19997
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -20376,7 +20466,7 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
|
|
|
20376
20466
|
extendedDoc = parsed.doc;
|
|
20377
20467
|
}
|
|
20378
20468
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
20379
|
-
if (documentXml) for (const match of String(documentXml).matchAll(
|
|
20469
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/\bw14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
20380
20470
|
const parentParagraph = commentThreadParagraph(parent);
|
|
20381
20471
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
20382
20472
|
let parentParaId = commentParaId(parent);
|
|
@@ -20439,232 +20529,6 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
|
|
|
20439
20529
|
};
|
|
20440
20530
|
}
|
|
20441
20531
|
|
|
20442
|
-
// engine/formatting-removal.js
|
|
20443
|
-
function removeNode2(node) {
|
|
20444
|
-
if (node?.parentNode) {
|
|
20445
|
-
node.parentNode.removeChild(node);
|
|
20446
|
-
}
|
|
20447
|
-
}
|
|
20448
|
-
function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
20449
|
-
if (!rPr) return null;
|
|
20450
|
-
const rPrClone = rPr.cloneNode(true);
|
|
20451
|
-
if (formatTypes.includes("all")) {
|
|
20452
|
-
const toRemove = [
|
|
20453
|
-
"w:b",
|
|
20454
|
-
"w:i",
|
|
20455
|
-
"w:u",
|
|
20456
|
-
"w:strike",
|
|
20457
|
-
"w:dstrike",
|
|
20458
|
-
"w:color",
|
|
20459
|
-
"w:sz",
|
|
20460
|
-
"w:szCs",
|
|
20461
|
-
"w:rFonts",
|
|
20462
|
-
"w:highlight",
|
|
20463
|
-
"w:vertAlign",
|
|
20464
|
-
"w:spacing",
|
|
20465
|
-
"w:w",
|
|
20466
|
-
"w:kern",
|
|
20467
|
-
"w:position"
|
|
20468
|
-
];
|
|
20469
|
-
toRemove.forEach((tag) => {
|
|
20470
|
-
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
20471
|
-
elements.forEach(removeNode2);
|
|
20472
|
-
});
|
|
20473
|
-
} else {
|
|
20474
|
-
const tagMap = {
|
|
20475
|
-
"bold": "w:b",
|
|
20476
|
-
"italic": "w:i",
|
|
20477
|
-
"underline": "w:u",
|
|
20478
|
-
"strikethrough": "w:strike",
|
|
20479
|
-
"doubleStrike": "w:dstrike",
|
|
20480
|
-
"color": "w:color",
|
|
20481
|
-
"highlight": "w:highlight",
|
|
20482
|
-
"fontSize": "w:sz",
|
|
20483
|
-
"fontSizeCs": "w:szCs",
|
|
20484
|
-
// Complex script font size
|
|
20485
|
-
"fontFamily": "w:rFonts",
|
|
20486
|
-
"superscript": "w:vertAlign",
|
|
20487
|
-
"subscript": "w:vertAlign"
|
|
20488
|
-
};
|
|
20489
|
-
formatTypes.forEach((type) => {
|
|
20490
|
-
const tag = tagMap[type];
|
|
20491
|
-
if (tag) {
|
|
20492
|
-
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
20493
|
-
elements.forEach(removeNode2);
|
|
20494
|
-
}
|
|
20495
|
-
});
|
|
20496
|
-
}
|
|
20497
|
-
return rPrClone.children.length > 0 ? rPrClone : null;
|
|
20498
|
-
}
|
|
20499
|
-
function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
20500
|
-
if (!targetText || !ooxmlString) return ooxmlString;
|
|
20501
|
-
const doc = parseOoxml(ooxmlString);
|
|
20502
|
-
if (!doc) return ooxmlString;
|
|
20503
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20504
|
-
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
20505
|
-
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
20506
|
-
const allRuns = [...Array.from(runs)];
|
|
20507
|
-
for (const ins of insertions) {
|
|
20508
|
-
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
20509
|
-
allRuns.push(...Array.from(insideRuns));
|
|
20510
|
-
}
|
|
20511
|
-
for (const run of allRuns) {
|
|
20512
|
-
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
20513
|
-
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
20514
|
-
if (runText.includes(targetText) || runText === targetText) {
|
|
20515
|
-
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
20516
|
-
if (rPrElements.length > 0) {
|
|
20517
|
-
const rPr = rPrElements[0];
|
|
20518
|
-
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
20519
|
-
if (newRPr) {
|
|
20520
|
-
if (rPr.parentNode) {
|
|
20521
|
-
rPr.parentNode.replaceChild(newRPr, rPr);
|
|
20522
|
-
}
|
|
20523
|
-
} else {
|
|
20524
|
-
removeNode2(rPr);
|
|
20525
|
-
}
|
|
20526
|
-
}
|
|
20527
|
-
}
|
|
20528
|
-
}
|
|
20529
|
-
return serializeOoxml(doc);
|
|
20530
|
-
}
|
|
20531
|
-
var HIGHLIGHT_COLOR_MAP = {
|
|
20532
|
-
"yellow": "yellow",
|
|
20533
|
-
"green": "green",
|
|
20534
|
-
"cyan": "cyan",
|
|
20535
|
-
"magenta": "magenta",
|
|
20536
|
-
"blue": "blue",
|
|
20537
|
-
"red": "red",
|
|
20538
|
-
"darkblue": "darkBlue",
|
|
20539
|
-
"darkcyan": "darkCyan",
|
|
20540
|
-
"darkgreen": "darkGreen",
|
|
20541
|
-
"darkmagenta": "darkMagenta",
|
|
20542
|
-
"darkred": "darkRed",
|
|
20543
|
-
"darkyellow": "darkYellow",
|
|
20544
|
-
"gray25": "lightGray",
|
|
20545
|
-
"gray50": "darkGray",
|
|
20546
|
-
"black": "black",
|
|
20547
|
-
"white": "white"
|
|
20548
|
-
};
|
|
20549
|
-
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
20550
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20551
|
-
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
20552
|
-
const generateRedlines = options?.generateRedlines ?? false;
|
|
20553
|
-
const author = options?.author || getDefaultAuthor();
|
|
20554
|
-
let rPrElement = rPr;
|
|
20555
|
-
if (!rPrElement) {
|
|
20556
|
-
rPrElement = createWordElement(doc, "w:rPr");
|
|
20557
|
-
} else {
|
|
20558
|
-
rPrElement = rPr.cloneNode(true);
|
|
20559
|
-
}
|
|
20560
|
-
let previousRPrState = null;
|
|
20561
|
-
if (generateRedlines) {
|
|
20562
|
-
previousRPrState = createWordElement(doc, "w:rPr");
|
|
20563
|
-
Array.from(rPrElement.childNodes).forEach((child) => {
|
|
20564
|
-
if (child.nodeName !== "w:rPrChange") {
|
|
20565
|
-
previousRPrState.appendChild(child.cloneNode(true));
|
|
20566
|
-
}
|
|
20567
|
-
});
|
|
20568
|
-
}
|
|
20569
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
20570
|
-
Array.from(existingHighlight).forEach(removeNode2);
|
|
20571
|
-
const highlightEl = createWordElement(doc, "w:highlight");
|
|
20572
|
-
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
20573
|
-
rPrElement.appendChild(highlightEl);
|
|
20574
|
-
if (generateRedlines && previousRPrState) {
|
|
20575
|
-
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
20576
|
-
const metadata = createRevisionMetadata(author, doc);
|
|
20577
|
-
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
20578
|
-
rPrChange.setAttribute("w:author", metadata.author);
|
|
20579
|
-
rPrChange.setAttribute("w:date", metadata.date);
|
|
20580
|
-
rPrChange.appendChild(previousRPrState);
|
|
20581
|
-
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
20582
|
-
Array.from(existingChange).forEach(removeNode2);
|
|
20583
|
-
rPrElement.appendChild(rPrChange);
|
|
20584
|
-
}
|
|
20585
|
-
return rPrElement;
|
|
20586
|
-
}
|
|
20587
|
-
function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
|
|
20588
|
-
if (!targetText || !ooxmlString) return ooxmlString;
|
|
20589
|
-
const doc = parseOoxml(ooxmlString);
|
|
20590
|
-
if (!doc) return ooxmlString;
|
|
20591
|
-
let revisionIdAllocator;
|
|
20592
|
-
if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
|
|
20593
|
-
revisionIdAllocator = options._revisionIdAllocator;
|
|
20594
|
-
seedRevisionIdsFromDocument(doc, revisionIdAllocator);
|
|
20595
|
-
} else {
|
|
20596
|
-
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
20597
|
-
}
|
|
20598
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20599
|
-
const getRunText = (run) => {
|
|
20600
|
-
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
20601
|
-
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
20602
|
-
};
|
|
20603
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
20604
|
-
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
20605
|
-
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
20606
|
-
const clonedRun = sourceRun.cloneNode(true);
|
|
20607
|
-
if (sourceRunsWithClaimedRevisionIds.has(sourceRun)) {
|
|
20608
|
-
refreshRunPropertyChangeIds(clonedRun, revisionIdAllocator);
|
|
20609
|
-
} else {
|
|
20610
|
-
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
20611
|
-
}
|
|
20612
|
-
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
20613
|
-
Array.from(textNodes).forEach(removeNode2);
|
|
20614
|
-
const newText = createWordElement(doc, "w:t");
|
|
20615
|
-
newText.setAttribute("xml:space", "preserve");
|
|
20616
|
-
newText.textContent = text2;
|
|
20617
|
-
clonedRun.appendChild(newText);
|
|
20618
|
-
if (shouldHighlight) {
|
|
20619
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
20620
|
-
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
20621
|
-
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
20622
|
-
if (existingRPr) {
|
|
20623
|
-
clonedRun.replaceChild(newRPr, existingRPr);
|
|
20624
|
-
} else {
|
|
20625
|
-
clonedRun.insertBefore(newRPr, clonedRun.firstChild);
|
|
20626
|
-
}
|
|
20627
|
-
}
|
|
20628
|
-
return clonedRun;
|
|
20629
|
-
};
|
|
20630
|
-
for (const run of allRuns) {
|
|
20631
|
-
const runText = getRunText(run);
|
|
20632
|
-
if (!runText) continue;
|
|
20633
|
-
const matchIndexes = [];
|
|
20634
|
-
let searchOffset = 0;
|
|
20635
|
-
while (searchOffset <= runText.length - targetText.length) {
|
|
20636
|
-
const matchIndex = runText.indexOf(targetText, searchOffset);
|
|
20637
|
-
if (matchIndex === -1) break;
|
|
20638
|
-
matchIndexes.push(matchIndex);
|
|
20639
|
-
searchOffset = matchIndex + targetText.length;
|
|
20640
|
-
}
|
|
20641
|
-
if (matchIndexes.length === 0) continue;
|
|
20642
|
-
const parent = run.parentNode;
|
|
20643
|
-
if (!parent) {
|
|
20644
|
-
console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
|
|
20645
|
-
continue;
|
|
20646
|
-
}
|
|
20647
|
-
const fragment = doc.createDocumentFragment();
|
|
20648
|
-
let cursor = 0;
|
|
20649
|
-
for (const matchIndex of matchIndexes) {
|
|
20650
|
-
if (matchIndex > cursor) {
|
|
20651
|
-
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
|
|
20652
|
-
}
|
|
20653
|
-
fragment.appendChild(cloneRunWithText2(
|
|
20654
|
-
run,
|
|
20655
|
-
runText.slice(matchIndex, matchIndex + targetText.length),
|
|
20656
|
-
true
|
|
20657
|
-
));
|
|
20658
|
-
cursor = matchIndex + targetText.length;
|
|
20659
|
-
}
|
|
20660
|
-
if (cursor < runText.length) {
|
|
20661
|
-
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
|
|
20662
|
-
}
|
|
20663
|
-
parent.replaceChild(fragment, run);
|
|
20664
|
-
}
|
|
20665
|
-
return serializeOoxml(doc);
|
|
20666
|
-
}
|
|
20667
|
-
|
|
20668
20532
|
// services/package-parts.js
|
|
20669
20533
|
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
20670
20534
|
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
@@ -20952,31 +20816,34 @@ async function ensureNumberingArtifactsInZip(zip, numberingXmlList, options = {}
|
|
|
20952
20816
|
}
|
|
20953
20817
|
}
|
|
20954
20818
|
}
|
|
20819
|
+
function mergeCommentsPartXml(existingXml, commentsXml, options = {}) {
|
|
20820
|
+
if (!existingXml || options.replaceExisting === true) return commentsXml;
|
|
20821
|
+
const existingDoc = parseXmlStrictStandalone(existingXml, "word/comments.xml (existing)");
|
|
20822
|
+
const incomingDoc = parseXmlStrictStandalone(commentsXml, "word/comments.xml (incoming)");
|
|
20823
|
+
const incomingComments = Array.from(incomingDoc.documentElement.getElementsByTagNameNS(NS_W7, "comment"));
|
|
20824
|
+
if (incomingComments.length === 0) return existingXml;
|
|
20825
|
+
const existingRoot = existingDoc.documentElement;
|
|
20826
|
+
const usedIds = new Set(
|
|
20827
|
+
Array.from(existingRoot.getElementsByTagNameNS(NS_W7, "comment")).map((comment) => comment.getAttribute("w:id") || comment.getAttribute("id")).filter(Boolean)
|
|
20828
|
+
);
|
|
20829
|
+
for (const incomingComment of incomingComments) {
|
|
20830
|
+
const id = incomingComment.getAttribute("w:id") || incomingComment.getAttribute("id");
|
|
20831
|
+
if (id && usedIds.has(id)) {
|
|
20832
|
+
throw Object.assign(new Error(`Duplicate comment id: ${id}`), { code: "DUPLICATE_COMMENT_ID", commentId: id });
|
|
20833
|
+
}
|
|
20834
|
+
if (id) usedIds.add(id);
|
|
20835
|
+
existingRoot.appendChild(existingDoc.importNode(incomingComment, true));
|
|
20836
|
+
}
|
|
20837
|
+
return createSerializer().serializeToString(existingDoc);
|
|
20838
|
+
}
|
|
20955
20839
|
async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
|
|
20956
20840
|
const onInfo = typeof options?.onInfo === "function" ? options.onInfo : () => {
|
|
20957
20841
|
};
|
|
20958
20842
|
if (!commentsXml) return;
|
|
20959
20843
|
const serializer = createSerializer();
|
|
20960
20844
|
const existingText = await readZipText(zip, COMMENTS_PATH);
|
|
20961
|
-
if (!existingText || options.replaceExisting === true)
|
|
20962
|
-
|
|
20963
|
-
zip.file(COMMENTS_PATH, commentsXml);
|
|
20964
|
-
} else {
|
|
20965
|
-
const existingDoc = parseXmlStrictStandalone(existingText, "word/comments.xml (existing)");
|
|
20966
|
-
const incomingDoc = parseXmlStrictStandalone(commentsXml, "word/comments.xml (incoming)");
|
|
20967
|
-
const existingRoot = existingDoc.documentElement;
|
|
20968
|
-
const existingIds = new Set(
|
|
20969
|
-
Array.from(existingRoot.getElementsByTagNameNS(NS_W7, "comment")).map((comment) => comment.getAttribute("w:id") || comment.getAttribute("id")).filter(Boolean)
|
|
20970
|
-
);
|
|
20971
|
-
for (const incomingComment of Array.from(incomingDoc.documentElement.getElementsByTagNameNS(NS_W7, "comment"))) {
|
|
20972
|
-
const id = incomingComment.getAttribute("w:id") || incomingComment.getAttribute("id");
|
|
20973
|
-
if (id && existingIds.has(id)) {
|
|
20974
|
-
throw new Error(`Duplicate comment id: ${id}`);
|
|
20975
|
-
}
|
|
20976
|
-
existingRoot.appendChild(existingDoc.importNode(incomingComment, true));
|
|
20977
|
-
}
|
|
20978
|
-
zip.file(COMMENTS_PATH, serializer.serializeToString(existingDoc));
|
|
20979
|
-
}
|
|
20845
|
+
if (!existingText || options.replaceExisting === true) onInfo("[Demo] Adding comments.xml");
|
|
20846
|
+
zip.file(COMMENTS_PATH, mergeCommentsPartXml(existingText, commentsXml, options));
|
|
20980
20847
|
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
20981
20848
|
if (ctText) {
|
|
20982
20849
|
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
@@ -21212,6 +21079,318 @@ async function validateDocxPackage(zip) {
|
|
|
21212
21079
|
}
|
|
21213
21080
|
}
|
|
21214
21081
|
|
|
21082
|
+
// services/comment-parts-merge.js
|
|
21083
|
+
var PART_KEYS = Object.freeze({
|
|
21084
|
+
comments: "commentsXml",
|
|
21085
|
+
commentsExtended: "commentsExtendedXml",
|
|
21086
|
+
commentsIds: "commentsIdsXml",
|
|
21087
|
+
commentsExtensible: "commentsExtensibleXml"
|
|
21088
|
+
});
|
|
21089
|
+
var partsOf = (parts) => Object.fromEntries(Object.values(PART_KEYS).map((key) => [key, parts?.[key] || null]));
|
|
21090
|
+
function mergeCommentParts(parts = {}, output = {}) {
|
|
21091
|
+
const before = partsOf(parts);
|
|
21092
|
+
const after = { ...before };
|
|
21093
|
+
const fail = (error2) => ({ status: "error", hasChanges: false, parts: before, changedParts: [], addedParts: [], error: error2 });
|
|
21094
|
+
const commentsMode = output?.commentsXmlMode === "replace" ? "replace" : "merge";
|
|
21095
|
+
const extendedMode = output?.commentsExtendedXmlMode === "replace" ? "replace" : "merge";
|
|
21096
|
+
for (const [label, value] of [
|
|
21097
|
+
...Object.values(PART_KEYS).map((key) => [`parts.${key}`, before[key]]),
|
|
21098
|
+
["output.commentsXml", output?.commentsXml],
|
|
21099
|
+
["output.commentsExtendedXml", output?.commentsExtendedXml]
|
|
21100
|
+
]) {
|
|
21101
|
+
if (value != null && typeof value !== "string") {
|
|
21102
|
+
return fail({ code: "INVALID_COMMENT_PARTS", message: `${label} must be an XML string, null or omitted (got ${typeof value}).` });
|
|
21103
|
+
}
|
|
21104
|
+
}
|
|
21105
|
+
const requirePart = (xml, part, rootName, label) => {
|
|
21106
|
+
let doc;
|
|
21107
|
+
try {
|
|
21108
|
+
doc = parseXmlStrictStandalone(xml, label);
|
|
21109
|
+
} catch (error2) {
|
|
21110
|
+
throw Object.assign(error2, { part });
|
|
21111
|
+
}
|
|
21112
|
+
if (doc.documentElement?.localName !== rootName) {
|
|
21113
|
+
throw Object.assign(new Error(`${label}: expected a <${rootName}> root element, found <${doc.documentElement?.localName || "none"}>.`), { code: "INVALID_COMMENT_PARTS", part });
|
|
21114
|
+
}
|
|
21115
|
+
return doc;
|
|
21116
|
+
};
|
|
21117
|
+
try {
|
|
21118
|
+
if (output?.commentsXml) {
|
|
21119
|
+
const incomingDoc = requirePart(output.commentsXml, "comments", "comments", "word/comments.xml (incoming)");
|
|
21120
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21121
|
+
for (const node of Array.from(incomingDoc.getElementsByTagNameNS("*", "comment"))) {
|
|
21122
|
+
const id = node.getAttribute("w:id") || node.getAttribute("id");
|
|
21123
|
+
if (!id) continue;
|
|
21124
|
+
if (seen.has(id)) {
|
|
21125
|
+
throw Object.assign(new Error(`Duplicate comment id: ${id}`), { code: "DUPLICATE_COMMENT_ID", commentId: id, part: "comments" });
|
|
21126
|
+
}
|
|
21127
|
+
seen.add(id);
|
|
21128
|
+
}
|
|
21129
|
+
if (before.commentsXml) requirePart(before.commentsXml, "comments", "comments", "word/comments.xml (existing)");
|
|
21130
|
+
if (before.commentsIdsXml) requirePart(before.commentsIdsXml, "commentsIds", "commentsIds", "word/commentsIds.xml (existing)");
|
|
21131
|
+
if (before.commentsExtensibleXml) requirePart(before.commentsExtensibleXml, "commentsExtensible", "commentsExtensible", "word/commentsExtensible.xml (existing)");
|
|
21132
|
+
after.commentsXml = mergeCommentsPartXml(before.commentsXml, output.commentsXml, { replaceExisting: commentsMode === "replace" });
|
|
21133
|
+
Object.assign(after, reconcileCommentSiblingParts({
|
|
21134
|
+
commentsXml: after.commentsXml,
|
|
21135
|
+
commentsIdsXml: after.commentsIdsXml,
|
|
21136
|
+
commentsExtensibleXml: after.commentsExtensibleXml
|
|
21137
|
+
}));
|
|
21138
|
+
}
|
|
21139
|
+
if (output?.commentsExtendedXml) {
|
|
21140
|
+
if (extendedMode !== "replace" && before.commentsExtendedXml) {
|
|
21141
|
+
return fail({
|
|
21142
|
+
code: "UNSUPPORTED_EXISTING_COMMENT_THREAD_METADATA",
|
|
21143
|
+
part: "commentsExtended",
|
|
21144
|
+
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.'
|
|
21145
|
+
});
|
|
21146
|
+
}
|
|
21147
|
+
requirePart(output.commentsExtendedXml, "commentsExtended", "commentsEx", "word/commentsExtended.xml (incoming)");
|
|
21148
|
+
after.commentsExtendedXml = output.commentsExtendedXml;
|
|
21149
|
+
}
|
|
21150
|
+
} catch (error2) {
|
|
21151
|
+
return fail({
|
|
21152
|
+
code: error2?.code || "COMMENT_PARTS_MERGE_FAILED",
|
|
21153
|
+
message: error2?.message || String(error2),
|
|
21154
|
+
...error2?.part || error2?.code === "DUPLICATE_COMMENT_ID" ? { part: error2.part || "comments" } : {},
|
|
21155
|
+
...error2?.commentId ? { commentId: error2.commentId } : {}
|
|
21156
|
+
});
|
|
21157
|
+
}
|
|
21158
|
+
const changedParts = [];
|
|
21159
|
+
const addedParts = [];
|
|
21160
|
+
for (const [name, key] of Object.entries(PART_KEYS)) {
|
|
21161
|
+
if (after[key] === before[key]) continue;
|
|
21162
|
+
changedParts.push(name);
|
|
21163
|
+
if (!before[key]) addedParts.push(name);
|
|
21164
|
+
}
|
|
21165
|
+
return { status: "ok", hasChanges: changedParts.length > 0, parts: after, changedParts, addedParts };
|
|
21166
|
+
}
|
|
21167
|
+
|
|
21168
|
+
// engine/formatting-removal.js
|
|
21169
|
+
function removeNode2(node) {
|
|
21170
|
+
if (node?.parentNode) {
|
|
21171
|
+
node.parentNode.removeChild(node);
|
|
21172
|
+
}
|
|
21173
|
+
}
|
|
21174
|
+
function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
21175
|
+
if (!rPr) return null;
|
|
21176
|
+
const rPrClone = rPr.cloneNode(true);
|
|
21177
|
+
if (formatTypes.includes("all")) {
|
|
21178
|
+
const toRemove = [
|
|
21179
|
+
"w:b",
|
|
21180
|
+
"w:i",
|
|
21181
|
+
"w:u",
|
|
21182
|
+
"w:strike",
|
|
21183
|
+
"w:dstrike",
|
|
21184
|
+
"w:color",
|
|
21185
|
+
"w:sz",
|
|
21186
|
+
"w:szCs",
|
|
21187
|
+
"w:rFonts",
|
|
21188
|
+
"w:highlight",
|
|
21189
|
+
"w:vertAlign",
|
|
21190
|
+
"w:spacing",
|
|
21191
|
+
"w:w",
|
|
21192
|
+
"w:kern",
|
|
21193
|
+
"w:position"
|
|
21194
|
+
];
|
|
21195
|
+
toRemove.forEach((tag) => {
|
|
21196
|
+
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
21197
|
+
elements.forEach(removeNode2);
|
|
21198
|
+
});
|
|
21199
|
+
} else {
|
|
21200
|
+
const tagMap = {
|
|
21201
|
+
"bold": "w:b",
|
|
21202
|
+
"italic": "w:i",
|
|
21203
|
+
"underline": "w:u",
|
|
21204
|
+
"strikethrough": "w:strike",
|
|
21205
|
+
"doubleStrike": "w:dstrike",
|
|
21206
|
+
"color": "w:color",
|
|
21207
|
+
"highlight": "w:highlight",
|
|
21208
|
+
"fontSize": "w:sz",
|
|
21209
|
+
"fontSizeCs": "w:szCs",
|
|
21210
|
+
// Complex script font size
|
|
21211
|
+
"fontFamily": "w:rFonts",
|
|
21212
|
+
"superscript": "w:vertAlign",
|
|
21213
|
+
"subscript": "w:vertAlign"
|
|
21214
|
+
};
|
|
21215
|
+
formatTypes.forEach((type) => {
|
|
21216
|
+
const tag = tagMap[type];
|
|
21217
|
+
if (tag) {
|
|
21218
|
+
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
21219
|
+
elements.forEach(removeNode2);
|
|
21220
|
+
}
|
|
21221
|
+
});
|
|
21222
|
+
}
|
|
21223
|
+
return rPrClone.children.length > 0 ? rPrClone : null;
|
|
21224
|
+
}
|
|
21225
|
+
function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
21226
|
+
if (!targetText || !ooxmlString) return ooxmlString;
|
|
21227
|
+
const doc = parseOoxml(ooxmlString);
|
|
21228
|
+
if (!doc) return ooxmlString;
|
|
21229
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
21230
|
+
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
21231
|
+
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
21232
|
+
const allRuns = [...Array.from(runs)];
|
|
21233
|
+
for (const ins of insertions) {
|
|
21234
|
+
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
21235
|
+
allRuns.push(...Array.from(insideRuns));
|
|
21236
|
+
}
|
|
21237
|
+
for (const run of allRuns) {
|
|
21238
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
21239
|
+
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
21240
|
+
if (runText.includes(targetText) || runText === targetText) {
|
|
21241
|
+
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
21242
|
+
if (rPrElements.length > 0) {
|
|
21243
|
+
const rPr = rPrElements[0];
|
|
21244
|
+
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
21245
|
+
if (newRPr) {
|
|
21246
|
+
if (rPr.parentNode) {
|
|
21247
|
+
rPr.parentNode.replaceChild(newRPr, rPr);
|
|
21248
|
+
}
|
|
21249
|
+
} else {
|
|
21250
|
+
removeNode2(rPr);
|
|
21251
|
+
}
|
|
21252
|
+
}
|
|
21253
|
+
}
|
|
21254
|
+
}
|
|
21255
|
+
return serializeOoxml(doc);
|
|
21256
|
+
}
|
|
21257
|
+
var HIGHLIGHT_COLOR_MAP = {
|
|
21258
|
+
"yellow": "yellow",
|
|
21259
|
+
"green": "green",
|
|
21260
|
+
"cyan": "cyan",
|
|
21261
|
+
"magenta": "magenta",
|
|
21262
|
+
"blue": "blue",
|
|
21263
|
+
"red": "red",
|
|
21264
|
+
"darkblue": "darkBlue",
|
|
21265
|
+
"darkcyan": "darkCyan",
|
|
21266
|
+
"darkgreen": "darkGreen",
|
|
21267
|
+
"darkmagenta": "darkMagenta",
|
|
21268
|
+
"darkred": "darkRed",
|
|
21269
|
+
"darkyellow": "darkYellow",
|
|
21270
|
+
"gray25": "lightGray",
|
|
21271
|
+
"gray50": "darkGray",
|
|
21272
|
+
"black": "black",
|
|
21273
|
+
"white": "white"
|
|
21274
|
+
};
|
|
21275
|
+
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
21276
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
21277
|
+
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
21278
|
+
const generateRedlines = options?.generateRedlines ?? false;
|
|
21279
|
+
const author = options?.author || getDefaultAuthor();
|
|
21280
|
+
let rPrElement = rPr;
|
|
21281
|
+
if (!rPrElement) {
|
|
21282
|
+
rPrElement = createWordElement(doc, "w:rPr");
|
|
21283
|
+
} else {
|
|
21284
|
+
rPrElement = rPr.cloneNode(true);
|
|
21285
|
+
}
|
|
21286
|
+
let previousRPrState = null;
|
|
21287
|
+
if (generateRedlines) {
|
|
21288
|
+
previousRPrState = createWordElement(doc, "w:rPr");
|
|
21289
|
+
Array.from(rPrElement.childNodes).forEach((child) => {
|
|
21290
|
+
if (child.nodeName !== "w:rPrChange") {
|
|
21291
|
+
previousRPrState.appendChild(child.cloneNode(true));
|
|
21292
|
+
}
|
|
21293
|
+
});
|
|
21294
|
+
}
|
|
21295
|
+
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
21296
|
+
Array.from(existingHighlight).forEach(removeNode2);
|
|
21297
|
+
const highlightEl = createWordElement(doc, "w:highlight");
|
|
21298
|
+
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
21299
|
+
rPrElement.appendChild(highlightEl);
|
|
21300
|
+
if (generateRedlines && previousRPrState) {
|
|
21301
|
+
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
21302
|
+
const metadata = createRevisionMetadata(author, doc);
|
|
21303
|
+
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
21304
|
+
rPrChange.setAttribute("w:author", metadata.author);
|
|
21305
|
+
rPrChange.setAttribute("w:date", metadata.date);
|
|
21306
|
+
rPrChange.appendChild(previousRPrState);
|
|
21307
|
+
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
21308
|
+
Array.from(existingChange).forEach(removeNode2);
|
|
21309
|
+
rPrElement.appendChild(rPrChange);
|
|
21310
|
+
}
|
|
21311
|
+
return rPrElement;
|
|
21312
|
+
}
|
|
21313
|
+
function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
|
|
21314
|
+
if (!targetText || !ooxmlString) return ooxmlString;
|
|
21315
|
+
const doc = parseOoxml(ooxmlString);
|
|
21316
|
+
if (!doc) return ooxmlString;
|
|
21317
|
+
let revisionIdAllocator;
|
|
21318
|
+
if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
|
|
21319
|
+
revisionIdAllocator = options._revisionIdAllocator;
|
|
21320
|
+
seedRevisionIdsFromDocument(doc, revisionIdAllocator);
|
|
21321
|
+
} else {
|
|
21322
|
+
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
21323
|
+
}
|
|
21324
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
21325
|
+
const getRunText = (run) => {
|
|
21326
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
21327
|
+
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
21328
|
+
};
|
|
21329
|
+
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
21330
|
+
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
21331
|
+
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
21332
|
+
const clonedRun = sourceRun.cloneNode(true);
|
|
21333
|
+
if (sourceRunsWithClaimedRevisionIds.has(sourceRun)) {
|
|
21334
|
+
refreshRunPropertyChangeIds(clonedRun, revisionIdAllocator);
|
|
21335
|
+
} else {
|
|
21336
|
+
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
21337
|
+
}
|
|
21338
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
21339
|
+
Array.from(textNodes).forEach(removeNode2);
|
|
21340
|
+
const newText = createWordElement(doc, "w:t");
|
|
21341
|
+
newText.setAttribute("xml:space", "preserve");
|
|
21342
|
+
newText.textContent = text2;
|
|
21343
|
+
clonedRun.appendChild(newText);
|
|
21344
|
+
if (shouldHighlight) {
|
|
21345
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
21346
|
+
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
21347
|
+
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
21348
|
+
if (existingRPr) {
|
|
21349
|
+
clonedRun.replaceChild(newRPr, existingRPr);
|
|
21350
|
+
} else {
|
|
21351
|
+
clonedRun.insertBefore(newRPr, clonedRun.firstChild);
|
|
21352
|
+
}
|
|
21353
|
+
}
|
|
21354
|
+
return clonedRun;
|
|
21355
|
+
};
|
|
21356
|
+
for (const run of allRuns) {
|
|
21357
|
+
const runText = getRunText(run);
|
|
21358
|
+
if (!runText) continue;
|
|
21359
|
+
const matchIndexes = [];
|
|
21360
|
+
let searchOffset = 0;
|
|
21361
|
+
while (searchOffset <= runText.length - targetText.length) {
|
|
21362
|
+
const matchIndex = runText.indexOf(targetText, searchOffset);
|
|
21363
|
+
if (matchIndex === -1) break;
|
|
21364
|
+
matchIndexes.push(matchIndex);
|
|
21365
|
+
searchOffset = matchIndex + targetText.length;
|
|
21366
|
+
}
|
|
21367
|
+
if (matchIndexes.length === 0) continue;
|
|
21368
|
+
const parent = run.parentNode;
|
|
21369
|
+
if (!parent) {
|
|
21370
|
+
console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
|
|
21371
|
+
continue;
|
|
21372
|
+
}
|
|
21373
|
+
const fragment = doc.createDocumentFragment();
|
|
21374
|
+
let cursor = 0;
|
|
21375
|
+
for (const matchIndex of matchIndexes) {
|
|
21376
|
+
if (matchIndex > cursor) {
|
|
21377
|
+
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
|
|
21378
|
+
}
|
|
21379
|
+
fragment.appendChild(cloneRunWithText2(
|
|
21380
|
+
run,
|
|
21381
|
+
runText.slice(matchIndex, matchIndex + targetText.length),
|
|
21382
|
+
true
|
|
21383
|
+
));
|
|
21384
|
+
cursor = matchIndex + targetText.length;
|
|
21385
|
+
}
|
|
21386
|
+
if (cursor < runText.length) {
|
|
21387
|
+
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
|
|
21388
|
+
}
|
|
21389
|
+
parent.replaceChild(fragment, run);
|
|
21390
|
+
}
|
|
21391
|
+
return serializeOoxml(doc);
|
|
21392
|
+
}
|
|
21393
|
+
|
|
21215
21394
|
// orchestration/route-plan.js
|
|
21216
21395
|
var RoutePlanKind = Object.freeze({
|
|
21217
21396
|
STRUCTURED_LIST_DIRECT: "structured_list_direct",
|
|
@@ -22678,6 +22857,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
22678
22857
|
"highlight"
|
|
22679
22858
|
]);
|
|
22680
22859
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
22860
|
+
"auto",
|
|
22681
22861
|
"merge-same-author",
|
|
22682
22862
|
"slice-cross-author",
|
|
22683
22863
|
"reject-input",
|
|
@@ -24915,7 +25095,7 @@ function deriveParagraphThenTableAppend(modifiedText) {
|
|
|
24915
25095
|
const tableLines = lines.slice(1);
|
|
24916
25096
|
if (!tableLines.every((line) => line.trim().startsWith("|"))) return null;
|
|
24917
25097
|
const separator = tableLines[1].replace(/\s+/g, "");
|
|
24918
|
-
if (!/^\|:?-{3,}:?(\|:?-{3,}:?)
|
|
25098
|
+
if (!/^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(separator)) return null;
|
|
24919
25099
|
return { paragraphMarkdown: lines[0].trim(), tableText: tableLines.join("\n") };
|
|
24920
25100
|
}
|
|
24921
25101
|
|
|
@@ -25545,7 +25725,10 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
25545
25725
|
String(text2 || ""),
|
|
25546
25726
|
{
|
|
25547
25727
|
author,
|
|
25548
|
-
generateRedlines: false
|
|
25728
|
+
generateRedlines: false,
|
|
25729
|
+
// A caller's structuredContent: false keeps inserted paragraphs plain too: no headings,
|
|
25730
|
+
// and inline Markdown markers stay literal.
|
|
25731
|
+
...options.plainTextContent === true ? { structuredContent: false, plainTextContent: true } : {}
|
|
25549
25732
|
}
|
|
25550
25733
|
);
|
|
25551
25734
|
let sourceParagraph = null;
|
|
@@ -26732,13 +26915,17 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
26732
26915
|
}
|
|
26733
26916
|
};
|
|
26734
26917
|
}
|
|
26918
|
+
const autoExistingRevisions = options.existingRevisions === "auto";
|
|
26919
|
+
if (autoExistingRevisions) {
|
|
26920
|
+
options = { ...options, existingRevisions: resolveExistingRevisionsPolicy("auto", targetParagraph, author) };
|
|
26921
|
+
}
|
|
26735
26922
|
const hasRevisions = containsTrackedChanges(targetParagraph);
|
|
26736
26923
|
const existingPolicy = options.existingRevisions || "merge-same-author";
|
|
26737
26924
|
if (hasRevisions && existingPolicy !== "accept-all-first" && existingPolicy !== "accept-all-first-keep-normalized") {
|
|
26738
26925
|
if (existingPolicy === "merge-same-author" || existingPolicy === "slice-cross-author") {
|
|
26739
26926
|
const authors = getTrackedChangeAuthors(targetParagraph);
|
|
26740
26927
|
const opAuthor = String(author || "").trim().toLowerCase();
|
|
26741
|
-
const allSame = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
26928
|
+
const allSame = authors.length > 0 && !hasUnattributedTrackedChange(targetParagraph) && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
26742
26929
|
if (!allSame && existingPolicy === "merge-same-author") {
|
|
26743
26930
|
return {
|
|
26744
26931
|
documentXml,
|
|
@@ -26747,7 +26934,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
26747
26934
|
status: "error",
|
|
26748
26935
|
error: {
|
|
26749
26936
|
code: "EXISTING_REVISIONS",
|
|
26750
|
-
message: `Target paragraph contains tracked changes from another author (${authors
|
|
26937
|
+
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.`,
|
|
26751
26938
|
revisionAuthors: authors,
|
|
26752
26939
|
currentPolicy: existingPolicy
|
|
26753
26940
|
}
|
|
@@ -26889,6 +27076,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
26889
27076
|
onWarn
|
|
26890
27077
|
}) : typeof targetText === "string" && /\r?\n/.test(targetText) ? findReconstructionParagraphRange(xmlDoc, targetText) : null;
|
|
26891
27078
|
const hasExplicitRangeScope = Array.isArray(explicitRangeParagraphs) && explicitRangeParagraphs.length > 0;
|
|
27079
|
+
if (autoExistingRevisions && hasExplicitRangeScope) {
|
|
27080
|
+
options = { ...options, existingRevisions: resolveExistingRevisionsPolicy("auto", explicitRangeParagraphs, author) };
|
|
27081
|
+
}
|
|
26892
27082
|
if (!useTableScope && hasExplicitRangeScope) {
|
|
26893
27083
|
const insertionEntries = buildExplicitRangeInsertionEntries(explicitRangeParagraphs, effectiveModifiedText);
|
|
26894
27084
|
if (insertionEntries && insertionEntries.length > 0) {
|
|
@@ -27064,7 +27254,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27064
27254
|
author,
|
|
27065
27255
|
// A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
|
|
27066
27256
|
// Reject All removes the text but leaves an empty paragraph behind.
|
|
27067
|
-
{ generateRedlines, trackParagraphMark: true }
|
|
27257
|
+
{ generateRedlines, trackParagraphMark: true, plainTextContent: options.plainTextContent === true }
|
|
27068
27258
|
);
|
|
27069
27259
|
const imported = xmlDoc.importNode(plainParagraph, true);
|
|
27070
27260
|
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
@@ -27203,6 +27393,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27203
27393
|
existingRevisions: options.existingRevisions || "merge-same-author",
|
|
27204
27394
|
structuredContent: options.structuredContent !== false,
|
|
27205
27395
|
explicitStructuredContent: options.explicitStructuredContent === true,
|
|
27396
|
+
plainTextContent: options.plainTextContent === true,
|
|
27206
27397
|
_revisionIdAllocator: revisionIdAllocator,
|
|
27207
27398
|
_isolatedTableCell: useTableScope
|
|
27208
27399
|
}) : await applyRedlineToOxml2(scopedXml, originalTextForApply, effectiveModifiedText, {
|
|
@@ -27214,6 +27405,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27214
27405
|
pairReplacements: options.pairReplacements === true,
|
|
27215
27406
|
insertionAffinity: options.insertionAffinity || null,
|
|
27216
27407
|
literalTextEdit: options.literalTextEdit === true,
|
|
27408
|
+
plainTextContent: options.plainTextContent === true,
|
|
27217
27409
|
_revisionIdAllocator: revisionIdAllocator,
|
|
27218
27410
|
_isolatedTableCell: useTableScope
|
|
27219
27411
|
});
|
|
@@ -28224,7 +28416,31 @@ function verifyUntrackedLocality({ before, after, targetSourceIndex, beforeViews
|
|
|
28224
28416
|
}
|
|
28225
28417
|
return violations.length ? { ok: false, violations } : { ok: true };
|
|
28226
28418
|
}
|
|
28227
|
-
|
|
28419
|
+
var numberingDefinitionCache = /* @__PURE__ */ new Map();
|
|
28420
|
+
var NUMBERING_DEFINITION_CACHE_SIZE = 8;
|
|
28421
|
+
function parseNumberingDefinitions(text2) {
|
|
28422
|
+
const cached = numberingDefinitionCache.get(text2);
|
|
28423
|
+
if (cached) return cached;
|
|
28424
|
+
const abstractLevels = /* @__PURE__ */ new Map();
|
|
28425
|
+
for (const match of text2.matchAll(/<(?:\w+:)?abstractNum\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?abstractNum>/g)) {
|
|
28426
|
+
const id = /abstractNumId="([^"]*)"/.exec(match[1])?.[1];
|
|
28427
|
+
if (id == null) continue;
|
|
28428
|
+
const levels = abstractLevels.get(id) || /* @__PURE__ */ new Set();
|
|
28429
|
+
for (const lvl of match[2].matchAll(/<(?:\w+:)?lvl\b[^>]*?ilvl="([^"]*)"/g)) levels.add(lvl[1]);
|
|
28430
|
+
abstractLevels.set(id, levels);
|
|
28431
|
+
}
|
|
28432
|
+
const numToAbstract = /* @__PURE__ */ new Map();
|
|
28433
|
+
for (const match of text2.matchAll(/<(?:\w+:)?num\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?num>/g)) {
|
|
28434
|
+
const id = /numId="([^"]*)"/.exec(match[1])?.[1];
|
|
28435
|
+
const abstractId = /abstractNumId[^>]*?val="([^"]*)"/.exec(match[2])?.[1];
|
|
28436
|
+
if (id != null && abstractId != null) numToAbstract.set(id, abstractId);
|
|
28437
|
+
}
|
|
28438
|
+
const definitions = { abstractLevels, numToAbstract };
|
|
28439
|
+
if (numberingDefinitionCache.size >= NUMBERING_DEFINITION_CACHE_SIZE) numberingDefinitionCache.delete(numberingDefinitionCache.keys().next().value);
|
|
28440
|
+
numberingDefinitionCache.set(text2, definitions);
|
|
28441
|
+
return definitions;
|
|
28442
|
+
}
|
|
28443
|
+
function verifyNumberingReferences({ before, after, numberingXmls }) {
|
|
28228
28444
|
const references = (documentNode) => {
|
|
28229
28445
|
const found = /* @__PURE__ */ new Map();
|
|
28230
28446
|
for (const numIdNode of getAllReferencedNumIdElements(documentNode)) {
|
|
@@ -28240,21 +28456,7 @@ function verifyNumberingReferences({ before, after, numberingXmls, beforeViews =
|
|
|
28240
28456
|
const used = references(after);
|
|
28241
28457
|
if (used.size === 0) return { ok: true };
|
|
28242
28458
|
const known = references(before);
|
|
28243
|
-
const
|
|
28244
|
-
const abstractLevels = /* @__PURE__ */ new Map();
|
|
28245
|
-
for (const match of text2.matchAll(/<(?:\w+:)?abstractNum\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?abstractNum>/g)) {
|
|
28246
|
-
const id = /abstractNumId="([^"]*)"/.exec(match[1])?.[1];
|
|
28247
|
-
if (id == null) continue;
|
|
28248
|
-
const levels = abstractLevels.get(id) || /* @__PURE__ */ new Set();
|
|
28249
|
-
for (const lvl of match[2].matchAll(/<(?:\w+:)?lvl\b[^>]*?ilvl="([^"]*)"/g)) levels.add(lvl[1]);
|
|
28250
|
-
abstractLevels.set(id, levels);
|
|
28251
|
-
}
|
|
28252
|
-
const numToAbstract = /* @__PURE__ */ new Map();
|
|
28253
|
-
for (const match of text2.matchAll(/<(?:\w+:)?num\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?num>/g)) {
|
|
28254
|
-
const id = /numId="([^"]*)"/.exec(match[1])?.[1];
|
|
28255
|
-
const abstractId = /abstractNumId[^>]*?val="([^"]*)"/.exec(match[2])?.[1];
|
|
28256
|
-
if (id != null && abstractId != null) numToAbstract.set(id, abstractId);
|
|
28257
|
-
}
|
|
28459
|
+
const { abstractLevels, numToAbstract } = parseNumberingDefinitions((numberingXmls || []).filter(Boolean).join("\n"));
|
|
28258
28460
|
const violations = [];
|
|
28259
28461
|
for (const [numId, levels] of used) {
|
|
28260
28462
|
if (known.has(numId)) continue;
|
|
@@ -28603,6 +28805,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
28603
28805
|
...operation.existingRevisions ? { existingRevisions: operation.existingRevisions } : {},
|
|
28604
28806
|
structuredContent: typeof operation.structuredContent === "boolean" ? operation.structuredContent : options.structuredContent !== false,
|
|
28605
28807
|
explicitStructuredContent: operation.structuredContent === true,
|
|
28808
|
+
plainTextContent: operation.structuredContent === false || operation.structuredContent == null && options.structuredContent === false,
|
|
28606
28809
|
...operation._localizedReplacementCompilation && operation.structuredContent !== true ? { literalTextEdit: true } : {},
|
|
28607
28810
|
pairReplacements: typeof operation.pairReplacements === "boolean" ? operation.pairReplacements : options.pairReplacements !== false,
|
|
28608
28811
|
...operation.insertionAffinity ? { insertionAffinity: operation.insertionAffinity } : {},
|
|
@@ -29857,35 +30060,44 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
29857
30060
|
const anchor = operation.operationKind === "comment" ? operation.textToComment || paragraphText : operation.operationKind === "highlight" ? operation.textToHighlight : operation.operationKind === "format" ? operation.textToFormat : null;
|
|
29858
30061
|
const anchorResolution = operation.operationKind === "comment" && anchor != null ? resolveTextInParagraphIndex(createParagraphTextIndex(paragraph, { revisionView: targetView }), anchor) : null;
|
|
29859
30062
|
const anchorFound = anchor == null || (anchorResolution ? anchorResolution.found : paragraphText.includes(anchor));
|
|
30063
|
+
const rangeParagraphs = (opType) => {
|
|
30064
|
+
if (operation.targetEndRef) {
|
|
30065
|
+
return resolveParagraphRangeByRefs(xmlDoc, operation.targetRef, operation.targetEndRef, {
|
|
30066
|
+
opType,
|
|
30067
|
+
targetRefSnapshot: options.targetRefSnapshot || null,
|
|
30068
|
+
onInfo: options.onInfo,
|
|
30069
|
+
onWarn: options.onWarn
|
|
30070
|
+
});
|
|
30071
|
+
}
|
|
30072
|
+
if (!operation.targetEndDescriptor) return [paragraph];
|
|
30073
|
+
const endResolved = resolveTargetParagraph(xmlDoc, {
|
|
30074
|
+
targetText: operation.targetEndDescriptor.text,
|
|
30075
|
+
targetRef: operation.targetEndDescriptor.index,
|
|
30076
|
+
targetDescriptor: operation.targetEndDescriptor,
|
|
30077
|
+
opType,
|
|
30078
|
+
strictAmbiguity: strictTargets,
|
|
30079
|
+
paragraphMetadataIndex: currentMetadataIndex,
|
|
30080
|
+
metadataIndices,
|
|
30081
|
+
onInfo: options.onInfo,
|
|
30082
|
+
onWarn: options.onWarn
|
|
30083
|
+
});
|
|
30084
|
+
const allParagraphs = getDocumentParagraphNodes(xmlDoc);
|
|
30085
|
+
const startIndex = allParagraphs.indexOf(paragraph);
|
|
30086
|
+
const endIndex = allParagraphs.indexOf(endResolved?.paragraph);
|
|
30087
|
+
return startIndex >= 0 && endIndex >= startIndex ? allParagraphs.slice(startIndex, endIndex + 1) : null;
|
|
30088
|
+
};
|
|
29860
30089
|
const hasRevisions = containsTrackedChanges(paragraph);
|
|
29861
|
-
const
|
|
30090
|
+
const requestedPolicy = operation.existingRevisions || options.existingRevisions || "merge-same-author";
|
|
30091
|
+
const existingPolicy = resolveExistingRevisionsPolicy(
|
|
30092
|
+
requestedPolicy,
|
|
30093
|
+
requestedPolicy === "auto" && operation.operationKind === "redline" && (operation.targetEndRef || operation.targetEndDescriptor) ? rangeParagraphs("redline") || paragraph : paragraph,
|
|
30094
|
+
authorUsed
|
|
30095
|
+
);
|
|
29862
30096
|
const deletingWholeParagraph = operation.operationKind === "redline" && operation.modified === "";
|
|
29863
30097
|
const commentIds = deletingWholeParagraph ? getCommentIdsInParagraph(paragraph) : [];
|
|
29864
30098
|
let error2 = null;
|
|
29865
30099
|
if (operation.operationKind === "restore") {
|
|
29866
|
-
|
|
29867
|
-
opType: "restore",
|
|
29868
|
-
targetRefSnapshot: options.targetRefSnapshot || null,
|
|
29869
|
-
onInfo: options.onInfo,
|
|
29870
|
-
onWarn: options.onWarn
|
|
29871
|
-
}) : [paragraph];
|
|
29872
|
-
if (!operation.targetEndRef && operation.targetEndDescriptor) {
|
|
29873
|
-
const endResolved = resolveTargetParagraph(xmlDoc, {
|
|
29874
|
-
targetText: operation.targetEndDescriptor.text,
|
|
29875
|
-
targetRef: operation.targetEndDescriptor.index,
|
|
29876
|
-
targetDescriptor: operation.targetEndDescriptor,
|
|
29877
|
-
opType: "restore",
|
|
29878
|
-
strictAmbiguity: strictTargets,
|
|
29879
|
-
paragraphMetadataIndex: currentMetadataIndex,
|
|
29880
|
-
metadataIndices,
|
|
29881
|
-
onInfo: options.onInfo,
|
|
29882
|
-
onWarn: options.onWarn
|
|
29883
|
-
});
|
|
29884
|
-
const allParagraphs = getDocumentParagraphNodes(xmlDoc);
|
|
29885
|
-
const startIndex = allParagraphs.indexOf(paragraph);
|
|
29886
|
-
const endIndex = allParagraphs.indexOf(endResolved?.paragraph);
|
|
29887
|
-
restorationParagraphs = startIndex >= 0 && endIndex >= startIndex ? allParagraphs.slice(startIndex, endIndex + 1) : null;
|
|
29888
|
-
}
|
|
30100
|
+
const restorationParagraphs = rangeParagraphs("restore");
|
|
29889
30101
|
const replacements = Array.isArray(operation.modified) ? operation.modified : [operation.modified];
|
|
29890
30102
|
if (!restorationParagraphs || restorationParagraphs[0] !== paragraph || replacements.length !== restorationParagraphs.length) {
|
|
29891
30103
|
error2 = {
|
|
@@ -29932,11 +30144,11 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
29932
30144
|
if (existingPolicy === "merge-same-author" || existingPolicy === "slice-cross-author") {
|
|
29933
30145
|
const authors = getTrackedChangeAuthors(paragraph);
|
|
29934
30146
|
const opAuthor = String(authorUsed || "").trim().toLowerCase();
|
|
29935
|
-
const allSame = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
30147
|
+
const allSame = authors.length > 0 && !hasUnattributedTrackedChange(paragraph) && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
29936
30148
|
if (!allSame && existingPolicy === "merge-same-author") {
|
|
29937
30149
|
error2 = {
|
|
29938
30150
|
code: "EXISTING_REVISIONS",
|
|
29939
|
-
message: `Target paragraph contains tracked changes from another author (${authors
|
|
30151
|
+
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.`,
|
|
29940
30152
|
revisionAuthors: authors,
|
|
29941
30153
|
currentPolicy: existingPolicy
|
|
29942
30154
|
};
|
|
@@ -32022,6 +32234,7 @@ export {
|
|
|
32022
32234
|
canonicalNumberingDefinition,
|
|
32023
32235
|
clearSingleLineListFallbackExplicitSequence,
|
|
32024
32236
|
collectContiguousListParagraphBlock,
|
|
32237
|
+
compareDocumentInspections,
|
|
32025
32238
|
computeDocumentPartsRevisionToken,
|
|
32026
32239
|
computePackageRevisionToken,
|
|
32027
32240
|
computeRevisionToken,
|
|
@@ -32073,9 +32286,11 @@ export {
|
|
|
32073
32286
|
injectCommentsIntoPackage2 as injectCommentsIntoPackage,
|
|
32074
32287
|
insertBodyElementBeforeSectPr,
|
|
32075
32288
|
inspectDocumentParts,
|
|
32289
|
+
inspectionReviewShape,
|
|
32076
32290
|
isLikelyStructuredTableSourceParagraph,
|
|
32077
32291
|
isMarkdownTableText,
|
|
32078
32292
|
isNodeVisibleInRevisionView,
|
|
32293
|
+
mergeCommentParts,
|
|
32079
32294
|
mergeNumberingParts,
|
|
32080
32295
|
mergeNumberingXmlBySchemaOrder,
|
|
32081
32296
|
normalizeBodySectionOrderStandalone,
|
|
@@ -32102,6 +32317,7 @@ export {
|
|
|
32102
32317
|
repairKnownContentTypes,
|
|
32103
32318
|
reserveNextNumberingId,
|
|
32104
32319
|
reserveNextNumberingIdPair,
|
|
32320
|
+
resolveExistingRevisionsPolicy,
|
|
32105
32321
|
resolveParagraphRangeByRefs,
|
|
32106
32322
|
resolveSingleLineListFallbackNumberingAction,
|
|
32107
32323
|
resolveTargetParagraph,
|