@ansonlai/docx-redline-js 0.10.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 +13 -1
- package/CHANGELOG.md +35 -0
- package/README.md +12 -4
- package/dist/docx-redline-js.esm.js +358 -262
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +92 -92
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +358 -262
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +358 -262
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +35 -2
- package/docs/TESTING.md +6 -1
- package/engine/oxml-engine.js +2 -2
- package/engine/table-mode.js +7 -4
- package/index.d.ts +23 -1
- package/index.js +1 -0
- package/package.json +1 -1
- package/pipeline/content-analysis.js +2 -1
- package/pipeline/structured-content.js +2 -1
- package/services/comment-parts-merge.js +124 -0
- package/services/document-operation-mutations.js +6 -2
- package/services/operation-heuristics.js +1 -1
- 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;
|
|
@@ -8065,6 +8065,7 @@ __export(index_exports, {
|
|
|
8065
8065
|
isLikelyStructuredTableSourceParagraph: () => isLikelyStructuredTableSourceParagraph,
|
|
8066
8066
|
isMarkdownTableText: () => isMarkdownTableText,
|
|
8067
8067
|
isNodeVisibleInRevisionView: () => isNodeVisibleInRevisionView,
|
|
8068
|
+
mergeCommentParts: () => mergeCommentParts,
|
|
8068
8069
|
mergeNumberingParts: () => mergeNumberingParts,
|
|
8069
8070
|
mergeNumberingXmlBySchemaOrder: () => mergeNumberingXmlBySchemaOrder,
|
|
8070
8071
|
normalizeBodySectionOrderStandalone: () => normalizeBodySectionOrderStandalone,
|
|
@@ -10595,6 +10596,7 @@ function paragraphMarkPPrXml(kind, ctx = {}) {
|
|
|
10595
10596
|
}
|
|
10596
10597
|
|
|
10597
10598
|
// services/table-reconciliation.js
|
|
10599
|
+
var cellMarkdown = (text2, options) => options?.plainTextContent === true ? { cleanText: String(text2 ?? ""), formatHints: [] } : preprocessMarkdown(text2);
|
|
10598
10600
|
function generateTableOoxml(tableData, options = {}) {
|
|
10599
10601
|
const {
|
|
10600
10602
|
generateRedlines = false,
|
|
@@ -10628,7 +10630,7 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10628
10630
|
let cellsXml = "";
|
|
10629
10631
|
for (let c = 0; c < numCols; c++) {
|
|
10630
10632
|
const cellText = row[c] || "";
|
|
10631
|
-
const { cleanText, formatHints } =
|
|
10633
|
+
const { cleanText, formatHints } = cellMarkdown(cellText, options);
|
|
10632
10634
|
const runModel = [{
|
|
10633
10635
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10634
10636
|
text: cleanText,
|
|
@@ -10743,7 +10745,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
|
|
|
10743
10745
|
let cellsXml = "";
|
|
10744
10746
|
const rowInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
10745
10747
|
for (const cellText of op.cells) {
|
|
10746
|
-
const { cleanText, formatHints } =
|
|
10748
|
+
const { cleanText, formatHints } = cellMarkdown(cellText, options);
|
|
10747
10749
|
const runModel = [{
|
|
10748
10750
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10749
10751
|
text: cleanText,
|
|
@@ -10817,7 +10819,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
|
|
|
10817
10819
|
}
|
|
10818
10820
|
return;
|
|
10819
10821
|
}
|
|
10820
|
-
const { cleanText, formatHints } =
|
|
10822
|
+
const { cleanText, formatHints } = cellMarkdown(line, options);
|
|
10821
10823
|
const diffOps = computeWordLevelDiffOps(block.acceptedText, cleanText);
|
|
10822
10824
|
const model = applyPatches(splitRunsAtDiffBoundaries(block.runModel, diffOps), diffOps, {
|
|
10823
10825
|
generateRedlines,
|
|
@@ -10827,7 +10829,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
|
|
|
10827
10829
|
out += serializeToOoxml(model, block.pPr, formatHints, { author, generateRedlines, revisionIdAllocator });
|
|
10828
10830
|
});
|
|
10829
10831
|
for (let i2 = cell.blocks.length; i2 < lines.length; i2++) {
|
|
10830
|
-
const { cleanText, formatHints } =
|
|
10832
|
+
const { cleanText, formatHints } = cellMarkdown(lines[i2], options);
|
|
10831
10833
|
const runModel = [{
|
|
10832
10834
|
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10833
10835
|
text: cleanText,
|
|
@@ -10846,7 +10848,7 @@ function reconcileCellContent(cell, newText, options) {
|
|
|
10846
10848
|
return reconcileMultiBlockCellContent(cell, newText, options);
|
|
10847
10849
|
}
|
|
10848
10850
|
const { generateRedlines, author, revisionIdAllocator = null } = options;
|
|
10849
|
-
const { cleanText, formatHints } =
|
|
10851
|
+
const { cleanText, formatHints } = cellMarkdown(newText, options);
|
|
10850
10852
|
const oldText = cell.getText();
|
|
10851
10853
|
const diffOps = computeWordLevelDiffOps(oldText, cleanText);
|
|
10852
10854
|
const baseBlock = cell.blocks[0] || { runModel: [], pPr: null };
|
|
@@ -10892,7 +10894,7 @@ function parseTable(text2) {
|
|
|
10892
10894
|
}
|
|
10893
10895
|
const isSeparatorLine = (line) => {
|
|
10894
10896
|
const normalized = line.replace(/\s+/g, "");
|
|
10895
|
-
return /^\|:?-{3,}:?(\|:?-{3,}:?)
|
|
10897
|
+
return /^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(normalized);
|
|
10896
10898
|
};
|
|
10897
10899
|
const hasHeader = lines.some(isSeparatorLine);
|
|
10898
10900
|
const dataLines = lines.filter((line) => !isSeparatorLine(line));
|
|
@@ -11476,7 +11478,7 @@ var ReconciliationPipeline = class {
|
|
|
11476
11478
|
};
|
|
11477
11479
|
|
|
11478
11480
|
// pipeline/structured-content.js
|
|
11479
|
-
var TABLE_SEPARATOR = /^\s
|
|
11481
|
+
var TABLE_SEPARATOR = /^\s*(?:\|\s*:?-{3,}:?\s*\||\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?)\s*$/;
|
|
11480
11482
|
function isBlank(line) {
|
|
11481
11483
|
return !line || line.trim().length === 0;
|
|
11482
11484
|
}
|
|
@@ -15133,7 +15135,7 @@ function keepUnchangedTextOutsideWrappers(diffs, mapping) {
|
|
|
15133
15135
|
function noChanges(serializer, xmlDoc) {
|
|
15134
15136
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
15135
15137
|
}
|
|
15136
|
-
function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
|
|
15138
|
+
function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true, extra = {}) {
|
|
15137
15139
|
const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
15138
15140
|
const newTableData = parseTable(modifiedText);
|
|
15139
15141
|
const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
|
|
@@ -15149,7 +15151,8 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
15149
15151
|
const options = {
|
|
15150
15152
|
generateRedlines,
|
|
15151
15153
|
author,
|
|
15152
|
-
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
|
|
15154
|
+
revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc),
|
|
15155
|
+
plainTextContent: extra.plainTextContent === true
|
|
15153
15156
|
};
|
|
15154
15157
|
const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
|
|
15155
15158
|
const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
|
|
@@ -15169,14 +15172,14 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
|
|
|
15169
15172
|
targetTable.parentNode.replaceChild(importedTable, targetTable);
|
|
15170
15173
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
|
|
15171
15174
|
}
|
|
15172
|
-
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
|
|
15175
|
+
function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines, extra = {}) {
|
|
15173
15176
|
const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
|
|
15174
15177
|
const tableData = parseTable(modifiedText);
|
|
15175
15178
|
if (!tableData || tableData.rows.length === 0 && tableData.headers.length === 0) {
|
|
15176
15179
|
log("[OxmlEngine] Failed to parse table data from Markdown");
|
|
15177
15180
|
return noChanges(serializer, xmlDoc);
|
|
15178
15181
|
}
|
|
15179
|
-
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true });
|
|
15182
|
+
const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true, plainTextContent: extra.plainTextContent === true });
|
|
15180
15183
|
const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, "application/xml").doc;
|
|
15181
15184
|
if (!tableDoc) return noChanges(serializer, xmlDoc);
|
|
15182
15185
|
const tableParseError = getXmlParseError(tableDoc);
|
|
@@ -16375,11 +16378,11 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16375
16378
|
if (isMarkdownTable && !hasTables) {
|
|
16376
16379
|
recordRouteSelection(options, "table", { transformation: "text-to-table" });
|
|
16377
16380
|
log("[OxmlEngine] Text-to-table transformation: generating new table from Markdown");
|
|
16378
|
-
return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
|
|
16381
|
+
return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
|
|
16379
16382
|
}
|
|
16380
16383
|
if (hasTables && isMarkdownTable) {
|
|
16381
16384
|
recordRouteSelection(options, "table", { transformation: "table-reconciliation" });
|
|
16382
|
-
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
|
|
16385
|
+
return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
|
|
16383
16386
|
}
|
|
16384
16387
|
if (usesSurgicalTextMode) {
|
|
16385
16388
|
recordRouteSelection(options, "surgical", { tableScoped: hasTables });
|
|
@@ -20675,232 +20678,6 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
|
|
|
20675
20678
|
};
|
|
20676
20679
|
}
|
|
20677
20680
|
|
|
20678
|
-
// engine/formatting-removal.js
|
|
20679
|
-
function removeNode2(node) {
|
|
20680
|
-
if (node?.parentNode) {
|
|
20681
|
-
node.parentNode.removeChild(node);
|
|
20682
|
-
}
|
|
20683
|
-
}
|
|
20684
|
-
function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
|
|
20685
|
-
if (!rPr) return null;
|
|
20686
|
-
const rPrClone = rPr.cloneNode(true);
|
|
20687
|
-
if (formatTypes.includes("all")) {
|
|
20688
|
-
const toRemove = [
|
|
20689
|
-
"w:b",
|
|
20690
|
-
"w:i",
|
|
20691
|
-
"w:u",
|
|
20692
|
-
"w:strike",
|
|
20693
|
-
"w:dstrike",
|
|
20694
|
-
"w:color",
|
|
20695
|
-
"w:sz",
|
|
20696
|
-
"w:szCs",
|
|
20697
|
-
"w:rFonts",
|
|
20698
|
-
"w:highlight",
|
|
20699
|
-
"w:vertAlign",
|
|
20700
|
-
"w:spacing",
|
|
20701
|
-
"w:w",
|
|
20702
|
-
"w:kern",
|
|
20703
|
-
"w:position"
|
|
20704
|
-
];
|
|
20705
|
-
toRemove.forEach((tag) => {
|
|
20706
|
-
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
20707
|
-
elements.forEach(removeNode2);
|
|
20708
|
-
});
|
|
20709
|
-
} else {
|
|
20710
|
-
const tagMap = {
|
|
20711
|
-
"bold": "w:b",
|
|
20712
|
-
"italic": "w:i",
|
|
20713
|
-
"underline": "w:u",
|
|
20714
|
-
"strikethrough": "w:strike",
|
|
20715
|
-
"doubleStrike": "w:dstrike",
|
|
20716
|
-
"color": "w:color",
|
|
20717
|
-
"highlight": "w:highlight",
|
|
20718
|
-
"fontSize": "w:sz",
|
|
20719
|
-
"fontSizeCs": "w:szCs",
|
|
20720
|
-
// Complex script font size
|
|
20721
|
-
"fontFamily": "w:rFonts",
|
|
20722
|
-
"superscript": "w:vertAlign",
|
|
20723
|
-
"subscript": "w:vertAlign"
|
|
20724
|
-
};
|
|
20725
|
-
formatTypes.forEach((type) => {
|
|
20726
|
-
const tag = tagMap[type];
|
|
20727
|
-
if (tag) {
|
|
20728
|
-
const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
|
|
20729
|
-
elements.forEach(removeNode2);
|
|
20730
|
-
}
|
|
20731
|
-
});
|
|
20732
|
-
}
|
|
20733
|
-
return rPrClone.children.length > 0 ? rPrClone : null;
|
|
20734
|
-
}
|
|
20735
|
-
function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
20736
|
-
if (!targetText || !ooxmlString) return ooxmlString;
|
|
20737
|
-
const doc = parseOoxml(ooxmlString);
|
|
20738
|
-
if (!doc) return ooxmlString;
|
|
20739
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20740
|
-
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
20741
|
-
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
20742
|
-
const allRuns = [...Array.from(runs)];
|
|
20743
|
-
for (const ins of insertions) {
|
|
20744
|
-
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
20745
|
-
allRuns.push(...Array.from(insideRuns));
|
|
20746
|
-
}
|
|
20747
|
-
for (const run of allRuns) {
|
|
20748
|
-
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
20749
|
-
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
20750
|
-
if (runText.includes(targetText) || runText === targetText) {
|
|
20751
|
-
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
20752
|
-
if (rPrElements.length > 0) {
|
|
20753
|
-
const rPr = rPrElements[0];
|
|
20754
|
-
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
20755
|
-
if (newRPr) {
|
|
20756
|
-
if (rPr.parentNode) {
|
|
20757
|
-
rPr.parentNode.replaceChild(newRPr, rPr);
|
|
20758
|
-
}
|
|
20759
|
-
} else {
|
|
20760
|
-
removeNode2(rPr);
|
|
20761
|
-
}
|
|
20762
|
-
}
|
|
20763
|
-
}
|
|
20764
|
-
}
|
|
20765
|
-
return serializeOoxml(doc);
|
|
20766
|
-
}
|
|
20767
|
-
var HIGHLIGHT_COLOR_MAP = {
|
|
20768
|
-
"yellow": "yellow",
|
|
20769
|
-
"green": "green",
|
|
20770
|
-
"cyan": "cyan",
|
|
20771
|
-
"magenta": "magenta",
|
|
20772
|
-
"blue": "blue",
|
|
20773
|
-
"red": "red",
|
|
20774
|
-
"darkblue": "darkBlue",
|
|
20775
|
-
"darkcyan": "darkCyan",
|
|
20776
|
-
"darkgreen": "darkGreen",
|
|
20777
|
-
"darkmagenta": "darkMagenta",
|
|
20778
|
-
"darkred": "darkRed",
|
|
20779
|
-
"darkyellow": "darkYellow",
|
|
20780
|
-
"gray25": "lightGray",
|
|
20781
|
-
"gray50": "darkGray",
|
|
20782
|
-
"black": "black",
|
|
20783
|
-
"white": "white"
|
|
20784
|
-
};
|
|
20785
|
-
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
20786
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20787
|
-
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
20788
|
-
const generateRedlines = options?.generateRedlines ?? false;
|
|
20789
|
-
const author = options?.author || getDefaultAuthor();
|
|
20790
|
-
let rPrElement = rPr;
|
|
20791
|
-
if (!rPrElement) {
|
|
20792
|
-
rPrElement = createWordElement(doc, "w:rPr");
|
|
20793
|
-
} else {
|
|
20794
|
-
rPrElement = rPr.cloneNode(true);
|
|
20795
|
-
}
|
|
20796
|
-
let previousRPrState = null;
|
|
20797
|
-
if (generateRedlines) {
|
|
20798
|
-
previousRPrState = createWordElement(doc, "w:rPr");
|
|
20799
|
-
Array.from(rPrElement.childNodes).forEach((child) => {
|
|
20800
|
-
if (child.nodeName !== "w:rPrChange") {
|
|
20801
|
-
previousRPrState.appendChild(child.cloneNode(true));
|
|
20802
|
-
}
|
|
20803
|
-
});
|
|
20804
|
-
}
|
|
20805
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
20806
|
-
Array.from(existingHighlight).forEach(removeNode2);
|
|
20807
|
-
const highlightEl = createWordElement(doc, "w:highlight");
|
|
20808
|
-
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
20809
|
-
rPrElement.appendChild(highlightEl);
|
|
20810
|
-
if (generateRedlines && previousRPrState) {
|
|
20811
|
-
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
20812
|
-
const metadata = createRevisionMetadata(author, doc);
|
|
20813
|
-
rPrChange.setAttribute("w:id", String(metadata.id));
|
|
20814
|
-
rPrChange.setAttribute("w:author", metadata.author);
|
|
20815
|
-
rPrChange.setAttribute("w:date", metadata.date);
|
|
20816
|
-
rPrChange.appendChild(previousRPrState);
|
|
20817
|
-
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
20818
|
-
Array.from(existingChange).forEach(removeNode2);
|
|
20819
|
-
rPrElement.appendChild(rPrChange);
|
|
20820
|
-
}
|
|
20821
|
-
return rPrElement;
|
|
20822
|
-
}
|
|
20823
|
-
function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
|
|
20824
|
-
if (!targetText || !ooxmlString) return ooxmlString;
|
|
20825
|
-
const doc = parseOoxml(ooxmlString);
|
|
20826
|
-
if (!doc) return ooxmlString;
|
|
20827
|
-
let revisionIdAllocator;
|
|
20828
|
-
if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
|
|
20829
|
-
revisionIdAllocator = options._revisionIdAllocator;
|
|
20830
|
-
seedRevisionIdsFromDocument(doc, revisionIdAllocator);
|
|
20831
|
-
} else {
|
|
20832
|
-
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
20833
|
-
}
|
|
20834
|
-
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20835
|
-
const getRunText = (run) => {
|
|
20836
|
-
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
20837
|
-
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
20838
|
-
};
|
|
20839
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
20840
|
-
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
20841
|
-
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
20842
|
-
const clonedRun = sourceRun.cloneNode(true);
|
|
20843
|
-
if (sourceRunsWithClaimedRevisionIds.has(sourceRun)) {
|
|
20844
|
-
refreshRunPropertyChangeIds(clonedRun, revisionIdAllocator);
|
|
20845
|
-
} else {
|
|
20846
|
-
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
20847
|
-
}
|
|
20848
|
-
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
20849
|
-
Array.from(textNodes).forEach(removeNode2);
|
|
20850
|
-
const newText = createWordElement(doc, "w:t");
|
|
20851
|
-
newText.setAttribute("xml:space", "preserve");
|
|
20852
|
-
newText.textContent = text2;
|
|
20853
|
-
clonedRun.appendChild(newText);
|
|
20854
|
-
if (shouldHighlight) {
|
|
20855
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
20856
|
-
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
20857
|
-
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
20858
|
-
if (existingRPr) {
|
|
20859
|
-
clonedRun.replaceChild(newRPr, existingRPr);
|
|
20860
|
-
} else {
|
|
20861
|
-
clonedRun.insertBefore(newRPr, clonedRun.firstChild);
|
|
20862
|
-
}
|
|
20863
|
-
}
|
|
20864
|
-
return clonedRun;
|
|
20865
|
-
};
|
|
20866
|
-
for (const run of allRuns) {
|
|
20867
|
-
const runText = getRunText(run);
|
|
20868
|
-
if (!runText) continue;
|
|
20869
|
-
const matchIndexes = [];
|
|
20870
|
-
let searchOffset = 0;
|
|
20871
|
-
while (searchOffset <= runText.length - targetText.length) {
|
|
20872
|
-
const matchIndex = runText.indexOf(targetText, searchOffset);
|
|
20873
|
-
if (matchIndex === -1) break;
|
|
20874
|
-
matchIndexes.push(matchIndex);
|
|
20875
|
-
searchOffset = matchIndex + targetText.length;
|
|
20876
|
-
}
|
|
20877
|
-
if (matchIndexes.length === 0) continue;
|
|
20878
|
-
const parent = run.parentNode;
|
|
20879
|
-
if (!parent) {
|
|
20880
|
-
console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
|
|
20881
|
-
continue;
|
|
20882
|
-
}
|
|
20883
|
-
const fragment = doc.createDocumentFragment();
|
|
20884
|
-
let cursor = 0;
|
|
20885
|
-
for (const matchIndex of matchIndexes) {
|
|
20886
|
-
if (matchIndex > cursor) {
|
|
20887
|
-
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
|
|
20888
|
-
}
|
|
20889
|
-
fragment.appendChild(cloneRunWithText2(
|
|
20890
|
-
run,
|
|
20891
|
-
runText.slice(matchIndex, matchIndex + targetText.length),
|
|
20892
|
-
true
|
|
20893
|
-
));
|
|
20894
|
-
cursor = matchIndex + targetText.length;
|
|
20895
|
-
}
|
|
20896
|
-
if (cursor < runText.length) {
|
|
20897
|
-
fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
|
|
20898
|
-
}
|
|
20899
|
-
parent.replaceChild(fragment, run);
|
|
20900
|
-
}
|
|
20901
|
-
return serializeOoxml(doc);
|
|
20902
|
-
}
|
|
20903
|
-
|
|
20904
20681
|
// services/package-parts.js
|
|
20905
20682
|
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
20906
20683
|
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
@@ -21188,31 +20965,34 @@ async function ensureNumberingArtifactsInZip(zip, numberingXmlList, options = {}
|
|
|
21188
20965
|
}
|
|
21189
20966
|
}
|
|
21190
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
|
+
}
|
|
21191
20988
|
async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
|
|
21192
20989
|
const onInfo = typeof options?.onInfo === "function" ? options.onInfo : () => {
|
|
21193
20990
|
};
|
|
21194
20991
|
if (!commentsXml) return;
|
|
21195
20992
|
const serializer = createSerializer();
|
|
21196
20993
|
const existingText = await readZipText(zip, COMMENTS_PATH);
|
|
21197
|
-
if (!existingText || options.replaceExisting === true)
|
|
21198
|
-
|
|
21199
|
-
zip.file(COMMENTS_PATH, commentsXml);
|
|
21200
|
-
} else {
|
|
21201
|
-
const existingDoc = parseXmlStrictStandalone(existingText, "word/comments.xml (existing)");
|
|
21202
|
-
const incomingDoc = parseXmlStrictStandalone(commentsXml, "word/comments.xml (incoming)");
|
|
21203
|
-
const existingRoot = existingDoc.documentElement;
|
|
21204
|
-
const existingIds = new Set(
|
|
21205
|
-
Array.from(existingRoot.getElementsByTagNameNS(NS_W7, "comment")).map((comment) => comment.getAttribute("w:id") || comment.getAttribute("id")).filter(Boolean)
|
|
21206
|
-
);
|
|
21207
|
-
for (const incomingComment of Array.from(incomingDoc.documentElement.getElementsByTagNameNS(NS_W7, "comment"))) {
|
|
21208
|
-
const id = incomingComment.getAttribute("w:id") || incomingComment.getAttribute("id");
|
|
21209
|
-
if (id && existingIds.has(id)) {
|
|
21210
|
-
throw new Error(`Duplicate comment id: ${id}`);
|
|
21211
|
-
}
|
|
21212
|
-
existingRoot.appendChild(existingDoc.importNode(incomingComment, true));
|
|
21213
|
-
}
|
|
21214
|
-
zip.file(COMMENTS_PATH, serializer.serializeToString(existingDoc));
|
|
21215
|
-
}
|
|
20994
|
+
if (!existingText || options.replaceExisting === true) onInfo("[Demo] Adding comments.xml");
|
|
20995
|
+
zip.file(COMMENTS_PATH, mergeCommentsPartXml(existingText, commentsXml, options));
|
|
21216
20996
|
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
21217
20997
|
if (ctText) {
|
|
21218
20998
|
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
@@ -21448,6 +21228,318 @@ async function validateDocxPackage(zip) {
|
|
|
21448
21228
|
}
|
|
21449
21229
|
}
|
|
21450
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
|
+
|
|
21451
21543
|
// orchestration/route-plan.js
|
|
21452
21544
|
var RoutePlanKind = Object.freeze({
|
|
21453
21545
|
STRUCTURED_LIST_DIRECT: "structured_list_direct",
|
|
@@ -25152,7 +25244,7 @@ function deriveParagraphThenTableAppend(modifiedText) {
|
|
|
25152
25244
|
const tableLines = lines.slice(1);
|
|
25153
25245
|
if (!tableLines.every((line) => line.trim().startsWith("|"))) return null;
|
|
25154
25246
|
const separator = tableLines[1].replace(/\s+/g, "");
|
|
25155
|
-
if (!/^\|:?-{3,}:?(\|:?-{3,}:?)
|
|
25247
|
+
if (!/^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(separator)) return null;
|
|
25156
25248
|
return { paragraphMarkdown: lines[0].trim(), tableText: tableLines.join("\n") };
|
|
25157
25249
|
}
|
|
25158
25250
|
|
|
@@ -25782,7 +25874,10 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
25782
25874
|
String(text2 || ""),
|
|
25783
25875
|
{
|
|
25784
25876
|
author,
|
|
25785
|
-
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 } : {}
|
|
25786
25881
|
}
|
|
25787
25882
|
);
|
|
25788
25883
|
let sourceParagraph = null;
|
|
@@ -27308,7 +27403,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27308
27403
|
author,
|
|
27309
27404
|
// A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
|
|
27310
27405
|
// Reject All removes the text but leaves an empty paragraph behind.
|
|
27311
|
-
{ generateRedlines, trackParagraphMark: true }
|
|
27406
|
+
{ generateRedlines, trackParagraphMark: true, plainTextContent: options.plainTextContent === true }
|
|
27312
27407
|
);
|
|
27313
27408
|
const imported = xmlDoc.importNode(plainParagraph, true);
|
|
27314
27409
|
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
@@ -27447,6 +27542,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27447
27542
|
existingRevisions: options.existingRevisions || "merge-same-author",
|
|
27448
27543
|
structuredContent: options.structuredContent !== false,
|
|
27449
27544
|
explicitStructuredContent: options.explicitStructuredContent === true,
|
|
27545
|
+
plainTextContent: options.plainTextContent === true,
|
|
27450
27546
|
_revisionIdAllocator: revisionIdAllocator,
|
|
27451
27547
|
_isolatedTableCell: useTableScope
|
|
27452
27548
|
}) : await applyRedlineToOxml2(scopedXml, originalTextForApply, effectiveModifiedText, {
|