@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 standalone bundle v0.10.0 — https://github.com/AnsonLai/docx-redline-js
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;
@@ -10447,6 +10447,7 @@ function paragraphMarkPPrXml(kind, ctx = {}) {
10447
10447
  }
10448
10448
 
10449
10449
  // services/table-reconciliation.js
10450
+ var cellMarkdown = (text2, options) => options?.plainTextContent === true ? { cleanText: String(text2 ?? ""), formatHints: [] } : preprocessMarkdown(text2);
10450
10451
  function generateTableOoxml(tableData, options = {}) {
10451
10452
  const {
10452
10453
  generateRedlines = false,
@@ -10480,7 +10481,7 @@ function generateTableOoxml(tableData, options = {}) {
10480
10481
  let cellsXml = "";
10481
10482
  for (let c = 0; c < numCols; c++) {
10482
10483
  const cellText = row[c] || "";
10483
- const { cleanText, formatHints } = preprocessMarkdown(cellText);
10484
+ const { cleanText, formatHints } = cellMarkdown(cellText, options);
10484
10485
  const runModel = [{
10485
10486
  kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
10486
10487
  text: cleanText,
@@ -10595,7 +10596,7 @@ function serializeVirtualGridToOoxml(grid, operations, options) {
10595
10596
  let cellsXml = "";
10596
10597
  const rowInsertMeta = generateRedlines ? createRevisionMetadata(author, revisionIdAllocator) : null;
10597
10598
  for (const cellText of op.cells) {
10598
- const { cleanText, formatHints } = preprocessMarkdown(cellText);
10599
+ const { cleanText, formatHints } = cellMarkdown(cellText, options);
10599
10600
  const runModel = [{
10600
10601
  kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
10601
10602
  text: cleanText,
@@ -10669,7 +10670,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
10669
10670
  }
10670
10671
  return;
10671
10672
  }
10672
- const { cleanText, formatHints } = preprocessMarkdown(line);
10673
+ const { cleanText, formatHints } = cellMarkdown(line, options);
10673
10674
  const diffOps = computeWordLevelDiffOps(block.acceptedText, cleanText);
10674
10675
  const model = applyPatches(splitRunsAtDiffBoundaries(block.runModel, diffOps), diffOps, {
10675
10676
  generateRedlines,
@@ -10679,7 +10680,7 @@ function reconcileMultiBlockCellContent(cell, newText, options) {
10679
10680
  out += serializeToOoxml(model, block.pPr, formatHints, { author, generateRedlines, revisionIdAllocator });
10680
10681
  });
10681
10682
  for (let i2 = cell.blocks.length; i2 < lines.length; i2++) {
10682
- const { cleanText, formatHints } = preprocessMarkdown(lines[i2]);
10683
+ const { cleanText, formatHints } = cellMarkdown(lines[i2], options);
10683
10684
  const runModel = [{
10684
10685
  kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
10685
10686
  text: cleanText,
@@ -10698,7 +10699,7 @@ function reconcileCellContent(cell, newText, options) {
10698
10699
  return reconcileMultiBlockCellContent(cell, newText, options);
10699
10700
  }
10700
10701
  const { generateRedlines, author, revisionIdAllocator = null } = options;
10701
- const { cleanText, formatHints } = preprocessMarkdown(newText);
10702
+ const { cleanText, formatHints } = cellMarkdown(newText, options);
10702
10703
  const oldText = cell.getText();
10703
10704
  const diffOps = computeWordLevelDiffOps(oldText, cleanText);
10704
10705
  const baseBlock = cell.blocks[0] || { runModel: [], pPr: null };
@@ -10744,7 +10745,7 @@ function parseTable(text2) {
10744
10745
  }
10745
10746
  const isSeparatorLine = (line) => {
10746
10747
  const normalized = line.replace(/\s+/g, "");
10747
- return /^\|:?-{3,}:?(\|:?-{3,}:?)+\|?$/.test(normalized);
10748
+ return /^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(normalized);
10748
10749
  };
10749
10750
  const hasHeader = lines.some(isSeparatorLine);
10750
10751
  const dataLines = lines.filter((line) => !isSeparatorLine(line));
@@ -11328,7 +11329,7 @@ var ReconciliationPipeline = class {
11328
11329
  };
11329
11330
 
11330
11331
  // pipeline/structured-content.js
11331
- var TABLE_SEPARATOR = /^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?\s*$/;
11332
+ var TABLE_SEPARATOR = /^\s*(?:\|\s*:?-{3,}:?\s*\||\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?)\s*$/;
11332
11333
  function isBlank(line) {
11333
11334
  return !line || line.trim().length === 0;
11334
11335
  }
@@ -14985,7 +14986,7 @@ function keepUnchangedTextOutsideWrappers(diffs, mapping) {
14985
14986
  function noChanges(serializer, xmlDoc) {
14986
14987
  return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
14987
14988
  }
14988
- function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true) {
14989
+ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines = true, extra = {}) {
14989
14990
  const tableNodes = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
14990
14991
  const newTableData = parseTable(modifiedText);
14991
14992
  const hasNewContent = newTableData.rows.length > 0 || newTableData.headers.length > 0;
@@ -15001,7 +15002,8 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
15001
15002
  const options = {
15002
15003
  generateRedlines,
15003
15004
  author,
15004
- revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc)
15005
+ revisionIdAllocator: getRevisionIdAllocatorForDocument(xmlDoc),
15006
+ plainTextContent: extra.plainTextContent === true
15005
15007
  };
15006
15008
  const reconciledOxml = serializeVirtualGridToOoxml(oldGrid, operations, options);
15007
15009
  const wrappedOxml = `<root xmlns:w="${NS_W}">${reconciledOxml}</root>`;
@@ -15021,14 +15023,14 @@ function applyTableReconciliation(xmlDoc, modifiedText, serializer, parser, auth
15021
15023
  targetTable.parentNode.replaceChild(importedTable, targetTable);
15022
15024
  return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: true });
15023
15025
  }
15024
- function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines) {
15026
+ function applyTextToTableTransformation(xmlDoc, modifiedText, serializer, parser, author, generateRedlines, extra = {}) {
15025
15027
  const revisionIdAllocator = getRevisionIdAllocatorForDocument(xmlDoc);
15026
15028
  const tableData = parseTable(modifiedText);
15027
15029
  if (!tableData || tableData.rows.length === 0 && tableData.headers.length === 0) {
15028
15030
  log("[OxmlEngine] Failed to parse table data from Markdown");
15029
15031
  return noChanges(serializer, xmlDoc);
15030
15032
  }
15031
- const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true });
15033
+ const tableOoxml = generateTableOoxml(tableData, { generateRedlines, author, revisionIdAllocator, trackAsBlock: true, plainTextContent: extra.plainTextContent === true });
15032
15034
  const tableDoc = parseOoxmlSafe(`<root xmlns:w="${NS_W}">${tableOoxml}</root>`, "application/xml").doc;
15033
15035
  if (!tableDoc) return noChanges(serializer, xmlDoc);
15034
15036
  const tableParseError = getXmlParseError(tableDoc);
@@ -16227,11 +16229,11 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
16227
16229
  if (isMarkdownTable && !hasTables) {
16228
16230
  recordRouteSelection(options, "table", { transformation: "text-to-table" });
16229
16231
  log("[OxmlEngine] Text-to-table transformation: generating new table from Markdown");
16230
- return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
16232
+ return finalize(applyTextToTableTransformation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
16231
16233
  }
16232
16234
  if (hasTables && isMarkdownTable) {
16233
16235
  recordRouteSelection(options, "table", { transformation: "table-reconciliation" });
16234
- return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines));
16236
+ return finalize(applyTableReconciliation(xmlDoc, cleanModifiedText, serializer, null, author, generateRedlines, { plainTextContent: options.plainTextContent === true }));
16235
16237
  }
16236
16238
  if (usesSurgicalTextMode) {
16237
16239
  recordRouteSelection(options, "surgical", { tableScoped: hasTables });
@@ -20527,232 +20529,6 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
20527
20529
  };
20528
20530
  }
20529
20531
 
20530
- // engine/formatting-removal.js
20531
- function removeNode2(node) {
20532
- if (node?.parentNode) {
20533
- node.parentNode.removeChild(node);
20534
- }
20535
- }
20536
- function removeFormattingFromRPr(rPr, formatTypes = ["all"]) {
20537
- if (!rPr) return null;
20538
- const rPrClone = rPr.cloneNode(true);
20539
- if (formatTypes.includes("all")) {
20540
- const toRemove = [
20541
- "w:b",
20542
- "w:i",
20543
- "w:u",
20544
- "w:strike",
20545
- "w:dstrike",
20546
- "w:color",
20547
- "w:sz",
20548
- "w:szCs",
20549
- "w:rFonts",
20550
- "w:highlight",
20551
- "w:vertAlign",
20552
- "w:spacing",
20553
- "w:w",
20554
- "w:kern",
20555
- "w:position"
20556
- ];
20557
- toRemove.forEach((tag) => {
20558
- const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
20559
- elements.forEach(removeNode2);
20560
- });
20561
- } else {
20562
- const tagMap = {
20563
- "bold": "w:b",
20564
- "italic": "w:i",
20565
- "underline": "w:u",
20566
- "strikethrough": "w:strike",
20567
- "doubleStrike": "w:dstrike",
20568
- "color": "w:color",
20569
- "highlight": "w:highlight",
20570
- "fontSize": "w:sz",
20571
- "fontSizeCs": "w:szCs",
20572
- // Complex script font size
20573
- "fontFamily": "w:rFonts",
20574
- "superscript": "w:vertAlign",
20575
- "subscript": "w:vertAlign"
20576
- };
20577
- formatTypes.forEach((type) => {
20578
- const tag = tagMap[type];
20579
- if (tag) {
20580
- const elements = rPrClone.querySelectorAll(`${tag}, ${tag.replace("w:", "")}`);
20581
- elements.forEach(removeNode2);
20582
- }
20583
- });
20584
- }
20585
- return rPrClone.children.length > 0 ? rPrClone : null;
20586
- }
20587
- function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
20588
- if (!targetText || !ooxmlString) return ooxmlString;
20589
- const doc = parseOoxml(ooxmlString);
20590
- if (!doc) return ooxmlString;
20591
- const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
20592
- const runs = doc.getElementsByTagNameNS(NS_W11, "r");
20593
- const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
20594
- const allRuns = [...Array.from(runs)];
20595
- for (const ins of insertions) {
20596
- const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
20597
- allRuns.push(...Array.from(insideRuns));
20598
- }
20599
- for (const run of allRuns) {
20600
- const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
20601
- const runText = Array.from(textNodes).map((t) => t.textContent).join("");
20602
- if (runText.includes(targetText) || runText === targetText) {
20603
- const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
20604
- if (rPrElements.length > 0) {
20605
- const rPr = rPrElements[0];
20606
- const newRPr = removeFormattingFromRPr(rPr, formatTypes);
20607
- if (newRPr) {
20608
- if (rPr.parentNode) {
20609
- rPr.parentNode.replaceChild(newRPr, rPr);
20610
- }
20611
- } else {
20612
- removeNode2(rPr);
20613
- }
20614
- }
20615
- }
20616
- }
20617
- return serializeOoxml(doc);
20618
- }
20619
- var HIGHLIGHT_COLOR_MAP = {
20620
- "yellow": "yellow",
20621
- "green": "green",
20622
- "cyan": "cyan",
20623
- "magenta": "magenta",
20624
- "blue": "blue",
20625
- "red": "red",
20626
- "darkblue": "darkBlue",
20627
- "darkcyan": "darkCyan",
20628
- "darkgreen": "darkGreen",
20629
- "darkmagenta": "darkMagenta",
20630
- "darkred": "darkRed",
20631
- "darkyellow": "darkYellow",
20632
- "gray25": "lightGray",
20633
- "gray50": "darkGray",
20634
- "black": "black",
20635
- "white": "white"
20636
- };
20637
- function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
20638
- const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
20639
- const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
20640
- const generateRedlines = options?.generateRedlines ?? false;
20641
- const author = options?.author || getDefaultAuthor();
20642
- let rPrElement = rPr;
20643
- if (!rPrElement) {
20644
- rPrElement = createWordElement(doc, "w:rPr");
20645
- } else {
20646
- rPrElement = rPr.cloneNode(true);
20647
- }
20648
- let previousRPrState = null;
20649
- if (generateRedlines) {
20650
- previousRPrState = createWordElement(doc, "w:rPr");
20651
- Array.from(rPrElement.childNodes).forEach((child) => {
20652
- if (child.nodeName !== "w:rPrChange") {
20653
- previousRPrState.appendChild(child.cloneNode(true));
20654
- }
20655
- });
20656
- }
20657
- const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
20658
- Array.from(existingHighlight).forEach(removeNode2);
20659
- const highlightEl = createWordElement(doc, "w:highlight");
20660
- highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
20661
- rPrElement.appendChild(highlightEl);
20662
- if (generateRedlines && previousRPrState) {
20663
- const rPrChange = createWordElement(doc, "w:rPrChange");
20664
- const metadata = createRevisionMetadata(author, doc);
20665
- rPrChange.setAttribute("w:id", String(metadata.id));
20666
- rPrChange.setAttribute("w:author", metadata.author);
20667
- rPrChange.setAttribute("w:date", metadata.date);
20668
- rPrChange.appendChild(previousRPrState);
20669
- const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
20670
- Array.from(existingChange).forEach(removeNode2);
20671
- rPrElement.appendChild(rPrChange);
20672
- }
20673
- return rPrElement;
20674
- }
20675
- function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", options = {}) {
20676
- if (!targetText || !ooxmlString) return ooxmlString;
20677
- const doc = parseOoxml(ooxmlString);
20678
- if (!doc) return ooxmlString;
20679
- let revisionIdAllocator;
20680
- if (options?._revisionIdAllocator instanceof RevisionIdAllocator) {
20681
- revisionIdAllocator = options._revisionIdAllocator;
20682
- seedRevisionIdsFromDocument(doc, revisionIdAllocator);
20683
- } else {
20684
- revisionIdAllocator = createRevisionIdAllocator(doc);
20685
- }
20686
- const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
20687
- const getRunText = (run) => {
20688
- const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
20689
- return Array.from(textNodes).map((t) => t.textContent).join("");
20690
- };
20691
- const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
20692
- const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
20693
- const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
20694
- const clonedRun = sourceRun.cloneNode(true);
20695
- if (sourceRunsWithClaimedRevisionIds.has(sourceRun)) {
20696
- refreshRunPropertyChangeIds(clonedRun, revisionIdAllocator);
20697
- } else {
20698
- sourceRunsWithClaimedRevisionIds.add(sourceRun);
20699
- }
20700
- const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
20701
- Array.from(textNodes).forEach(removeNode2);
20702
- const newText = createWordElement(doc, "w:t");
20703
- newText.setAttribute("xml:space", "preserve");
20704
- newText.textContent = text2;
20705
- clonedRun.appendChild(newText);
20706
- if (shouldHighlight) {
20707
- const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
20708
- const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
20709
- const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
20710
- if (existingRPr) {
20711
- clonedRun.replaceChild(newRPr, existingRPr);
20712
- } else {
20713
- clonedRun.insertBefore(newRPr, clonedRun.firstChild);
20714
- }
20715
- }
20716
- return clonedRun;
20717
- };
20718
- for (const run of allRuns) {
20719
- const runText = getRunText(run);
20720
- if (!runText) continue;
20721
- const matchIndexes = [];
20722
- let searchOffset = 0;
20723
- while (searchOffset <= runText.length - targetText.length) {
20724
- const matchIndex = runText.indexOf(targetText, searchOffset);
20725
- if (matchIndex === -1) break;
20726
- matchIndexes.push(matchIndex);
20727
- searchOffset = matchIndex + targetText.length;
20728
- }
20729
- if (matchIndexes.length === 0) continue;
20730
- const parent = run.parentNode;
20731
- if (!parent) {
20732
- console.warn("[Highlight] Run parent is null; skipping. Likely already processed.");
20733
- continue;
20734
- }
20735
- const fragment = doc.createDocumentFragment();
20736
- let cursor = 0;
20737
- for (const matchIndex of matchIndexes) {
20738
- if (matchIndex > cursor) {
20739
- fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor, matchIndex), false));
20740
- }
20741
- fragment.appendChild(cloneRunWithText2(
20742
- run,
20743
- runText.slice(matchIndex, matchIndex + targetText.length),
20744
- true
20745
- ));
20746
- cursor = matchIndex + targetText.length;
20747
- }
20748
- if (cursor < runText.length) {
20749
- fragment.appendChild(cloneRunWithText2(run, runText.slice(cursor), false));
20750
- }
20751
- parent.replaceChild(fragment, run);
20752
- }
20753
- return serializeOoxml(doc);
20754
- }
20755
-
20756
20532
  // services/package-parts.js
20757
20533
  var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
20758
20534
  var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
@@ -21040,31 +20816,34 @@ async function ensureNumberingArtifactsInZip(zip, numberingXmlList, options = {}
21040
20816
  }
21041
20817
  }
21042
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
+ }
21043
20839
  async function ensureCommentsArtifactsInZip(zip, commentsXml, options = {}) {
21044
20840
  const onInfo = typeof options?.onInfo === "function" ? options.onInfo : () => {
21045
20841
  };
21046
20842
  if (!commentsXml) return;
21047
20843
  const serializer = createSerializer();
21048
20844
  const existingText = await readZipText(zip, COMMENTS_PATH);
21049
- if (!existingText || options.replaceExisting === true) {
21050
- onInfo("[Demo] Adding comments.xml");
21051
- zip.file(COMMENTS_PATH, commentsXml);
21052
- } else {
21053
- const existingDoc = parseXmlStrictStandalone(existingText, "word/comments.xml (existing)");
21054
- const incomingDoc = parseXmlStrictStandalone(commentsXml, "word/comments.xml (incoming)");
21055
- const existingRoot = existingDoc.documentElement;
21056
- const existingIds = new Set(
21057
- Array.from(existingRoot.getElementsByTagNameNS(NS_W7, "comment")).map((comment) => comment.getAttribute("w:id") || comment.getAttribute("id")).filter(Boolean)
21058
- );
21059
- for (const incomingComment of Array.from(incomingDoc.documentElement.getElementsByTagNameNS(NS_W7, "comment"))) {
21060
- const id = incomingComment.getAttribute("w:id") || incomingComment.getAttribute("id");
21061
- if (id && existingIds.has(id)) {
21062
- throw new Error(`Duplicate comment id: ${id}`);
21063
- }
21064
- existingRoot.appendChild(existingDoc.importNode(incomingComment, true));
21065
- }
21066
- zip.file(COMMENTS_PATH, serializer.serializeToString(existingDoc));
21067
- }
20845
+ if (!existingText || options.replaceExisting === true) onInfo("[Demo] Adding comments.xml");
20846
+ zip.file(COMMENTS_PATH, mergeCommentsPartXml(existingText, commentsXml, options));
21068
20847
  const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
21069
20848
  if (ctText) {
21070
20849
  const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
@@ -21300,6 +21079,318 @@ async function validateDocxPackage(zip) {
21300
21079
  }
21301
21080
  }
21302
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
+
21303
21394
  // orchestration/route-plan.js
21304
21395
  var RoutePlanKind = Object.freeze({
21305
21396
  STRUCTURED_LIST_DIRECT: "structured_list_direct",
@@ -25004,7 +25095,7 @@ function deriveParagraphThenTableAppend(modifiedText) {
25004
25095
  const tableLines = lines.slice(1);
25005
25096
  if (!tableLines.every((line) => line.trim().startsWith("|"))) return null;
25006
25097
  const separator = tableLines[1].replace(/\s+/g, "");
25007
- if (!/^\|:?-{3,}:?(\|:?-{3,}:?)+\|?$/.test(separator)) return null;
25098
+ if (!/^\|:?-{3,}:?(?:(\|:?-{3,}:?)+\|?|\|)$/.test(separator)) return null;
25008
25099
  return { paragraphMarkdown: lines[0].trim(), tableText: tableLines.join("\n") };
25009
25100
  }
25010
25101
 
@@ -25634,7 +25725,10 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
25634
25725
  String(text2 || ""),
25635
25726
  {
25636
25727
  author,
25637
- 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 } : {}
25638
25732
  }
25639
25733
  );
25640
25734
  let sourceParagraph = null;
@@ -27160,7 +27254,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
27160
27254
  author,
27161
27255
  // A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
27162
27256
  // Reject All removes the text but leaves an empty paragraph behind.
27163
- { generateRedlines, trackParagraphMark: true }
27257
+ { generateRedlines, trackParagraphMark: true, plainTextContent: options.plainTextContent === true }
27164
27258
  );
27165
27259
  const imported = xmlDoc.importNode(plainParagraph, true);
27166
27260
  ensureParagraphIdsOnImportedNode(imported, operationSession);
@@ -27299,6 +27393,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
27299
27393
  existingRevisions: options.existingRevisions || "merge-same-author",
27300
27394
  structuredContent: options.structuredContent !== false,
27301
27395
  explicitStructuredContent: options.explicitStructuredContent === true,
27396
+ plainTextContent: options.plainTextContent === true,
27302
27397
  _revisionIdAllocator: revisionIdAllocator,
27303
27398
  _isolatedTableCell: useTableScope
27304
27399
  }) : await applyRedlineToOxml2(scopedXml, originalTextForApply, effectiveModifiedText, {
@@ -32195,6 +32290,7 @@ export {
32195
32290
  isLikelyStructuredTableSourceParagraph,
32196
32291
  isMarkdownTableText,
32197
32292
  isNodeVisibleInRevisionView,
32293
+ mergeCommentParts,
32198
32294
  mergeNumberingParts,
32199
32295
  mergeNumberingXmlBySchemaOrder,
32200
32296
  normalizeBodySectionOrderStandalone,