@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.
@@ -1,4 +1,4 @@
1
- // @ansonlai/docx-redline-js v0.10.0 — https://github.com/AnsonLai/docx-redline-js
1
+ // @ansonlai/docx-redline-js 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;
@@ -3968,6 +3968,7 @@ function paragraphMarkPPrXml(kind, ctx = {}) {
3968
3968
  }
3969
3969
 
3970
3970
  // services/table-reconciliation.js
3971
+ var cellMarkdown = (text2, options) => options?.plainTextContent === true ? { cleanText: String(text2 ?? ""), formatHints: [] } : preprocessMarkdown(text2);
3971
3972
  function generateTableOoxml(tableData, options = {}) {
3972
3973
  const {
3973
3974
  generateRedlines = false,
@@ -4001,7 +4002,7 @@ function generateTableOoxml(tableData, options = {}) {
4001
4002
  let cellsXml = "";
4002
4003
  for (let c = 0; c < numCols; c++) {
4003
4004
  const cellText = row[c] || "";
4004
- const { cleanText, formatHints } = preprocessMarkdown(cellText);
4005
+ const { cleanText, formatHints } = cellMarkdown(cellText, options);
4005
4006
  const runModel = [{
4006
4007
  kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
4007
4008
  text: cleanText,
@@ -4116,7 +4117,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
4116
4117
  let cellsXml = "";
4117
4118
  const rowInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
4118
4119
  for (const cellText of op.cells) {
4119
- const { cleanText, formatHints } = preprocessMarkdown(cellText);
4120
+ const { cleanText, formatHints } = cellMarkdown(cellText, options);
4120
4121
  const runModel = [{
4121
4122
  kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
4122
4123
  text: cleanText,
@@ -4190,7 +4191,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
4190
4191
  }
4191
4192
  return;
4192
4193
  }
4193
- const { cleanText, formatHints } = preprocessMarkdown(line);
4194
+ const { cleanText, formatHints } = cellMarkdown(line, options);
4194
4195
  const diffOps = computeWordLevelDiffOps(block.acceptedText, cleanText);
4195
4196
  const model = applyPatches(splitRunsAtDiffBoundaries(block.runModel, diffOps), diffOps, {
4196
4197
  generateRedlines,
@@ -4200,7 +4201,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
4200
4201
  out += serializeToOoxml(model, block.pPr, formatHints, { author, generateRedlines, revisionIdAllocator });
4201
4202
  });
4202
4203
  for (let i2 = cell.blocks.length; i2 < lines.length; i2++) {
4203
- const { cleanText, formatHints } = preprocessMarkdown(lines[i2]);
4204
+ const { cleanText, formatHints } = cellMarkdown(lines[i2], options);
4204
4205
  const runModel = [{
4205
4206
  kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
4206
4207
  text: cleanText,
@@ -4219,7 +4220,7 @@ function reconcileCellContent(cell, newText, options) {
4219
4220
  return reconcileMultiBlockCellContent(cell, newText, options);
4220
4221
  }
4221
4222
  const { generateRedlines, author, revisionIdAllocator = null } = options;
4222
- const { cleanText, formatHints } = preprocessMarkdown(newText);
4223
+ const { cleanText, formatHints } = cellMarkdown(newText, options);
4223
4224
  const oldText = cell.getText();
4224
4225
  const diffOps = computeWordLevelDiffOps(oldText, cleanText);
4225
4226
  const baseBlock = cell.blocks[0] || { runModel: [], pPr: null };
@@ -4265,7 +4266,7 @@ function parseTable(text2) {
4265
4266
  }
4266
4267
  const isSeparatorLine = (line) => {
4267
4268
  const normalized = line.replace(/\s+/g, "");
4268
- return /^\|:?-{3,}:?(\|:?-{3,}:?)+\|?$/.test(normalized);
4269
+ return /^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(normalized);
4269
4270
  };
4270
4271
  const hasHeader = lines.some(isSeparatorLine);
4271
4272
  const dataLines = lines.filter((line) => !isSeparatorLine(line));
@@ -4849,7 +4850,7 @@ var ReconciliationPipeline = class {
4849
4850
  };
4850
4851
 
4851
4852
  // pipeline/structured-content.js
4852
- var TABLE_SEPARATOR = /^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?\s*$/;
4853
+ var TABLE_SEPARATOR = /^\s*(?:\|\s*:?-{3,}:?\s*\||\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?)\s*$/;
4853
4854
  function isBlank(line) {
4854
4855
  return !line || line.trim().length === 0;
4855
4856
  }
@@ -8506,7 +8507,7 @@ function keepUnchangedTextOutsideWrappers(diffs, mapping) {
8506
8507
  function noChanges(serializer, xmlDoc) {
8507
8508
  return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
8508
8509
  }
8509
- function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
8510
+ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true, extra = {}) {
8510
8511
  const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
8511
8512
  const newTableData = parseTable(modifiedText);
8512
8513
  const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
@@ -8522,7 +8523,8 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
8522
8523
  const options = {
8523
8524
  generateRedlines,
8524
8525
  author,
8525
- revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
8526
+ revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc),
8527
+ plainTextContent: extra.plainTextContent === true
8526
8528
  };
8527
8529
  const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
8528
8530
  const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
@@ -8542,14 +8544,14 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
8542
8544
  targetTable.parentNode.replaceChild(importedTable, targetTable);
8543
8545
  return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
8544
8546
  }
8545
- function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
8547
+ function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines, extra = {}) {
8546
8548
  const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
8547
8549
  const tableData = parseTable(modifiedText);
8548
8550
  if (!tableData || tableData.rows.length === 0 && tableData.headers.length === 0) {
8549
8551
  log("[OxmlEngine] Failed to parse table data from Markdown");
8550
8552
  return noChanges(serializer, xmlDoc);
8551
8553
  }
8552
- const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true });
8554
+ const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true, plainTextContent: extra.plainTextContent === true });
8553
8555
  const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, "application/xml").doc;
8554
8556
  if (!tableDoc) return noChanges(serializer, xmlDoc);
8555
8557
  const tableParseError = getXmlParseError(tableDoc);
@@ -9748,11 +9750,11 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
9748
9750
  if (isMarkdownTable && !hasTables) {
9749
9751
  recordRouteSelection(options, "table", { transformation: "text-to-table" });
9750
9752
  log("[OxmlEngine] Text-to-table transformation: generating new table from Markdown");
9751
- return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
9753
+ return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
9752
9754
  }
9753
9755
  if (hasTables && isMarkdownTable) {
9754
9756
  recordRouteSelection(options, "table", { transformation: "table-reconciliation" });
9755
- return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
9757
+ return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
9756
9758
  }
9757
9759
  if (usesSurgicalTextMode) {
9758
9760
  recordRouteSelection(options, "surgical", { tableScoped: hasTables });
@@ -14048,232 +14050,6 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
14048
14050
  };
14049
14051
  }
14050
14052
 
14051
- // engine/formatting-removal.js
14052
- function removeNode2(node) {
14053
- if (node?.parentNode) {
14054
- node.parentNode.removeChild(node);
14055
- }
14056
- }
14057
- function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
14058
- if (!rPr) return null;
14059
- const rPrClone = rPr.cloneNode(true);
14060
- if (formatTypes.includes("all")) {
14061
- const toRemove = [
14062
- "w:b",
14063
- "w:i",
14064
- "w:u",
14065
- "w:strike",
14066
- "w:dstrike",
14067
- "w:color",
14068
- "w:sz",
14069
- "w:szCs",
14070
- "w:rFonts",
14071
- "w:highlight",
14072
- "w:vertAlign",
14073
- "w:spacing",
14074
- "w:w",
14075
- "w:kern",
14076
- "w:position"
14077
- ];
14078
- toRemove.forEach((tag) => {
14079
- const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
14080
- elements.forEach(removeNode2);
14081
- });
14082
- } else {
14083
- const tagMap = {
14084
- "bold": "w:b",
14085
- "italic": "w:i",
14086
- "underline": "w:u",
14087
- "strikethrough": "w:strike",
14088
- "doubleStrike": "w:dstrike",
14089
- "color": "w:color",
14090
- "highlight": "w:highlight",
14091
- "fontSize": "w:sz",
14092
- "fontSizeCs": "w:szCs",
14093
- // Complex script font size
14094
- "fontFamily": "w:rFonts",
14095
- "superscript": "w:vertAlign",
14096
- "subscript": "w:vertAlign"
14097
- };
14098
- formatTypes.forEach((type) => {
14099
- const tag = tagMap[type];
14100
- if (tag) {
14101
- const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
14102
- elements.forEach(removeNode2);
14103
- }
14104
- });
14105
- }
14106
- return rPrClone.children.length > 0 ? rPrClone : null;
14107
- }
14108
- function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
14109
- if (!targetText || !ooxmlString) return ooxmlString;
14110
- const doc = parseOoxml(ooxmlString);
14111
- if (!doc) return ooxmlString;
14112
- const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
14113
- const runs = doc.getElementsByTagNameNS(NS_W11, "r");
14114
- const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
14115
- const allRuns = [...Array.from(runs)];
14116
- for (const ins of insertions) {
14117
- const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
14118
- allRuns.push(...Array.from(insideRuns));
14119
- }
14120
- for (const run of allRuns) {
14121
- const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
14122
- const runText = Array.from(textNodes).map((t) => t.textContent).join("");
14123
- if (runText.includes(targetText) || runText === targetText) {
14124
- const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
14125
- if (rPrElements.length > 0) {
14126
- const rPr = rPrElements[0];
14127
- const newRPr = removeFormattingFromRPr(rPr, formatTypes);
14128
- if (newRPr) {
14129
- if (rPr.parentNode) {
14130
- rPr.parentNode.replaceChild(newRPr, rPr);
14131
- }
14132
- } else {
14133
- removeNode2(rPr);
14134
- }
14135
- }
14136
- }
14137
- }
14138
- return serializeOoxml(doc);
14139
- }
14140
- var HIGHLIGHT_COLOR_MAP = {
14141
- "yellow": "yellow",
14142
- "green": "green",
14143
- "cyan": "cyan",
14144
- "magenta": "magenta",
14145
- "blue": "blue",
14146
- "red": "red",
14147
- "darkblue": "darkBlue",
14148
- "darkcyan": "darkCyan",
14149
- "darkgreen": "darkGreen",
14150
- "darkmagenta": "darkMagenta",
14151
- "darkred": "darkRed",
14152
- "darkyellow": "darkYellow",
14153
- "gray25": "lightGray",
14154
- "gray50": "darkGray",
14155
- "black": "black",
14156
- "white": "white"
14157
- };
14158
- function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
14159
- const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
14160
- const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
14161
- const generateRedlines = options?.generateRedlines ?? false;
14162
- const author = options?.author || getDefaultAuthor();
14163
- let rPrElement = rPr;
14164
- if (!rPrElement) {
14165
- rPrElement = createWordElement(doc, "w:rPr");
14166
- } else {
14167
- rPrElement = rPr.cloneNode(true);
14168
- }
14169
- let previousRPrState = null;
14170
- if (generateRedlines) {
14171
- previousRPrState = createWordElement(doc, "w:rPr");
14172
- Array.from(rPrElement.childNodes).forEach((child) => {
14173
- if (child.nodeName !== "w:rPrChange") {
14174
- previousRPrState.appendChild(child.cloneNode(true));
14175
- }
14176
- });
14177
- }
14178
- const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
14179
- Array.from(existingHighlight).forEach(removeNode2);
14180
- const highlightEl = createWordElement(doc, "w:highlight");
14181
- highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
14182
- rPrElement.appendChild(highlightEl);
14183
- if (generateRedlines && previousRPrState) {
14184
- const rPrChange = createWordElement(doc, "w:rPrChange");
14185
- const metadata = createRevisionMetadata(author, doc);
14186
- rPrChange.setAttribute("w:id", String(metadata.id));
14187
- rPrChange.setAttribute("w:author", metadata.author);
14188
- rPrChange.setAttribute("w:date", metadata.date);
14189
- rPrChange.appendChild(previousRPrState);
14190
- const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
14191
- Array.from(existingChange).forEach(removeNode2);
14192
- rPrElement.appendChild(rPrChange);
14193
- }
14194
- return rPrElement;
14195
- }
14196
- function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
14197
- if (!targetText || !ooxmlString) return ooxmlString;
14198
- const doc = parseOoxml(ooxmlString);
14199
- if (!doc) return ooxmlString;
14200
- let revisionIdAllocator;
14201
- if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
14202
- revisionIdAllocator = options._revisionIdAllocator;
14203
- seedRevisionIdsFromDocument(doc, revisionIdAllocator);
14204
- } else {
14205
- revisionIdAllocator = createRevisionIdAllocator(doc);
14206
- }
14207
- const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
14208
- const getRunText = (run) => {
14209
- const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
14210
- return Array.from(textNodes).map((t) => t.textContent).join("");
14211
- };
14212
- const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
14213
- const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
14214
- const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
14215
- const clonedRun = sourceRun.cloneNode(true);
14216
- if (sourceRunsWithClaimedRevisionIds.has(sourceRun)) {
14217
- refreshRunPropertyChangeIds(clonedRun, revisionIdAllocator);
14218
- } else {
14219
- sourceRunsWithClaimedRevisionIds.add(sourceRun);
14220
- }
14221
- const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
14222
- Array.from(textNodes).forEach(removeNode2);
14223
- const newText = createWordElement(doc, "w:t");
14224
- newText.setAttribute("xml:space", "preserve");
14225
- newText.textContent = text2;
14226
- clonedRun.appendChild(newText);
14227
- if (shouldHighlight) {
14228
- const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
14229
- const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
14230
- const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
14231
- if (existingRPr) {
14232
- clonedRun.replaceChild(newRPr, existingRPr);
14233
- } else {
14234
- clonedRun.insertBefore(newRPr, clonedRun.firstChild);
14235
- }
14236
- }
14237
- return clonedRun;
14238
- };
14239
- for (const run of allRuns) {
14240
- const runText = getRunText(run);
14241
- if (!runText) continue;
14242
- const matchIndexes = [];
14243
- let searchOffset = 0;
14244
- while (searchOffset <= runText.length - targetText.length) {
14245
- const matchIndex = runText.indexOf(targetText, searchOffset);
14246
- if (matchIndex === -1) break;
14247
- matchIndexes.push(matchIndex);
14248
- searchOffset = matchIndex + targetText.length;
14249
- }
14250
- if (matchIndexes.length === 0) continue;
14251
- const parent = run.parentNode;
14252
- if (!parent) {
14253
- console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
14254
- continue;
14255
- }
14256
- const fragment = doc.createDocumentFragment();
14257
- let cursor = 0;
14258
- for (const matchIndex of matchIndexes) {
14259
- if (matchIndex > cursor) {
14260
- fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
14261
- }
14262
- fragment.appendChild(cloneRunWithText2(
14263
- run,
14264
- runText.slice(matchIndex, matchIndex + targetText.length),
14265
- true
14266
- ));
14267
- cursor = matchIndex + targetText.length;
14268
- }
14269
- if (cursor < runText.length) {
14270
- fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
14271
- }
14272
- parent.replaceChild(fragment, run);
14273
- }
14274
- return serializeOoxml(doc);
14275
- }
14276
-
14277
14053
  // services/package-parts.js
14278
14054
  var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
14279
14055
  var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
@@ -14561,31 +14337,34 @@ async function ensureNumberingArtifactsInZip(zip, numberingXmlList, options = {}
14561
14337
  }
14562
14338
  }
14563
14339
  }
14340
+ function mergeCommentsPartXml(existingXml, commentsXml, options = {}) {
14341
+ if (!existingXml || options.replaceExisting === true) return commentsXml;
14342
+ const existingDoc = parseXmlStrictStandalone(existingXml, "word/comments.xml (existing)");
14343
+ const incomingDoc = parseXmlStrictStandalone(commentsXml, "word/comments.xml (incoming)");
14344
+ const incomingComments = Array.from(incomingDoc.documentElement.getElementsByTagNameNS(NS_W7, "comment"));
14345
+ if (incomingComments.length === 0) return existingXml;
14346
+ const existingRoot = existingDoc.documentElement;
14347
+ const usedIds = new Set(
14348
+ Array.from(existingRoot.getElementsByTagNameNS(NS_W7, "comment")).map((comment) => comment.getAttribute("w:id") || comment.getAttribute("id")).filter(Boolean)
14349
+ );
14350
+ for (const incomingComment of incomingComments) {
14351
+ const id = incomingComment.getAttribute("w:id") || incomingComment.getAttribute("id");
14352
+ if (id && usedIds.has(id)) {
14353
+ throw Object.assign(new Error(`Duplicate comment id: ${id}`), { code: "DUPLICATE_COMMENT_ID", commentId: id });
14354
+ }
14355
+ if (id) usedIds.add(id);
14356
+ existingRoot.appendChild(existingDoc.importNode(incomingComment, true));
14357
+ }
14358
+ return createSerializer().serializeToString(existingDoc);
14359
+ }
14564
14360
  async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
14565
14361
  const onInfo = typeof options?.onInfo === "function" ? options.onInfo : () => {
14566
14362
  };
14567
14363
  if (!commentsXml) return;
14568
14364
  const serializer = createSerializer();
14569
14365
  const existingText = await readZipText(zip, COMMENTS_PATH);
14570
- if (!existingText || options.replaceExisting === true) {
14571
- onInfo("[Demo] Adding comments.xml");
14572
- zip.file(COMMENTS_PATH, commentsXml);
14573
- } else {
14574
- const existingDoc = parseXmlStrictStandalone(existingText, "word/comments.xml (existing)");
14575
- const incomingDoc = parseXmlStrictStandalone(commentsXml, "word/comments.xml (incoming)");
14576
- const existingRoot = existingDoc.documentElement;
14577
- const existingIds = new Set(
14578
- Array.from(existingRoot.getElementsByTagNameNS(NS_W7, "comment")).map((comment) => comment.getAttribute("w:id") || comment.getAttribute("id")).filter(Boolean)
14579
- );
14580
- for (const incomingComment of Array.from(incomingDoc.documentElement.getElementsByTagNameNS(NS_W7, "comment"))) {
14581
- const id = incomingComment.getAttribute("w:id") || incomingComment.getAttribute("id");
14582
- if (id && existingIds.has(id)) {
14583
- throw new Error(`Duplicate comment id: ${id}`);
14584
- }
14585
- existingRoot.appendChild(existingDoc.importNode(incomingComment, true));
14586
- }
14587
- zip.file(COMMENTS_PATH, serializer.serializeToString(existingDoc));
14588
- }
14366
+ if (!existingText || options.replaceExisting === true) onInfo("[Demo] Adding comments.xml");
14367
+ zip.file(COMMENTS_PATH, mergeCommentsPartXml(existingText, commentsXml, options));
14589
14368
  const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
14590
14369
  if (ctText) {
14591
14370
  const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
@@ -14821,6 +14600,318 @@ async function validateDocxPackage(zip) {
14821
14600
  }
14822
14601
  }
14823
14602
 
14603
+ // services/comment-parts-merge.js
14604
+ var PART_KEYS = Object.freeze({
14605
+ comments: "commentsXml",
14606
+ commentsExtended: "commentsExtendedXml",
14607
+ commentsIds: "commentsIdsXml",
14608
+ commentsExtensible: "commentsExtensibleXml"
14609
+ });
14610
+ var partsOf = (parts) => Object.fromEntries(Object.values(PART_KEYS).map((key) => [key, parts?.[key] || null]));
14611
+ function mergeCommentParts(parts = {}, output = {}) {
14612
+ const before = partsOf(parts);
14613
+ const after = { ...before };
14614
+ const fail = (error2) => ({ status: "error", hasChanges: false, parts: before, changedParts: [], addedParts: [], error: error2 });
14615
+ const commentsMode = output?.commentsXmlMode === "replace" ? "replace" : "merge";
14616
+ const extendedMode = output?.commentsExtendedXmlMode === "replace" ? "replace" : "merge";
14617
+ for (const [label, value] of [
14618
+ ...Object.values(PART_KEYS).map((key) => [`parts.${key}`, before[key]]),
14619
+ ["output.commentsXml", output?.commentsXml],
14620
+ ["output.commentsExtendedXml", output?.commentsExtendedXml]
14621
+ ]) {
14622
+ if (value != null && typeof value !== "string") {
14623
+ return fail({ code: "INVALID_COMMENT_PARTS", message: `${label} must be an XML string, null or omitted (got ${typeof value}).` });
14624
+ }
14625
+ }
14626
+ const requirePart = (xml, part, rootName, label) => {
14627
+ let doc;
14628
+ try {
14629
+ doc = parseXmlStrictStandalone(xml, label);
14630
+ } catch (error2) {
14631
+ throw Object.assign(error2, { part });
14632
+ }
14633
+ if (doc.documentElement?.localName !== rootName) {
14634
+ throw Object.assign(new Error(`${label}: expected a <${rootName}> root element, found <${doc.documentElement?.localName || "none"}>.`), { code: "INVALID_COMMENT_PARTS", part });
14635
+ }
14636
+ return doc;
14637
+ };
14638
+ try {
14639
+ if (output?.commentsXml) {
14640
+ const incomingDoc = requirePart(output.commentsXml, "comments", "comments", "word/comments.xml (incoming)");
14641
+ const seen = /* @__PURE__ */ new Set();
14642
+ for (const node of Array.from(incomingDoc.getElementsByTagNameNS("*", "comment"))) {
14643
+ const id = node.getAttribute("w:id") || node.getAttribute("id");
14644
+ if (!id) continue;
14645
+ if (seen.has(id)) {
14646
+ throw Object.assign(new Error(`Duplicate comment id: ${id}`), { code: "DUPLICATE_COMMENT_ID", commentId: id, part: "comments" });
14647
+ }
14648
+ seen.add(id);
14649
+ }
14650
+ if (before.commentsXml) requirePart(before.commentsXml, "comments", "comments", "word/comments.xml (existing)");
14651
+ if (before.commentsIdsXml) requirePart(before.commentsIdsXml, "commentsIds", "commentsIds", "word/commentsIds.xml (existing)");
14652
+ if (before.commentsExtensibleXml) requirePart(before.commentsExtensibleXml, "commentsExtensible", "commentsExtensible", "word/commentsExtensible.xml (existing)");
14653
+ after.commentsXml = mergeCommentsPartXml(before.commentsXml, output.commentsXml, { replaceExisting: commentsMode === "replace" });
14654
+ Object.assign(after, reconcileCommentSiblingParts({
14655
+ commentsXml: after.commentsXml,
14656
+ commentsIdsXml: after.commentsIdsXml,
14657
+ commentsExtensibleXml: after.commentsExtensibleXml
14658
+ }));
14659
+ }
14660
+ if (output?.commentsExtendedXml) {
14661
+ if (extendedMode !== "replace" && before.commentsExtendedXml) {
14662
+ return fail({
14663
+ code: "UNSUPPORTED_EXISTING_COMMENT_THREAD_METADATA",
14664
+ part: "commentsExtended",
14665
+ 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.'
14666
+ });
14667
+ }
14668
+ requirePart(output.commentsExtendedXml, "commentsExtended", "commentsEx", "word/commentsExtended.xml (incoming)");
14669
+ after.commentsExtendedXml = output.commentsExtendedXml;
14670
+ }
14671
+ } catch (error2) {
14672
+ return fail({
14673
+ code: error2?.code || "COMMENT_PARTS_MERGE_FAILED",
14674
+ message: error2?.message || String(error2),
14675
+ ...error2?.part || error2?.code === "DUPLICATE_COMMENT_ID" ? { part: error2.part || "comments" } : {},
14676
+ ...error2?.commentId ? { commentId: error2.commentId } : {}
14677
+ });
14678
+ }
14679
+ const changedParts = [];
14680
+ const addedParts = [];
14681
+ for (const [name, key] of Object.entries(PART_KEYS)) {
14682
+ if (after[key] === before[key]) continue;
14683
+ changedParts.push(name);
14684
+ if (!before[key]) addedParts.push(name);
14685
+ }
14686
+ return { status: "ok", hasChanges: changedParts.length > 0, parts: after, changedParts, addedParts };
14687
+ }
14688
+
14689
+ // engine/formatting-removal.js
14690
+ function removeNode2(node) {
14691
+ if (node?.parentNode) {
14692
+ node.parentNode.removeChild(node);
14693
+ }
14694
+ }
14695
+ function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
14696
+ if (!rPr) return null;
14697
+ const rPrClone = rPr.cloneNode(true);
14698
+ if (formatTypes.includes("all")) {
14699
+ const toRemove = [
14700
+ "w:b",
14701
+ "w:i",
14702
+ "w:u",
14703
+ "w:strike",
14704
+ "w:dstrike",
14705
+ "w:color",
14706
+ "w:sz",
14707
+ "w:szCs",
14708
+ "w:rFonts",
14709
+ "w:highlight",
14710
+ "w:vertAlign",
14711
+ "w:spacing",
14712
+ "w:w",
14713
+ "w:kern",
14714
+ "w:position"
14715
+ ];
14716
+ toRemove.forEach((tag) => {
14717
+ const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
14718
+ elements.forEach(removeNode2);
14719
+ });
14720
+ } else {
14721
+ const tagMap = {
14722
+ "bold": "w:b",
14723
+ "italic": "w:i",
14724
+ "underline": "w:u",
14725
+ "strikethrough": "w:strike",
14726
+ "doubleStrike": "w:dstrike",
14727
+ "color": "w:color",
14728
+ "highlight": "w:highlight",
14729
+ "fontSize": "w:sz",
14730
+ "fontSizeCs": "w:szCs",
14731
+ // Complex script font size
14732
+ "fontFamily": "w:rFonts",
14733
+ "superscript": "w:vertAlign",
14734
+ "subscript": "w:vertAlign"
14735
+ };
14736
+ formatTypes.forEach((type) => {
14737
+ const tag = tagMap[type];
14738
+ if (tag) {
14739
+ const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
14740
+ elements.forEach(removeNode2);
14741
+ }
14742
+ });
14743
+ }
14744
+ return rPrClone.children.length > 0 ? rPrClone : null;
14745
+ }
14746
+ function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
14747
+ if (!targetText || !ooxmlString) return ooxmlString;
14748
+ const doc = parseOoxml(ooxmlString);
14749
+ if (!doc) return ooxmlString;
14750
+ const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
14751
+ const runs = doc.getElementsByTagNameNS(NS_W11, "r");
14752
+ const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
14753
+ const allRuns = [...Array.from(runs)];
14754
+ for (const ins of insertions) {
14755
+ const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
14756
+ allRuns.push(...Array.from(insideRuns));
14757
+ }
14758
+ for (const run of allRuns) {
14759
+ const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
14760
+ const runText = Array.from(textNodes).map((t) => t.textContent).join("");
14761
+ if (runText.includes(targetText) || runText === targetText) {
14762
+ const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
14763
+ if (rPrElements.length > 0) {
14764
+ const rPr = rPrElements[0];
14765
+ const newRPr = removeFormattingFromRPr(rPr, formatTypes);
14766
+ if (newRPr) {
14767
+ if (rPr.parentNode) {
14768
+ rPr.parentNode.replaceChild(newRPr, rPr);
14769
+ }
14770
+ } else {
14771
+ removeNode2(rPr);
14772
+ }
14773
+ }
14774
+ }
14775
+ }
14776
+ return serializeOoxml(doc);
14777
+ }
14778
+ var HIGHLIGHT_COLOR_MAP = {
14779
+ "yellow": "yellow",
14780
+ "green": "green",
14781
+ "cyan": "cyan",
14782
+ "magenta": "magenta",
14783
+ "blue": "blue",
14784
+ "red": "red",
14785
+ "darkblue": "darkBlue",
14786
+ "darkcyan": "darkCyan",
14787
+ "darkgreen": "darkGreen",
14788
+ "darkmagenta": "darkMagenta",
14789
+ "darkred": "darkRed",
14790
+ "darkyellow": "darkYellow",
14791
+ "gray25": "lightGray",
14792
+ "gray50": "darkGray",
14793
+ "black": "black",
14794
+ "white": "white"
14795
+ };
14796
+ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
14797
+ const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
14798
+ const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
14799
+ const generateRedlines = options?.generateRedlines ?? false;
14800
+ const author = options?.author || getDefaultAuthor();
14801
+ let rPrElement = rPr;
14802
+ if (!rPrElement) {
14803
+ rPrElement = createWordElement(doc, "w:rPr");
14804
+ } else {
14805
+ rPrElement = rPr.cloneNode(true);
14806
+ }
14807
+ let previousRPrState = null;
14808
+ if (generateRedlines) {
14809
+ previousRPrState = createWordElement(doc, "w:rPr");
14810
+ Array.from(rPrElement.childNodes).forEach((child) => {
14811
+ if (child.nodeName !== "w:rPrChange") {
14812
+ previousRPrState.appendChild(child.cloneNode(true));
14813
+ }
14814
+ });
14815
+ }
14816
+ const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
14817
+ Array.from(existingHighlight).forEach(removeNode2);
14818
+ const highlightEl = createWordElement(doc, "w:highlight");
14819
+ highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
14820
+ rPrElement.appendChild(highlightEl);
14821
+ if (generateRedlines && previousRPrState) {
14822
+ const rPrChange = createWordElement(doc, "w:rPrChange");
14823
+ const metadata = createRevisionMetadata(author, doc);
14824
+ rPrChange.setAttribute("w:id", String(metadata.id));
14825
+ rPrChange.setAttribute("w:author", metadata.author);
14826
+ rPrChange.setAttribute("w:date", metadata.date);
14827
+ rPrChange.appendChild(previousRPrState);
14828
+ const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
14829
+ Array.from(existingChange).forEach(removeNode2);
14830
+ rPrElement.appendChild(rPrChange);
14831
+ }
14832
+ return rPrElement;
14833
+ }
14834
+ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
14835
+ if (!targetText || !ooxmlString) return ooxmlString;
14836
+ const doc = parseOoxml(ooxmlString);
14837
+ if (!doc) return ooxmlString;
14838
+ let revisionIdAllocator;
14839
+ if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
14840
+ revisionIdAllocator = options._revisionIdAllocator;
14841
+ seedRevisionIdsFromDocument(doc, revisionIdAllocator);
14842
+ } else {
14843
+ revisionIdAllocator = createRevisionIdAllocator(doc);
14844
+ }
14845
+ const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
14846
+ const getRunText = (run) => {
14847
+ const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
14848
+ return Array.from(textNodes).map((t) => t.textContent).join("");
14849
+ };
14850
+ const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
14851
+ const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
14852
+ const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
14853
+ const clonedRun = sourceRun.cloneNode(true);
14854
+ if (sourceRunsWithClaimedRevisionIds.has(sourceRun)) {
14855
+ refreshRunPropertyChangeIds(clonedRun, revisionIdAllocator);
14856
+ } else {
14857
+ sourceRunsWithClaimedRevisionIds.add(sourceRun);
14858
+ }
14859
+ const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
14860
+ Array.from(textNodes).forEach(removeNode2);
14861
+ const newText = createWordElement(doc, "w:t");
14862
+ newText.setAttribute("xml:space", "preserve");
14863
+ newText.textContent = text2;
14864
+ clonedRun.appendChild(newText);
14865
+ if (shouldHighlight) {
14866
+ const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
14867
+ const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
14868
+ const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
14869
+ if (existingRPr) {
14870
+ clonedRun.replaceChild(newRPr, existingRPr);
14871
+ } else {
14872
+ clonedRun.insertBefore(newRPr, clonedRun.firstChild);
14873
+ }
14874
+ }
14875
+ return clonedRun;
14876
+ };
14877
+ for (const run of allRuns) {
14878
+ const runText = getRunText(run);
14879
+ if (!runText) continue;
14880
+ const matchIndexes = [];
14881
+ let searchOffset = 0;
14882
+ while (searchOffset <= runText.length - targetText.length) {
14883
+ const matchIndex = runText.indexOf(targetText, searchOffset);
14884
+ if (matchIndex === -1) break;
14885
+ matchIndexes.push(matchIndex);
14886
+ searchOffset = matchIndex + targetText.length;
14887
+ }
14888
+ if (matchIndexes.length === 0) continue;
14889
+ const parent = run.parentNode;
14890
+ if (!parent) {
14891
+ console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
14892
+ continue;
14893
+ }
14894
+ const fragment = doc.createDocumentFragment();
14895
+ let cursor = 0;
14896
+ for (const matchIndex of matchIndexes) {
14897
+ if (matchIndex > cursor) {
14898
+ fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
14899
+ }
14900
+ fragment.appendChild(cloneRunWithText2(
14901
+ run,
14902
+ runText.slice(matchIndex, matchIndex + targetText.length),
14903
+ true
14904
+ ));
14905
+ cursor = matchIndex + targetText.length;
14906
+ }
14907
+ if (cursor < runText.length) {
14908
+ fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
14909
+ }
14910
+ parent.replaceChild(fragment, run);
14911
+ }
14912
+ return serializeOoxml(doc);
14913
+ }
14914
+
14824
14915
  // orchestration/route-plan.js
14825
14916
  var RoutePlanKind = Object.freeze({
14826
14917
  STRUCTURED_LIST_DIRECT: "structured_list_direct",
@@ -18525,7 +18616,7 @@ function deriveParagraphThenTableAppend(modifiedText) {
18525
18616
  const tableLines = lines.slice(1);
18526
18617
  if (!tableLines.every((line) => line.trim().startsWith("|"))) return null;
18527
18618
  const separator = tableLines[1].replace(/\s+/g, "");
18528
- if (!/^\|:?-{3,}:?(\|:?-{3,}:?)+\|?$/.test(separator)) return null;
18619
+ if (!/^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(separator)) return null;
18529
18620
  return { paragraphMarkdown: lines[0].trim(), tableText: tableLines.join("\n") };
18530
18621
  }
18531
18622
 
@@ -19155,7 +19246,10 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
19155
19246
  String(text2 || ""),
19156
19247
  {
19157
19248
  author,
19158
- generateRedlines: false
19249
+ generateRedlines: false,
19250
+ // A caller's structuredContent: false keeps inserted paragraphs plain too: no headings,
19251
+ // and inline Markdown markers stay literal.
19252
+ ...options.plainTextContent === true ? { structuredContent: false, plainTextContent: true } : {}
19159
19253
  }
19160
19254
  );
19161
19255
  let sourceParagraph = null;
@@ -20681,7 +20775,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
20681
20775
  author,
20682
20776
  // A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
20683
20777
  // Reject All removes the text but leaves an empty paragraph behind.
20684
- { generateRedlines, trackParagraphMark: true }
20778
+ { generateRedlines, trackParagraphMark: true, plainTextContent: options.plainTextContent === true }
20685
20779
  );
20686
20780
  const imported = xmlDoc.importNode(plainParagraph, true);
20687
20781
  ensureParagraphIdsOnImportedNode(imported, operationSession);
@@ -20820,6 +20914,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
20820
20914
  existingRevisions: options.existingRevisions || "merge-same-author",
20821
20915
  structuredContent: options.structuredContent !== false,
20822
20916
  explicitStructuredContent: options.explicitStructuredContent === true,
20917
+ plainTextContent: options.plainTextContent === true,
20823
20918
  _revisionIdAllocator: revisionIdAllocator,
20824
20919
  _isolatedTableCell: useTableScope
20825
20920
  }) : await applyRedlineToOxml2(scopedXml, originalTextForApply, effectiveModifiedText, {
@@ -25716,6 +25811,7 @@ export {
25716
25811
  isLikelyStructuredTableSourceParagraph,
25717
25812
  isMarkdownTableText,
25718
25813
  isNodeVisibleInRevisionView,
25814
+ mergeCommentParts,
25719
25815
  mergeNumberingParts,
25720
25816
  mergeNumberingXmlBySchemaOrder,
25721
25817
  normalizeBodySectionOrderStandalone,