@ansonlai/docx-redline-js 0.9.0 → 0.11.0

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