@ansonlai/docx-redline-js 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -1
- package/README.md +28 -13
- package/core/list-targeting.js +12 -10
- package/dist/docx-redline-js.esm.js +837 -549
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +93 -90
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +837 -549
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +837 -549
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +4 -1
- package/docs/schemas/document-operations.schema.json +168 -168
- package/engine/oxml-engine.js +215 -195
- package/engine/reconstruction-mode.js +183 -183
- package/index.d.ts +4 -0
- package/index.js +36 -1
- package/node/cli.js +2 -1
- package/orchestration/list-structural-fallback.js +530 -518
- package/package.json +1 -1
- package/pipeline/list-generation.js +203 -168
- package/pipeline/serialization.js +111 -94
- package/services/batch-operation-orchestrator.js +6 -0
- package/services/document-inspection.js +10 -7
- package/services/document-operation-applier.js +2 -0
- package/services/document-operation-contract.js +553 -537
- package/services/document-operation-mutations.js +1140 -1011
- package/services/numbering-helpers.js +108 -0
- package/services/operation-batch-compiler.js +440 -436
- package/services/operation-heuristics.js +197 -173
- package/services/revision-comment-management.js +601 -594
- package/services/standalone-operation-runner.d.ts +429 -427
- package/services/table-reconciliation.js +48 -46
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js standalone bundle v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone bundle v0.8.4 — 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;
|
|
@@ -9733,7 +9733,14 @@ function buildRunXmlWithHints(item, formatHints, options = {}) {
|
|
|
9733
9733
|
function buildSimpleRun(text2, rPrXml) {
|
|
9734
9734
|
if (!text2) return "";
|
|
9735
9735
|
const rPr = rPrXml || "";
|
|
9736
|
-
return `<w:r>${rPr}
|
|
9736
|
+
return `<w:r>${rPr}${buildRunContent(text2, "w:t")}</w:r>`;
|
|
9737
|
+
}
|
|
9738
|
+
function buildRunContent(text2, textTag) {
|
|
9739
|
+
return String(text2).split(/(\r\n|[\n\r\v\t])/).map((part) => {
|
|
9740
|
+
if (part === " ") return "<w:tab/>";
|
|
9741
|
+
if (part === "\n" || part === "\r" || part === "\r\n" || part === "\v") return "<w:br/>";
|
|
9742
|
+
return part ? `<${textTag} xml:space="preserve">${escapeXml(part)}</${textTag}>` : "";
|
|
9743
|
+
}).join("");
|
|
9737
9744
|
}
|
|
9738
9745
|
function buildDeletionXml(item, options = {}) {
|
|
9739
9746
|
const metadata = createRevisionMetadata(
|
|
@@ -9746,7 +9753,7 @@ function buildDeletionXml(item, options = {}) {
|
|
|
9746
9753
|
if (font) {
|
|
9747
9754
|
rPr = applyFont(rPr, font);
|
|
9748
9755
|
}
|
|
9749
|
-
return `<w:del w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}"><w:r>${rPr}
|
|
9756
|
+
return `<w:del w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}"><w:r>${rPr}${buildRunContent(item.text, "w:delText")}</w:r></w:del>`;
|
|
9750
9757
|
}
|
|
9751
9758
|
function buildInsertionXml(item, formatHints, options = {}) {
|
|
9752
9759
|
const metadata = createRevisionMetadata(
|
|
@@ -10087,7 +10094,7 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10087
10094
|
revisionIdAllocator = null,
|
|
10088
10095
|
trackAsBlock = false
|
|
10089
10096
|
} = options;
|
|
10090
|
-
const
|
|
10097
|
+
const trackRows = generateRedlines && trackAsBlock;
|
|
10091
10098
|
const numCols = tableData.headers?.length || (tableData.rows?.[0]?.length || 1);
|
|
10092
10099
|
const tblPr = `
|
|
10093
10100
|
<w:tblPr>
|
|
@@ -10115,14 +10122,16 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10115
10122
|
const cellText = row[c] || "";
|
|
10116
10123
|
const { cleanText, formatHints } = preprocessMarkdown(cellText);
|
|
10117
10124
|
const runModel = [{
|
|
10118
|
-
kind: generateRedlines
|
|
10125
|
+
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10119
10126
|
text: cleanText,
|
|
10120
10127
|
rPrXml: isHeaderRow ? "<w:rPr><w:b/></w:rPr>" : "",
|
|
10121
10128
|
author,
|
|
10122
10129
|
startOffset: 0,
|
|
10123
10130
|
endOffset: cleanText.length
|
|
10124
10131
|
}];
|
|
10125
|
-
const
|
|
10132
|
+
const cellMarkMeta = trackRows ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
10133
|
+
const cellPPr = cellMarkMeta ? `<w:pPr><w:rPr><w:ins w:id="${cellMarkMeta.id}" w:author="${escapeXml(cellMarkMeta.author)}" w:date="${cellMarkMeta.date}"/></w:rPr></w:pPr>` : null;
|
|
10134
|
+
const runsOoxml = serializeToOoxml(runModel, cellPPr, formatHints, {
|
|
10126
10135
|
author,
|
|
10127
10136
|
generateRedlines,
|
|
10128
10137
|
revisionIdAllocator
|
|
@@ -10131,13 +10140,11 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10131
10140
|
cellsXml += `<w:tc>${tcPr}${runsOoxml}</w:tc>`;
|
|
10132
10141
|
}
|
|
10133
10142
|
const trPr = isHeaderRow ? "<w:trPr><w:tblHeader/><w:cantSplit/></w:trPr>" : "<w:trPr><w:cantSplit/></w:trPr>";
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
if (tableInsertMeta) {
|
|
10138
|
-
tableXml = `<w:ins w:id="${tableInsertMeta.id}" w:author="${escapeXml(tableInsertMeta.author)}" w:date="${tableInsertMeta.date}">${tableXml}</w:ins>`;
|
|
10143
|
+
const rowMeta = trackRows ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
10144
|
+
const trackedTrPr = rowMeta ? trPr.replace("</w:trPr>", `<w:ins w:id="${rowMeta.id}" w:author="${escapeXml(rowMeta.author)}" w:date="${rowMeta.date}"/></w:trPr>`) : trPr;
|
|
10145
|
+
rowsXml += `<w:tr>${trackedTrPr}${cellsXml}</w:tr>`;
|
|
10139
10146
|
}
|
|
10140
|
-
return
|
|
10147
|
+
return `<w:tbl>${tblPr}${tblGrid}${rowsXml}</w:tbl>`;
|
|
10141
10148
|
}
|
|
10142
10149
|
function diffTablesWithVirtualGrid(oldGrid, newTableData) {
|
|
10143
10150
|
const operations = [];
|
|
@@ -10452,7 +10459,37 @@ async function executeListGeneration(options) {
|
|
|
10452
10459
|
}];
|
|
10453
10460
|
}
|
|
10454
10461
|
}
|
|
10455
|
-
|
|
10462
|
+
const sourceParagraphCount = (originalRunModel || []).filter((run) => run.kind === RunKind.PARAGRAPH_START).length;
|
|
10463
|
+
if (generateRedlines && sourceParagraphCount > 0 && (sourceParagraphCount > 1 || deletionRuns.length === 0)) {
|
|
10464
|
+
let segment = null;
|
|
10465
|
+
const flushSegment = () => {
|
|
10466
|
+
if (!segment) return;
|
|
10467
|
+
results.push(serializeToOoxml(segment, null, [], {
|
|
10468
|
+
author,
|
|
10469
|
+
generateRedlines,
|
|
10470
|
+
revisionIdAllocator
|
|
10471
|
+
}));
|
|
10472
|
+
segment = null;
|
|
10473
|
+
};
|
|
10474
|
+
for (const run of originalRunModel) {
|
|
10475
|
+
if (run.kind === RunKind.PARAGRAPH_START) {
|
|
10476
|
+
flushSegment();
|
|
10477
|
+
segment = [{
|
|
10478
|
+
...run,
|
|
10479
|
+
pPrElement: null,
|
|
10480
|
+
pPrXml: addParagraphMarkRevision(
|
|
10481
|
+
run.pPrElement || run.pPrXml || null,
|
|
10482
|
+
"del",
|
|
10483
|
+
author,
|
|
10484
|
+
revisionIdAllocator
|
|
10485
|
+
)
|
|
10486
|
+
}];
|
|
10487
|
+
} else if (segment && (run.kind === "text" || run.kind === "run")) {
|
|
10488
|
+
segment.push({ ...run, kind: "deletion", author });
|
|
10489
|
+
}
|
|
10490
|
+
}
|
|
10491
|
+
flushSegment();
|
|
10492
|
+
} else if (generateRedlines && deletionRuns.length > 0) {
|
|
10456
10493
|
const deletedPPr = addParagraphMarkRevision(sourcePPr, "del", author, revisionIdAllocator);
|
|
10457
10494
|
const deletedParagraph = serializeToOoxml(deletionRuns, deletedPPr, [], {
|
|
10458
10495
|
author,
|
|
@@ -15031,6 +15068,11 @@ function rejectTrackedChangesInOoxml(oxml, options = {}) {
|
|
|
15031
15068
|
}
|
|
15032
15069
|
if (removeNode(insNode)) rejectedCount += 1;
|
|
15033
15070
|
}
|
|
15071
|
+
for (const tableNode of getWordElementsByLocalName(xmlDoc, "tbl")) {
|
|
15072
|
+
if (!tableNode.parentNode) continue;
|
|
15073
|
+
const hasRow = Array.from(tableNode.childNodes || []).some((child) => isWordElement5(child, "tr"));
|
|
15074
|
+
if (!hasRow && removeNode(tableNode)) rejectedCount += 1;
|
|
15075
|
+
}
|
|
15034
15076
|
for (const delNode of getWordElementsByLocalName(xmlDoc, "del")) {
|
|
15035
15077
|
if (!delNode.parentNode || !authorMatchesNode(delNode, filter)) continue;
|
|
15036
15078
|
if (isParagraphMarkRevisionMarker(delNode)) {
|
|
@@ -15660,7 +15702,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15660
15702
|
operationWarnings.push("Input was sanitized; pass sanitizeInput: false to disable.");
|
|
15661
15703
|
}
|
|
15662
15704
|
let structuredAnalysis = null;
|
|
15663
|
-
if (options.structuredContent !== false) {
|
|
15705
|
+
if (options.structuredContent !== false && options.literalTextEdit !== true) {
|
|
15664
15706
|
structuredAnalysis = analyzeStructuredContent(sanitizedText);
|
|
15665
15707
|
if (!structuredAnalysis.valid) {
|
|
15666
15708
|
if (options.explicitStructuredContent === true) {
|
|
@@ -15679,13 +15721,16 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15679
15721
|
}
|
|
15680
15722
|
}
|
|
15681
15723
|
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
15682
|
-
const hasTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
15683
|
-
const hasFormatHints = formatHints.length > 0;
|
|
15684
15724
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
15725
|
+
const hasLiteralTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
15726
|
+
const hasListStructureChange = !hasLiteralTextChanges && options.explicitStructuredContent === true && isListTargetLoose(cleanModifiedText) && !sourceMatchesListShape(paragraphs, cleanModifiedText);
|
|
15727
|
+
const hasTextChanges = hasLiteralTextChanges || hasListStructureChange;
|
|
15728
|
+
const hasFormatHints = formatHints.length > 0;
|
|
15685
15729
|
const hasExistingFormatting = existingFormatHints.length > 0;
|
|
15686
|
-
const
|
|
15730
|
+
const { textSpans: targetSpans } = buildTextSpansFromParagraphs(paragraphs);
|
|
15731
|
+
const visibleText = targetSpans.map((span) => textSpanVisibleText(span)).join("");
|
|
15687
15732
|
const targetFound = originalText.includes("\n") || originalText.includes("\r") ? originalText.split(/\r?\n/).map(normalizeTargetText).filter(Boolean).every((line) => paragraphs.some((paragraph) => {
|
|
15688
|
-
const paragraphText =
|
|
15733
|
+
const paragraphText = targetSpans.filter((span) => span.paragraph === paragraph).map(textSpanVisibleText).join("");
|
|
15689
15734
|
return normalizeTargetText(paragraphText).includes(line);
|
|
15690
15735
|
})) : visibleText.includes(originalText.trim()) || visibleText.replace(/[\t\n\u2011]/g, "").includes(originalText.trim().replace(/[\t\n\u2011]/g, "")) || normalizeTargetText(visibleText).includes(normalizeTargetText(originalText));
|
|
15691
15736
|
if (hasTextChanges && typeof originalText === "string" && originalText.trim() && !targetFound) {
|
|
@@ -15799,7 +15844,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15799
15844
|
const tables = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
15800
15845
|
const hasTables = tables.length > 0;
|
|
15801
15846
|
const isMarkdownTable = /^\|.+\|/.test(cleanModifiedText.trim()) && cleanModifiedText.includes("\n");
|
|
15802
|
-
const isTargetList = isListTargetLoose(cleanModifiedText);
|
|
15847
|
+
const isTargetList = options.literalTextEdit !== true && isListTargetLoose(cleanModifiedText);
|
|
15803
15848
|
const isStructuredContent = options.structuredContent !== false && structuredAnalysis?.requiresStructuredContent === true;
|
|
15804
15849
|
const tableCellContext = initialTableCellContext;
|
|
15805
15850
|
const usesSurgicalTextMode = hasTables || existingRevisionsPolicy === "slice-cross-author";
|
|
@@ -15914,6 +15959,10 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15914
15959
|
throw caught;
|
|
15915
15960
|
}
|
|
15916
15961
|
}
|
|
15962
|
+
function sourceMatchesListShape(paragraphs, listText) {
|
|
15963
|
+
const itemCount = String(listText).split(/\r?\n/).filter((line) => line.trim()).length;
|
|
15964
|
+
return paragraphs.length === itemCount && paragraphs.every((paragraph) => getElementsByTagNSOrTag(paragraph, NS_W, "numPr").length > 0);
|
|
15965
|
+
}
|
|
15917
15966
|
function normalizeTargetText(text2) {
|
|
15918
15967
|
return String(text2 || "").replace(/[\t\n\u2011]/g, " ").replace(/\s+/g, " ").trim();
|
|
15919
15968
|
}
|
|
@@ -16650,12 +16699,12 @@ function validateParagraphBoundaryMutation(targetParagraph, modifiedText, option
|
|
|
16650
16699
|
}
|
|
16651
16700
|
|
|
16652
16701
|
// core/list-targeting.js
|
|
16653
|
-
function
|
|
16654
|
-
if (!node
|
|
16655
|
-
const
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
return
|
|
16702
|
+
function getDirectChildByLocalName(node, localName2) {
|
|
16703
|
+
if (!node) return null;
|
|
16704
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
16705
|
+
if (child?.nodeType === 1 && child.localName === localName2) return child;
|
|
16706
|
+
}
|
|
16707
|
+
return null;
|
|
16659
16708
|
}
|
|
16660
16709
|
function readValAttribute(element) {
|
|
16661
16710
|
if (!element) return null;
|
|
@@ -16772,15 +16821,15 @@ function buildListEntriesForInsertion(parsedItems, normalizedTargetText, anchorL
|
|
|
16772
16821
|
}
|
|
16773
16822
|
function getParagraphListInfo(paragraph) {
|
|
16774
16823
|
if (!paragraph) return null;
|
|
16775
|
-
const pPr =
|
|
16824
|
+
const pPr = getDirectChildByLocalName(paragraph, "pPr");
|
|
16776
16825
|
if (!pPr) return null;
|
|
16777
|
-
const numPr =
|
|
16826
|
+
const numPr = getDirectChildByLocalName(pPr, "numPr");
|
|
16778
16827
|
if (!numPr) return null;
|
|
16779
|
-
const numIdEl =
|
|
16828
|
+
const numIdEl = getDirectChildByLocalName(numPr, "numId");
|
|
16780
16829
|
if (!numIdEl) return null;
|
|
16781
16830
|
const numId = readValAttribute(numIdEl);
|
|
16782
16831
|
if (!numId || numId === "0") return null;
|
|
16783
|
-
const ilvlEl =
|
|
16832
|
+
const ilvlEl = getDirectChildByLocalName(numPr, "ilvl");
|
|
16784
16833
|
const ilvlRaw = readValAttribute(ilvlEl);
|
|
16785
16834
|
const ilvl = Number.parseInt(ilvlRaw || "0", 10);
|
|
16786
16835
|
return {
|
|
@@ -17190,8 +17239,13 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
17190
17239
|
}
|
|
17191
17240
|
return updated;
|
|
17192
17241
|
}
|
|
17242
|
+
function parseOxmlFragment(oxml) {
|
|
17243
|
+
const text2 = String(oxml || "");
|
|
17244
|
+
const declared = /xmlns:w\s*=/.test(text2);
|
|
17245
|
+
return parseOoxmlSafe(declared ? text2 : `<w:root xmlns:w="${NS_W}">${text2}</w:root>`, "application/xml").doc;
|
|
17246
|
+
}
|
|
17193
17247
|
function getFirstParagraphFromOxml(oxml) {
|
|
17194
|
-
const doc =
|
|
17248
|
+
const doc = parseOxmlFragment(oxml);
|
|
17195
17249
|
if (!doc) return null;
|
|
17196
17250
|
const parseError = getXmlParseError(doc);
|
|
17197
17251
|
if (parseError) return null;
|
|
@@ -17218,17 +17272,17 @@ function setElementVal(element, value) {
|
|
|
17218
17272
|
element.setAttribute("w:val", String(value));
|
|
17219
17273
|
}
|
|
17220
17274
|
function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
17221
|
-
const doc =
|
|
17275
|
+
const doc = parseOxmlFragment(oxml);
|
|
17222
17276
|
if (!doc) return null;
|
|
17223
17277
|
const parseError = getXmlParseError(doc);
|
|
17224
17278
|
if (parseError) return null;
|
|
17225
17279
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
17226
|
-
const
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17280
|
+
for (const paragraph of paragraphs) {
|
|
17281
|
+
const numIdNodes = Array.from(paragraph.getElementsByTagNameNS("*", "numId"));
|
|
17282
|
+
for (const numIdNode of numIdNodes) {
|
|
17283
|
+
const numId = getElementId(numIdNode, ["w:val", "val"]);
|
|
17284
|
+
if (numId != null) return String(numId);
|
|
17285
|
+
}
|
|
17232
17286
|
}
|
|
17233
17287
|
return null;
|
|
17234
17288
|
}
|
|
@@ -17378,400 +17432,106 @@ async function executeSingleLineListStructuralFallback(plan, options = {}) {
|
|
|
17378
17432
|
};
|
|
17379
17433
|
}
|
|
17380
17434
|
|
|
17381
|
-
//
|
|
17382
|
-
|
|
17383
|
-
|
|
17384
|
-
|
|
17385
|
-
|
|
17386
|
-
|
|
17387
|
-
|
|
17388
|
-
|
|
17389
|
-
}
|
|
17390
|
-
function wordAttribute3(node, name) {
|
|
17391
|
-
return node.getAttribute(`w:${name}`) || node.getAttribute(name) || "";
|
|
17392
|
-
}
|
|
17393
|
-
function xmlSpaceAttribute(node) {
|
|
17394
|
-
return node.getAttribute("xml:space") || node.getAttribute("space") || node.getAttributeNS?.("http://www.w3.org/XML/1998/namespace", "space") || "";
|
|
17395
|
-
}
|
|
17396
|
-
function isParagraphMarkRevision(node) {
|
|
17397
|
-
return localNameOf3(node.parentNode) === "rPr";
|
|
17398
|
-
}
|
|
17399
|
-
function parseOoxmlForValidation(oxml) {
|
|
17400
|
-
const attempt = (xml) => {
|
|
17401
|
-
const doc = parseXml(xml);
|
|
17402
|
-
const parseError = doc.getElementsByTagName("parsererror")[0];
|
|
17403
|
-
if (parseError) {
|
|
17404
|
-
throw new Error(parseError.textContent || "XML parse error");
|
|
17405
|
-
}
|
|
17406
|
-
return doc;
|
|
17407
|
-
};
|
|
17408
|
-
try {
|
|
17409
|
-
return { doc: attempt(oxml) };
|
|
17410
|
-
} catch {
|
|
17411
|
-
try {
|
|
17412
|
-
return { doc: attempt(`<w:root xmlns:w="${NS_W}">${oxml}</w:root>`) };
|
|
17413
|
-
} catch (error2) {
|
|
17414
|
-
return { error: error2?.message || "XML parse error" };
|
|
17415
|
-
}
|
|
17435
|
+
// services/numbering-helpers.js
|
|
17436
|
+
function parseIntegerAttribute(element, names) {
|
|
17437
|
+
if (!element || !Array.isArray(names)) return null;
|
|
17438
|
+
for (const name of names) {
|
|
17439
|
+
const raw = element.getAttribute(name);
|
|
17440
|
+
if (raw == null || raw === "") continue;
|
|
17441
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
17442
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
17416
17443
|
}
|
|
17444
|
+
return null;
|
|
17417
17445
|
}
|
|
17418
|
-
function
|
|
17419
|
-
|
|
17420
|
-
const
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
addIssue("PARSE_ERROR", "error", "Input is not non-empty OOXML or a parsed XML DOM.");
|
|
17424
|
-
return { valid: false, issues };
|
|
17425
|
-
}
|
|
17426
|
-
const { doc, error: error2 } = isDomNode ? { doc: oxml.nodeType === 9 ? oxml : oxml.ownerDocument } : parseOoxmlForValidation(oxml);
|
|
17427
|
-
if (!doc) {
|
|
17428
|
-
addIssue("PARSE_ERROR", "error", `OOXML does not parse as XML: ${error2}`);
|
|
17429
|
-
return { valid: false, issues };
|
|
17430
|
-
}
|
|
17431
|
-
const insElements = elementsByLocalName(doc, "ins");
|
|
17432
|
-
const delElements = elementsByLocalName(doc, "del");
|
|
17433
|
-
const revisions = insElements.concat(delElements);
|
|
17434
|
-
for (const paragraph of elementsByLocalName(doc, "p")) {
|
|
17435
|
-
const nested = Array.from(paragraph.getElementsByTagName("*")).find((el) => el !== paragraph && localNameOf3(el) === "p");
|
|
17436
|
-
if (nested) {
|
|
17437
|
-
addIssue("NESTED_PARAGRAPH", "error", `<${paragraph.nodeName}> contains nested <${nested.nodeName}>.`);
|
|
17438
|
-
}
|
|
17439
|
-
}
|
|
17440
|
-
for (const body of elementsByLocalName(doc, "body")) {
|
|
17441
|
-
const children = Array.from(body.childNodes || []).filter((child) => child.nodeType === 1);
|
|
17442
|
-
const sectPrIndexes = children.map((child, index) => localNameOf3(child) === "sectPr" ? index : -1).filter((index) => index >= 0);
|
|
17443
|
-
if (sectPrIndexes.length > 1) {
|
|
17444
|
-
addIssue("MULTIPLE_BODY_SECTPR", "error", "<w:body> contains multiple direct <w:sectPr> elements.");
|
|
17445
|
-
} else if (sectPrIndexes.length === 1 && sectPrIndexes[0] !== children.length - 1) {
|
|
17446
|
-
addIssue("SECTPR_NOT_LAST", "error", "<w:sectPr> is not the last element child of <w:body>.");
|
|
17447
|
-
}
|
|
17446
|
+
function nextAvailableId(startId, occupiedIds, maxPreferred = null) {
|
|
17447
|
+
let candidate = Number.isInteger(startId) && startId > 0 ? startId : 1;
|
|
17448
|
+
const occupied = occupiedIds instanceof Set ? occupiedIds : /* @__PURE__ */ new Set();
|
|
17449
|
+
while (occupied.has(candidate)) {
|
|
17450
|
+
candidate += 1;
|
|
17448
17451
|
}
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
|
|
17452
|
-
if (localNameOf3(revision) !== "ins" || localNameOf3(candidate) !== "del") return true;
|
|
17453
|
-
return candidate.parentNode !== revision;
|
|
17454
|
-
});
|
|
17455
|
-
if (invalidNested) {
|
|
17456
|
-
addIssue(
|
|
17457
|
-
"NESTED_REVISION",
|
|
17458
|
-
"error",
|
|
17459
|
-
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") contains invalid nested <${invalidNested.nodeName}>.`
|
|
17460
|
-
);
|
|
17452
|
+
if (Number.isInteger(maxPreferred) && maxPreferred > 0 && candidate > maxPreferred) {
|
|
17453
|
+
for (let probe = 1; probe <= maxPreferred; probe += 1) {
|
|
17454
|
+
if (!occupied.has(probe)) return probe;
|
|
17461
17455
|
}
|
|
17462
17456
|
}
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17457
|
+
return candidate;
|
|
17458
|
+
}
|
|
17459
|
+
function createDynamicNumberingIdState(numberingXml, options = {}) {
|
|
17460
|
+
const minId = Number.isInteger(options?.minId) && options.minId > 0 ? options.minId : 1;
|
|
17461
|
+
const maxPreferred = Number.isInteger(options?.maxPreferred) && options.maxPreferred >= minId ? options.maxPreferred : 32767;
|
|
17462
|
+
const usedNumIds = /* @__PURE__ */ new Set();
|
|
17463
|
+
const usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
17464
|
+
if (String(numberingXml || "").trim()) {
|
|
17465
|
+
try {
|
|
17466
|
+
const numberingDoc = parseOoxml(numberingXml);
|
|
17467
|
+
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
17468
|
+
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
17469
|
+
for (const abstractNum of abstractNums) {
|
|
17470
|
+
const id = parseIntegerAttribute(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
17471
|
+
if (id != null) usedAbstractNumIds.add(id);
|
|
17472
|
+
}
|
|
17473
|
+
for (const num of nums) {
|
|
17474
|
+
const id = parseIntegerAttribute(num, ["w:numId", "numId"]);
|
|
17475
|
+
if (id != null) usedNumIds.add(id);
|
|
17476
|
+
}
|
|
17477
|
+
} catch {
|
|
17471
17478
|
}
|
|
17472
17479
|
}
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
|
|
17484
|
-
|
|
17480
|
+
const maxUsedNumId = usedNumIds.size > 0 ? Math.max(...usedNumIds) : 0;
|
|
17481
|
+
const maxUsedAbstractNumId = usedAbstractNumIds.size > 0 ? Math.max(...usedAbstractNumIds) : 0;
|
|
17482
|
+
const baseNumId = Math.max(minId, maxUsedNumId + 1);
|
|
17483
|
+
const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
|
|
17484
|
+
return {
|
|
17485
|
+
// Kept so list edits can recognise which existing definition a source list uses and continue it.
|
|
17486
|
+
sourceNumberingXml: String(numberingXml || ""),
|
|
17487
|
+
nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
|
|
17488
|
+
nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
|
|
17489
|
+
usedNumIds,
|
|
17490
|
+
usedAbstractNumIds,
|
|
17491
|
+
minId,
|
|
17492
|
+
maxPreferred
|
|
17493
|
+
};
|
|
17494
|
+
}
|
|
17495
|
+
function normalizeNumberingIdState(state) {
|
|
17496
|
+
if (!state || typeof state !== "object") return null;
|
|
17497
|
+
if (!(state.usedNumIds instanceof Set)) state.usedNumIds = /* @__PURE__ */ new Set();
|
|
17498
|
+
if (!(state.usedAbstractNumIds instanceof Set)) state.usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
17499
|
+
if (!Number.isInteger(state.minId) || state.minId < 1) {
|
|
17500
|
+
state.minId = 1;
|
|
17485
17501
|
}
|
|
17486
|
-
|
|
17487
|
-
|
|
17488
|
-
if (!REVISION_ID_ELEMENTS.has(localNameOf3(node))) continue;
|
|
17489
|
-
const id = wordAttribute3(node, "id");
|
|
17490
|
-
if (!id) continue;
|
|
17491
|
-
if (seenIds.has(id)) {
|
|
17492
|
-
addIssue("DUPLICATE_REVISION_ID", "error", `Revision id ${id} appears more than once.`);
|
|
17493
|
-
}
|
|
17494
|
-
seenIds.add(id);
|
|
17502
|
+
if (!Number.isInteger(state.maxPreferred) || state.maxPreferred < state.minId) {
|
|
17503
|
+
state.maxPreferred = 32767;
|
|
17495
17504
|
}
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
const text2 = node.textContent || "";
|
|
17499
|
-
if (/^\s|\s$/.test(text2) && xmlSpaceAttribute(node) !== "preserve") {
|
|
17500
|
-
addIssue(
|
|
17501
|
-
"MISSING_SPACE_PRESERVE",
|
|
17502
|
-
"error",
|
|
17503
|
-
`<${node.nodeName}> has boundary whitespace without xml:space="preserve".`
|
|
17504
|
-
);
|
|
17505
|
-
}
|
|
17506
|
-
if (text2 === "") {
|
|
17507
|
-
addIssue("EMPTY_TEXT_ELEMENT", "warning", `<${node.nodeName}> is empty.`);
|
|
17508
|
-
}
|
|
17505
|
+
if (!Number.isInteger(state.nextNumId) || state.nextNumId < state.minId) {
|
|
17506
|
+
state.nextNumId = state.minId;
|
|
17509
17507
|
}
|
|
17510
|
-
|
|
17511
|
-
|
|
17512
|
-
const hasElementChild = Array.from(revision.childNodes || []).some((child) => child.nodeType === 1);
|
|
17513
|
-
if (!hasElementChild) {
|
|
17514
|
-
addIssue(
|
|
17515
|
-
"EMPTY_REVISION_WRAPPER",
|
|
17516
|
-
"warning",
|
|
17517
|
-
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") wraps no content.`
|
|
17518
|
-
);
|
|
17519
|
-
}
|
|
17508
|
+
if (!Number.isInteger(state.nextAbstractNumId) || state.nextAbstractNumId < state.minId) {
|
|
17509
|
+
state.nextAbstractNumId = state.minId;
|
|
17520
17510
|
}
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17511
|
+
state.nextNumId = nextAvailableId(state.nextNumId, state.usedNumIds, state.maxPreferred);
|
|
17512
|
+
state.nextAbstractNumId = nextAvailableId(state.nextAbstractNumId, state.usedAbstractNumIds, state.maxPreferred);
|
|
17513
|
+
return state;
|
|
17514
|
+
}
|
|
17515
|
+
function reserveNextNumberingId(state, kind = "num") {
|
|
17516
|
+
const normalized = normalizeNumberingIdState(state);
|
|
17517
|
+
if (!normalized) return null;
|
|
17518
|
+
const useAbstract = kind === "abstract";
|
|
17519
|
+
const id = useAbstract ? normalized.nextAbstractNumId : normalized.nextNumId;
|
|
17520
|
+
if (!Number.isInteger(id) || id < 1) return null;
|
|
17521
|
+
if (useAbstract) {
|
|
17522
|
+
normalized.usedAbstractNumIds.add(id);
|
|
17523
|
+
normalized.nextAbstractNumId = nextAvailableId(id + 1, normalized.usedAbstractNumIds, normalized.maxPreferred);
|
|
17524
|
+
} else {
|
|
17525
|
+
normalized.usedNumIds.add(id);
|
|
17526
|
+
normalized.nextNumId = nextAvailableId(id + 1, normalized.usedNumIds, normalized.maxPreferred);
|
|
17528
17527
|
}
|
|
17529
|
-
return
|
|
17528
|
+
return id;
|
|
17530
17529
|
}
|
|
17531
|
-
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
if (
|
|
17535
|
-
return
|
|
17536
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI === WORD_MAIN_NS && node.localName === localName2
|
|
17537
|
-
);
|
|
17538
|
-
}
|
|
17539
|
-
function escapeMarkdownCell(text2) {
|
|
17540
|
-
return String(text2 || "").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
17541
|
-
}
|
|
17542
|
-
function extractTableMatrix(tableElement) {
|
|
17543
|
-
const rowElements = getDirectWordChildren(tableElement, "tr");
|
|
17544
|
-
const matrix = rowElements.map((row) => {
|
|
17545
|
-
const cellElements = getDirectWordChildren(row, "tc");
|
|
17546
|
-
return cellElements.map((cell) => {
|
|
17547
|
-
const paragraphs = getDirectWordChildren(cell, "p");
|
|
17548
|
-
if (paragraphs.length === 0) return normalizeWhitespaceForTargeting(getParagraphText(cell));
|
|
17549
|
-
const lines = paragraphs.map((p) => normalizeWhitespaceForTargeting(getParagraphText(p))).filter(Boolean);
|
|
17550
|
-
return lines.join("\n");
|
|
17551
|
-
});
|
|
17552
|
-
});
|
|
17553
|
-
const columnCount = matrix.reduce((max2, row) => Math.max(max2, row.length), 0);
|
|
17554
|
-
matrix.forEach((row) => {
|
|
17555
|
-
while (row.length < columnCount) row.push("");
|
|
17556
|
-
});
|
|
17557
|
-
return { matrix, rowElements, columnCount };
|
|
17558
|
-
}
|
|
17559
|
-
function tableMatrixToMarkdown(matrix, columnCount) {
|
|
17560
|
-
if (!Array.isArray(matrix) || matrix.length === 0 || columnCount <= 0) return null;
|
|
17561
|
-
const normalized = matrix.map((row) => {
|
|
17562
|
-
const copy = Array.isArray(row) ? row.slice(0, columnCount) : [];
|
|
17563
|
-
while (copy.length < columnCount) copy.push("");
|
|
17564
|
-
return copy;
|
|
17565
|
-
});
|
|
17566
|
-
const header = normalized[0];
|
|
17567
|
-
const separator = new Array(columnCount).fill("---");
|
|
17568
|
-
const bodyRows = normalized.slice(1);
|
|
17569
|
-
const toLine = (row) => `| ${row.map((cell) => escapeMarkdownCell(cell)).join(" | ")} |`;
|
|
17570
|
-
return [toLine(header), toLine(separator), ...bodyRows.map(toLine)].join("\n");
|
|
17571
|
-
}
|
|
17572
|
-
function isSymmetricLabelRow(rowValues) {
|
|
17573
|
-
if (!Array.isArray(rowValues) || rowValues.length < 2) return false;
|
|
17574
|
-
const normalized = rowValues.map((value) => normalizeWhitespaceForTargeting(value)).filter(Boolean);
|
|
17575
|
-
if (normalized.length < 2) return false;
|
|
17576
|
-
return normalized.every((value) => value === normalized[0]);
|
|
17577
|
-
}
|
|
17578
|
-
function isLikelyStructuredTableSourceParagraph(text2) {
|
|
17579
|
-
const normalized = String(text2 || "").trim();
|
|
17580
|
-
if (!normalized) return false;
|
|
17581
|
-
if (/^and$/i.test(normalized)) return true;
|
|
17582
|
-
if (/^\[.*\]$/.test(normalized)) return true;
|
|
17583
|
-
if (/^\(.*\)$/.test(normalized)) return true;
|
|
17584
|
-
if (/:\s*$/.test(normalized)) return true;
|
|
17585
|
-
if (normalized.length <= 90 && !/[.!?]$/.test(normalized) && /[:\[\]()]/.test(normalized)) return true;
|
|
17586
|
-
if (/^[\[(]/.test(normalized)) return true;
|
|
17587
|
-
return false;
|
|
17588
|
-
}
|
|
17589
|
-
function inferTableReplacementParagraphBlock(startParagraph, options = {}) {
|
|
17590
|
-
const maxScan = Number.isInteger(options?.maxScan) && options.maxScan > 0 ? options.maxScan : 10;
|
|
17591
|
-
const paragraphTextGetter = typeof options?.getParagraphText === "function" ? options.getParagraphText : getParagraphText;
|
|
17592
|
-
if (!startParagraph || !startParagraph.parentNode) return null;
|
|
17593
|
-
const block = [startParagraph];
|
|
17594
|
-
let cursor = startParagraph.nextSibling;
|
|
17595
|
-
let scanned = 0;
|
|
17596
|
-
while (cursor && scanned < maxScan) {
|
|
17597
|
-
scanned += 1;
|
|
17598
|
-
const nextCursor = cursor.nextSibling;
|
|
17599
|
-
if (cursor.nodeType !== 1 || cursor.namespaceURI !== WORD_MAIN_NS || cursor.localName !== "p") {
|
|
17600
|
-
cursor = nextCursor;
|
|
17601
|
-
continue;
|
|
17602
|
-
}
|
|
17603
|
-
const text2 = String(paragraphTextGetter(cursor) || "").trim();
|
|
17604
|
-
if (!text2) {
|
|
17605
|
-
if (block.length > 1) break;
|
|
17606
|
-
cursor = nextCursor;
|
|
17607
|
-
continue;
|
|
17608
|
-
}
|
|
17609
|
-
if (!isLikelyStructuredTableSourceParagraph(text2)) break;
|
|
17610
|
-
block.push(cursor);
|
|
17611
|
-
cursor = nextCursor;
|
|
17612
|
-
}
|
|
17613
|
-
return block.length > 1 ? block : null;
|
|
17614
|
-
}
|
|
17615
|
-
function synthesizeTableMarkdownFromMultilineCellEdit(targetParagraph, modifiedText, options = {}) {
|
|
17616
|
-
const onInfo = typeof options.onInfo === "function" ? options.onInfo : () => {
|
|
17617
|
-
};
|
|
17618
|
-
const onWarn = typeof options.onWarn === "function" ? options.onWarn : () => {
|
|
17619
|
-
};
|
|
17620
|
-
const rawModified = String(modifiedText || "");
|
|
17621
|
-
if (!rawModified.includes("\n")) return null;
|
|
17622
|
-
if (isMarkdownTableText(rawModified)) return null;
|
|
17623
|
-
const lines = rawModified.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean);
|
|
17624
|
-
if (lines.length < 2) return null;
|
|
17625
|
-
const tableElement = options.tableElement || findContainingWordElement(targetParagraph, "tbl");
|
|
17626
|
-
const rowElement = findContainingWordElement(targetParagraph, "tr");
|
|
17627
|
-
const cellElement = findContainingWordElement(targetParagraph, "tc");
|
|
17628
|
-
if (!tableElement || !rowElement || !cellElement) return null;
|
|
17629
|
-
const currentParagraphText = normalizeWhitespaceForTargeting(
|
|
17630
|
-
options.currentParagraphText || getParagraphText(targetParagraph)
|
|
17631
|
-
);
|
|
17632
|
-
const firstLine = normalizeWhitespaceForTargeting(lines[0]);
|
|
17633
|
-
if (currentParagraphText && firstLine && firstLine !== currentParagraphText) {
|
|
17634
|
-
onWarn("[Table] Multiline cell text did not anchor to original cell text; skipping table-row synthesis heuristic.");
|
|
17635
|
-
return null;
|
|
17636
|
-
}
|
|
17637
|
-
const { matrix, rowElements, columnCount } = extractTableMatrix(tableElement);
|
|
17638
|
-
if (matrix.length === 0 || columnCount === 0) return null;
|
|
17639
|
-
const rowIndex = rowElements.indexOf(rowElement);
|
|
17640
|
-
const cellElements = getDirectWordChildren(rowElement, "tc");
|
|
17641
|
-
const colIndex = cellElements.indexOf(cellElement);
|
|
17642
|
-
if (rowIndex < 0 || colIndex < 0 || colIndex >= columnCount) return null;
|
|
17643
|
-
matrix[rowIndex][colIndex] = lines[0];
|
|
17644
|
-
const mirrorAcrossColumns = isSymmetricLabelRow(matrix[rowIndex]);
|
|
17645
|
-
if (mirrorAcrossColumns) {
|
|
17646
|
-
onInfo("[Table] Symmetric row detected; mirroring inserted row values across columns.");
|
|
17647
|
-
}
|
|
17648
|
-
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
17649
|
-
const insertIndex = rowIndex + i2;
|
|
17650
|
-
const extraValue = lines[i2];
|
|
17651
|
-
if (insertIndex < matrix.length && !normalizeWhitespaceForTargeting(matrix[insertIndex][colIndex])) {
|
|
17652
|
-
if (mirrorAcrossColumns) {
|
|
17653
|
-
for (let col = 0; col < columnCount; col++) {
|
|
17654
|
-
if (!normalizeWhitespaceForTargeting(matrix[insertIndex][col])) {
|
|
17655
|
-
matrix[insertIndex][col] = extraValue;
|
|
17656
|
-
}
|
|
17657
|
-
}
|
|
17658
|
-
} else {
|
|
17659
|
-
matrix[insertIndex][colIndex] = extraValue;
|
|
17660
|
-
}
|
|
17661
|
-
} else {
|
|
17662
|
-
const newRow = new Array(columnCount).fill("");
|
|
17663
|
-
if (mirrorAcrossColumns) {
|
|
17664
|
-
for (let col = 0; col < columnCount; col++) newRow[col] = extraValue;
|
|
17665
|
-
} else {
|
|
17666
|
-
newRow[colIndex] = extraValue;
|
|
17667
|
-
}
|
|
17668
|
-
matrix.splice(Math.min(insertIndex, matrix.length), 0, newRow);
|
|
17669
|
-
}
|
|
17670
|
-
}
|
|
17671
|
-
const markdown = tableMatrixToMarkdown(matrix, columnCount);
|
|
17672
|
-
if (!markdown) return null;
|
|
17673
|
-
onInfo("[Table] Synthesized full markdown table from multiline cell edit for table-scope reconciliation.");
|
|
17674
|
-
return markdown;
|
|
17675
|
-
}
|
|
17676
|
-
|
|
17677
|
-
// services/numbering-helpers.js
|
|
17678
|
-
function parseIntegerAttribute(element, names) {
|
|
17679
|
-
if (!element || !Array.isArray(names)) return null;
|
|
17680
|
-
for (const name of names) {
|
|
17681
|
-
const raw = element.getAttribute(name);
|
|
17682
|
-
if (raw == null || raw === "") continue;
|
|
17683
|
-
const parsed = Number.parseInt(String(raw), 10);
|
|
17684
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
17685
|
-
}
|
|
17686
|
-
return null;
|
|
17687
|
-
}
|
|
17688
|
-
function nextAvailableId(startId, occupiedIds, maxPreferred = null) {
|
|
17689
|
-
let candidate = Number.isInteger(startId) && startId > 0 ? startId : 1;
|
|
17690
|
-
const occupied = occupiedIds instanceof Set ? occupiedIds : /* @__PURE__ */ new Set();
|
|
17691
|
-
while (occupied.has(candidate)) {
|
|
17692
|
-
candidate += 1;
|
|
17693
|
-
}
|
|
17694
|
-
if (Number.isInteger(maxPreferred) && maxPreferred > 0 && candidate > maxPreferred) {
|
|
17695
|
-
for (let probe = 1; probe <= maxPreferred; probe += 1) {
|
|
17696
|
-
if (!occupied.has(probe)) return probe;
|
|
17697
|
-
}
|
|
17698
|
-
}
|
|
17699
|
-
return candidate;
|
|
17700
|
-
}
|
|
17701
|
-
function createDynamicNumberingIdState(numberingXml, options = {}) {
|
|
17702
|
-
const minId = Number.isInteger(options?.minId) && options.minId > 0 ? options.minId : 1;
|
|
17703
|
-
const maxPreferred = Number.isInteger(options?.maxPreferred) && options.maxPreferred >= minId ? options.maxPreferred : 32767;
|
|
17704
|
-
const usedNumIds = /* @__PURE__ */ new Set();
|
|
17705
|
-
const usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
17706
|
-
if (String(numberingXml || "").trim()) {
|
|
17707
|
-
try {
|
|
17708
|
-
const numberingDoc = parseOoxml(numberingXml);
|
|
17709
|
-
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
17710
|
-
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
17711
|
-
for (const abstractNum of abstractNums) {
|
|
17712
|
-
const id = parseIntegerAttribute(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
17713
|
-
if (id != null) usedAbstractNumIds.add(id);
|
|
17714
|
-
}
|
|
17715
|
-
for (const num of nums) {
|
|
17716
|
-
const id = parseIntegerAttribute(num, ["w:numId", "numId"]);
|
|
17717
|
-
if (id != null) usedNumIds.add(id);
|
|
17718
|
-
}
|
|
17719
|
-
} catch {
|
|
17720
|
-
}
|
|
17721
|
-
}
|
|
17722
|
-
const maxUsedNumId = usedNumIds.size > 0 ? Math.max(...usedNumIds) : 0;
|
|
17723
|
-
const maxUsedAbstractNumId = usedAbstractNumIds.size > 0 ? Math.max(...usedAbstractNumIds) : 0;
|
|
17724
|
-
const baseNumId = Math.max(minId, maxUsedNumId + 1);
|
|
17725
|
-
const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
|
|
17726
|
-
return {
|
|
17727
|
-
nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
|
|
17728
|
-
nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
|
|
17729
|
-
usedNumIds,
|
|
17730
|
-
usedAbstractNumIds,
|
|
17731
|
-
minId,
|
|
17732
|
-
maxPreferred
|
|
17733
|
-
};
|
|
17734
|
-
}
|
|
17735
|
-
function normalizeNumberingIdState(state) {
|
|
17736
|
-
if (!state || typeof state !== "object") return null;
|
|
17737
|
-
if (!(state.usedNumIds instanceof Set)) state.usedNumIds = /* @__PURE__ */ new Set();
|
|
17738
|
-
if (!(state.usedAbstractNumIds instanceof Set)) state.usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
17739
|
-
if (!Number.isInteger(state.minId) || state.minId < 1) {
|
|
17740
|
-
state.minId = 1;
|
|
17741
|
-
}
|
|
17742
|
-
if (!Number.isInteger(state.maxPreferred) || state.maxPreferred < state.minId) {
|
|
17743
|
-
state.maxPreferred = 32767;
|
|
17744
|
-
}
|
|
17745
|
-
if (!Number.isInteger(state.nextNumId) || state.nextNumId < state.minId) {
|
|
17746
|
-
state.nextNumId = state.minId;
|
|
17747
|
-
}
|
|
17748
|
-
if (!Number.isInteger(state.nextAbstractNumId) || state.nextAbstractNumId < state.minId) {
|
|
17749
|
-
state.nextAbstractNumId = state.minId;
|
|
17750
|
-
}
|
|
17751
|
-
state.nextNumId = nextAvailableId(state.nextNumId, state.usedNumIds, state.maxPreferred);
|
|
17752
|
-
state.nextAbstractNumId = nextAvailableId(state.nextAbstractNumId, state.usedAbstractNumIds, state.maxPreferred);
|
|
17753
|
-
return state;
|
|
17754
|
-
}
|
|
17755
|
-
function reserveNextNumberingId(state, kind = "num") {
|
|
17756
|
-
const normalized = normalizeNumberingIdState(state);
|
|
17757
|
-
if (!normalized) return null;
|
|
17758
|
-
const useAbstract = kind === "abstract";
|
|
17759
|
-
const id = useAbstract ? normalized.nextAbstractNumId : normalized.nextNumId;
|
|
17760
|
-
if (!Number.isInteger(id) || id < 1) return null;
|
|
17761
|
-
if (useAbstract) {
|
|
17762
|
-
normalized.usedAbstractNumIds.add(id);
|
|
17763
|
-
normalized.nextAbstractNumId = nextAvailableId(id + 1, normalized.usedAbstractNumIds, normalized.maxPreferred);
|
|
17764
|
-
} else {
|
|
17765
|
-
normalized.usedNumIds.add(id);
|
|
17766
|
-
normalized.nextNumId = nextAvailableId(id + 1, normalized.usedNumIds, normalized.maxPreferred);
|
|
17767
|
-
}
|
|
17768
|
-
return id;
|
|
17769
|
-
}
|
|
17770
|
-
function reserveNextNumberingIdPair(state) {
|
|
17771
|
-
const numId = reserveNextNumberingId(state, "num");
|
|
17772
|
-
const abstractNumId = reserveNextNumberingId(state, "abstract");
|
|
17773
|
-
if (numId == null || abstractNumId == null) return null;
|
|
17774
|
-
return { numId, abstractNumId };
|
|
17530
|
+
function reserveNextNumberingIdPair(state) {
|
|
17531
|
+
const numId = reserveNextNumberingId(state, "num");
|
|
17532
|
+
const abstractNumId = reserveNextNumberingId(state, "abstract");
|
|
17533
|
+
if (numId == null || abstractNumId == null) return null;
|
|
17534
|
+
return { numId, abstractNumId };
|
|
17775
17535
|
}
|
|
17776
17536
|
function hasXmlParseError(doc) {
|
|
17777
17537
|
if (!doc || !doc.documentElement) return true;
|
|
@@ -17824,134 +17584,507 @@ function overwriteParagraphNumIds(paragraphNodes, targetNumId) {
|
|
|
17824
17584
|
}
|
|
17825
17585
|
}
|
|
17826
17586
|
}
|
|
17827
|
-
function extractFirstParagraphNumId(paragraphNodes) {
|
|
17828
|
-
for (const node of paragraphNodes || []) {
|
|
17829
|
-
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
17830
|
-
for (const numIdNode of numIdNodes) {
|
|
17831
|
-
const numId = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17832
|
-
if (numId != null) return String(numId);
|
|
17587
|
+
function extractFirstParagraphNumId(paragraphNodes) {
|
|
17588
|
+
for (const node of paragraphNodes || []) {
|
|
17589
|
+
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
17590
|
+
for (const numIdNode of numIdNodes) {
|
|
17591
|
+
const numId = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17592
|
+
if (numId != null) return String(numId);
|
|
17593
|
+
}
|
|
17594
|
+
}
|
|
17595
|
+
return null;
|
|
17596
|
+
}
|
|
17597
|
+
function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startAt) {
|
|
17598
|
+
const safeNumId = String(numId);
|
|
17599
|
+
const safeAbstractNumId = String(abstractNumId);
|
|
17600
|
+
const safeStartAt = Number.isInteger(startAt) && startAt > 0 ? startAt : 1;
|
|
17601
|
+
const levelsXml = Array.from({ length: 9 }, (_, level) => {
|
|
17602
|
+
const lvlText = Array.from({ length: level + 1 }, (_2, i2) => `%${i2 + 1}`).join(".") + ".";
|
|
17603
|
+
const left = 720 * (level + 1);
|
|
17604
|
+
return `
|
|
17605
|
+
<w:lvl w:ilvl="${level}">
|
|
17606
|
+
<w:start w:val="1"/>
|
|
17607
|
+
<w:numFmt w:val="decimal"/>
|
|
17608
|
+
<w:lvlText w:val="${lvlText}"/>
|
|
17609
|
+
<w:lvlJc w:val="left"/>
|
|
17610
|
+
<w:pPr><w:ind w:left="${left}" w:hanging="360"/></w:pPr>
|
|
17611
|
+
</w:lvl>`;
|
|
17612
|
+
}).join("");
|
|
17613
|
+
return `
|
|
17614
|
+
<w:numbering xmlns:w="${WORD_MAIN_NS}">
|
|
17615
|
+
<w:abstractNum w:abstractNumId="${safeAbstractNumId}">
|
|
17616
|
+
<w:multiLevelType w:val="multilevel"/>
|
|
17617
|
+
${levelsXml}
|
|
17618
|
+
</w:abstractNum>
|
|
17619
|
+
<w:num w:numId="${safeNumId}">
|
|
17620
|
+
<w:abstractNumId w:val="${safeAbstractNumId}"/>
|
|
17621
|
+
<w:lvlOverride w:ilvl="0">
|
|
17622
|
+
<w:startOverride w:val="${safeStartAt}"/>
|
|
17623
|
+
</w:lvlOverride>
|
|
17624
|
+
</w:num>
|
|
17625
|
+
</w:numbering>`.trim();
|
|
17626
|
+
}
|
|
17627
|
+
function isTrackedDeletedParagraph(node) {
|
|
17628
|
+
if (!node || node.nodeType !== 1 || node.localName !== "p") return false;
|
|
17629
|
+
const pPr = Array.from(node.childNodes || []).find((child) => isDirectWordChild(child, "pPr"));
|
|
17630
|
+
const rPr = pPr && Array.from(pPr.childNodes || []).find((child) => isDirectWordChild(child, "rPr"));
|
|
17631
|
+
return !!rPr && Array.from(rPr.childNodes || []).some((child) => isDirectWordChild(child, "del"));
|
|
17632
|
+
}
|
|
17633
|
+
function paragraphNumIdNodes(node) {
|
|
17634
|
+
return Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []).filter((numIdNode) => numIdNode.parentNode?.localName === "numPr");
|
|
17635
|
+
}
|
|
17636
|
+
function levelFormats(abstractNum) {
|
|
17637
|
+
const formats = /* @__PURE__ */ new Map();
|
|
17638
|
+
for (const lvl of Array.from(abstractNum?.getElementsByTagNameNS?.("*", "lvl") || [])) {
|
|
17639
|
+
const ilvl = getElementId2(lvl, ["w:ilvl", "ilvl"]);
|
|
17640
|
+
const fmt = Array.from(lvl.getElementsByTagNameNS("*", "numFmt"))[0]?.getAttribute("w:val") || null;
|
|
17641
|
+
if (ilvl != null && fmt) formats.set(ilvl, fmt);
|
|
17642
|
+
}
|
|
17643
|
+
return formats;
|
|
17644
|
+
}
|
|
17645
|
+
function normalizeListFormat(format) {
|
|
17646
|
+
return format === "bullet" || format === "circle" || format === "square" ? "bullet" : format;
|
|
17647
|
+
}
|
|
17648
|
+
function abstractNumForNumId(numberingDoc, numId) {
|
|
17649
|
+
const num = Array.from(numberingDoc.getElementsByTagNameNS("*", "num")).find((candidate) => getElementId2(candidate, ["w:numId", "numId"]) === numId);
|
|
17650
|
+
const abstractId = num && getElementId2(Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0], ["w:val", "val"]);
|
|
17651
|
+
if (abstractId == null) return null;
|
|
17652
|
+
return Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum")).find((candidate) => getElementId2(candidate, ["w:abstractNumId", "abstractNumId"]) === abstractId) || null;
|
|
17653
|
+
}
|
|
17654
|
+
function reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, numberingIdState) {
|
|
17655
|
+
const sourceXml = numberingIdState?.sourceNumberingXml;
|
|
17656
|
+
if (!numberingXml || !String(sourceXml || "").trim() || !Array.isArray(replacementNodes)) return null;
|
|
17657
|
+
const sourceNumIds = /* @__PURE__ */ new Set();
|
|
17658
|
+
for (const paragraph of sourceParagraphs || []) {
|
|
17659
|
+
for (const numIdNode of paragraphNumIdNodes(paragraph)) {
|
|
17660
|
+
const id = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17661
|
+
if (id != null && id !== 0) sourceNumIds.add(id);
|
|
17662
|
+
}
|
|
17663
|
+
}
|
|
17664
|
+
if (sourceNumIds.size !== 1) return null;
|
|
17665
|
+
const [sourceNumId] = sourceNumIds;
|
|
17666
|
+
const sourceDoc = parseOoxml(sourceXml);
|
|
17667
|
+
const payloadDoc = parseOoxml(numberingXml);
|
|
17668
|
+
if (hasXmlParseError(sourceDoc) || hasXmlParseError(payloadDoc)) return null;
|
|
17669
|
+
const sourceAbstract = abstractNumForNumId(sourceDoc, sourceNumId);
|
|
17670
|
+
if (!sourceAbstract) return null;
|
|
17671
|
+
const sourceFormats = levelFormats(sourceAbstract);
|
|
17672
|
+
const newParagraphNumIds = [];
|
|
17673
|
+
for (const node of replacementNodes) {
|
|
17674
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17675
|
+
for (const numIdNode of paragraphNumIdNodes(node)) {
|
|
17676
|
+
const ilvlNode = Array.from(numIdNode.parentNode.childNodes || []).find((child) => child.localName === "ilvl");
|
|
17677
|
+
newParagraphNumIds.push({
|
|
17678
|
+
numId: getElementId2(numIdNode, ["w:val", "val"]),
|
|
17679
|
+
ilvl: ilvlNode ? getElementId2(ilvlNode, ["w:val", "val"]) ?? 0 : 0
|
|
17680
|
+
});
|
|
17681
|
+
}
|
|
17682
|
+
}
|
|
17683
|
+
if (newParagraphNumIds.length === 0) return null;
|
|
17684
|
+
const payloadFormats = /* @__PURE__ */ new Map();
|
|
17685
|
+
for (const { numId, ilvl } of newParagraphNumIds) {
|
|
17686
|
+
if (numId == null) return null;
|
|
17687
|
+
if (!payloadFormats.has(numId)) {
|
|
17688
|
+
const abstract = abstractNumForNumId(payloadDoc, numId);
|
|
17689
|
+
if (!abstract) return null;
|
|
17690
|
+
payloadFormats.set(numId, levelFormats(abstract));
|
|
17691
|
+
}
|
|
17692
|
+
const generated = payloadFormats.get(numId).get(ilvl);
|
|
17693
|
+
const existing = sourceFormats.get(ilvl);
|
|
17694
|
+
if (!generated || !existing || normalizeListFormat(generated) !== normalizeListFormat(existing)) return null;
|
|
17695
|
+
}
|
|
17696
|
+
const clonedNodes = replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node);
|
|
17697
|
+
for (const node of clonedNodes) {
|
|
17698
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17699
|
+
for (const numIdNode of paragraphNumIdNodes(node)) setElementVal2(numIdNode, sourceNumId);
|
|
17700
|
+
}
|
|
17701
|
+
return { numId: sourceNumId, replacementNodes: clonedNodes };
|
|
17702
|
+
}
|
|
17703
|
+
function remapNumberingPayloadForDocument(numberingXml, replacementNodes, numberingIdState) {
|
|
17704
|
+
const numberingDoc = parseOoxml(numberingXml);
|
|
17705
|
+
if (hasXmlParseError(numberingDoc)) {
|
|
17706
|
+
return {
|
|
17707
|
+
numberingXml: String(numberingXml || ""),
|
|
17708
|
+
replacementNodes: Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : []
|
|
17709
|
+
};
|
|
17710
|
+
}
|
|
17711
|
+
const abstractNumMap = /* @__PURE__ */ new Map();
|
|
17712
|
+
const numIdMap = /* @__PURE__ */ new Map();
|
|
17713
|
+
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
17714
|
+
for (const abstractNum of abstractNums) {
|
|
17715
|
+
const oldId = getElementId2(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
17716
|
+
if (oldId == null) continue;
|
|
17717
|
+
const newId = reserveNextNumberingId(numberingIdState, "abstract");
|
|
17718
|
+
if (newId == null) continue;
|
|
17719
|
+
abstractNumMap.set(oldId, newId);
|
|
17720
|
+
setElementId(abstractNum, "w:abstractNumId", newId);
|
|
17721
|
+
}
|
|
17722
|
+
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
17723
|
+
for (const num of nums) {
|
|
17724
|
+
const oldNumId = getElementId2(num, ["w:numId", "numId"]);
|
|
17725
|
+
if (oldNumId == null) continue;
|
|
17726
|
+
const newNumId = reserveNextNumberingId(numberingIdState, "num");
|
|
17727
|
+
if (newNumId == null) continue;
|
|
17728
|
+
numIdMap.set(oldNumId, newNumId);
|
|
17729
|
+
setElementId(num, "w:numId", newNumId);
|
|
17730
|
+
const abstractNumIdNode = Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0] || null;
|
|
17731
|
+
if (abstractNumIdNode) {
|
|
17732
|
+
const oldAbsRef = getElementId2(abstractNumIdNode, ["w:val", "val"]);
|
|
17733
|
+
if (oldAbsRef != null && abstractNumMap.has(oldAbsRef)) {
|
|
17734
|
+
setElementVal2(abstractNumIdNode, abstractNumMap.get(oldAbsRef));
|
|
17735
|
+
}
|
|
17736
|
+
}
|
|
17737
|
+
}
|
|
17738
|
+
const clonedNodes = Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : [];
|
|
17739
|
+
for (const node of clonedNodes) {
|
|
17740
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17741
|
+
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
17742
|
+
for (const numIdNode of numIdNodes) {
|
|
17743
|
+
const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17744
|
+
if (oldNumRef != null && numIdMap.has(oldNumRef)) {
|
|
17745
|
+
setElementVal2(numIdNode, numIdMap.get(oldNumRef));
|
|
17746
|
+
}
|
|
17747
|
+
}
|
|
17748
|
+
}
|
|
17749
|
+
return {
|
|
17750
|
+
numberingXml: serializeOoxml(numberingDoc),
|
|
17751
|
+
replacementNodes: clonedNodes
|
|
17752
|
+
};
|
|
17753
|
+
}
|
|
17754
|
+
function mergeNumberingXmlBySchemaOrder(existingNumberingXml, incomingNumberingXml) {
|
|
17755
|
+
const existingText = String(existingNumberingXml || "");
|
|
17756
|
+
const incomingText = String(incomingNumberingXml || "");
|
|
17757
|
+
if (!incomingText.trim()) return existingText;
|
|
17758
|
+
if (!existingText.trim()) return incomingText;
|
|
17759
|
+
try {
|
|
17760
|
+
const existingDoc = parseOoxml(existingText);
|
|
17761
|
+
const incomingDoc = parseOoxml(incomingText);
|
|
17762
|
+
if (hasXmlParseError(existingDoc) || hasXmlParseError(incomingDoc)) {
|
|
17763
|
+
return existingText;
|
|
17764
|
+
}
|
|
17765
|
+
const existingRoot = existingDoc.documentElement;
|
|
17766
|
+
const incomingRoot = incomingDoc.documentElement;
|
|
17767
|
+
if (!existingRoot || !incomingRoot) return existingText;
|
|
17768
|
+
const existingAbstractIds = new Set(
|
|
17769
|
+
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "abstractNum")).map((node) => parseIntegerAttribute(node, ["w:abstractNumId", "abstractNumId"])).filter((id) => id != null)
|
|
17770
|
+
);
|
|
17771
|
+
const existingNumIds = new Set(
|
|
17772
|
+
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "num")).map((node) => parseIntegerAttribute(node, ["w:numId", "numId"])).filter((id) => id != null)
|
|
17773
|
+
);
|
|
17774
|
+
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
17775
|
+
if (!isDirectWordChild(incomingNode, "abstractNum")) continue;
|
|
17776
|
+
const incomingId = parseIntegerAttribute(incomingNode, ["w:abstractNumId", "abstractNumId"]);
|
|
17777
|
+
if (incomingId == null || existingAbstractIds.has(incomingId)) continue;
|
|
17778
|
+
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "abstract");
|
|
17779
|
+
existingAbstractIds.add(incomingId);
|
|
17780
|
+
}
|
|
17781
|
+
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
17782
|
+
if (!isDirectWordChild(incomingNode, "num")) continue;
|
|
17783
|
+
const incomingId = parseIntegerAttribute(incomingNode, ["w:numId", "numId"]);
|
|
17784
|
+
if (incomingId == null || existingNumIds.has(incomingId)) continue;
|
|
17785
|
+
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "num");
|
|
17786
|
+
existingNumIds.add(incomingId);
|
|
17787
|
+
}
|
|
17788
|
+
return serializeOoxml(existingDoc);
|
|
17789
|
+
} catch {
|
|
17790
|
+
return existingText;
|
|
17791
|
+
}
|
|
17792
|
+
}
|
|
17793
|
+
|
|
17794
|
+
// core/redline-validation.js
|
|
17795
|
+
var REVISION_ID_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "rPrChange", "pPrChange"]);
|
|
17796
|
+
var REVISION_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
|
|
17797
|
+
function localNameOf3(node) {
|
|
17798
|
+
return String(node?.localName || node?.nodeName || "").replace(/^.*:/, "");
|
|
17799
|
+
}
|
|
17800
|
+
function elementsByLocalName(root, name) {
|
|
17801
|
+
return Array.from(root.getElementsByTagName("*")).filter((el) => localNameOf3(el) === name);
|
|
17802
|
+
}
|
|
17803
|
+
function wordAttribute3(node, name) {
|
|
17804
|
+
return node.getAttribute(`w:${name}`) || node.getAttribute(name) || "";
|
|
17805
|
+
}
|
|
17806
|
+
function xmlSpaceAttribute(node) {
|
|
17807
|
+
return node.getAttribute("xml:space") || node.getAttribute("space") || node.getAttributeNS?.("http://www.w3.org/XML/1998/namespace", "space") || "";
|
|
17808
|
+
}
|
|
17809
|
+
function isParagraphMarkRevision(node) {
|
|
17810
|
+
return localNameOf3(node.parentNode) === "rPr";
|
|
17811
|
+
}
|
|
17812
|
+
function parseOoxmlForValidation(oxml) {
|
|
17813
|
+
const attempt = (xml) => {
|
|
17814
|
+
const doc = parseXml(xml);
|
|
17815
|
+
const parseError = doc.getElementsByTagName("parsererror")[0];
|
|
17816
|
+
if (parseError) {
|
|
17817
|
+
throw new Error(parseError.textContent || "XML parse error");
|
|
17818
|
+
}
|
|
17819
|
+
return doc;
|
|
17820
|
+
};
|
|
17821
|
+
try {
|
|
17822
|
+
return { doc: attempt(oxml) };
|
|
17823
|
+
} catch {
|
|
17824
|
+
try {
|
|
17825
|
+
return { doc: attempt(`<w:root xmlns:w="${NS_W}">${oxml}</w:root>`) };
|
|
17826
|
+
} catch (error2) {
|
|
17827
|
+
return { error: error2?.message || "XML parse error" };
|
|
17828
|
+
}
|
|
17829
|
+
}
|
|
17830
|
+
}
|
|
17831
|
+
function validateRedlineOoxml(oxml) {
|
|
17832
|
+
const issues = [];
|
|
17833
|
+
const addIssue = (code, severity, message) => issues.push({ code, severity, message });
|
|
17834
|
+
const isDomNode = oxml && typeof oxml === "object" && (oxml.nodeType === 9 || oxml.nodeType === 1);
|
|
17835
|
+
if (!isDomNode && (typeof oxml !== "string" || oxml.trim() === "")) {
|
|
17836
|
+
addIssue("PARSE_ERROR", "error", "Input is not non-empty OOXML or a parsed XML DOM.");
|
|
17837
|
+
return { valid: false, issues };
|
|
17838
|
+
}
|
|
17839
|
+
const { doc, error: error2 } = isDomNode ? { doc: oxml.nodeType === 9 ? oxml : oxml.ownerDocument } : parseOoxmlForValidation(oxml);
|
|
17840
|
+
if (!doc) {
|
|
17841
|
+
addIssue("PARSE_ERROR", "error", `OOXML does not parse as XML: ${error2}`);
|
|
17842
|
+
return { valid: false, issues };
|
|
17843
|
+
}
|
|
17844
|
+
const insElements = elementsByLocalName(doc, "ins");
|
|
17845
|
+
const delElements = elementsByLocalName(doc, "del");
|
|
17846
|
+
const revisions = insElements.concat(delElements);
|
|
17847
|
+
for (const paragraph of elementsByLocalName(doc, "p")) {
|
|
17848
|
+
const nested = Array.from(paragraph.getElementsByTagName("*")).find((el) => el !== paragraph && localNameOf3(el) === "p");
|
|
17849
|
+
if (nested) {
|
|
17850
|
+
addIssue("NESTED_PARAGRAPH", "error", `<${paragraph.nodeName}> contains nested <${nested.nodeName}>.`);
|
|
17851
|
+
}
|
|
17852
|
+
}
|
|
17853
|
+
for (const body of elementsByLocalName(doc, "body")) {
|
|
17854
|
+
const children = Array.from(body.childNodes || []).filter((child) => child.nodeType === 1);
|
|
17855
|
+
const sectPrIndexes = children.map((child, index) => localNameOf3(child) === "sectPr" ? index : -1).filter((index) => index >= 0);
|
|
17856
|
+
if (sectPrIndexes.length > 1) {
|
|
17857
|
+
addIssue("MULTIPLE_BODY_SECTPR", "error", "<w:body> contains multiple direct <w:sectPr> elements.");
|
|
17858
|
+
} else if (sectPrIndexes.length === 1 && sectPrIndexes[0] !== children.length - 1) {
|
|
17859
|
+
addIssue("SECTPR_NOT_LAST", "error", "<w:sectPr> is not the last element child of <w:body>.");
|
|
17860
|
+
}
|
|
17861
|
+
}
|
|
17862
|
+
for (const revision of revisions) {
|
|
17863
|
+
const nested = Array.from(revision.getElementsByTagName("*")).filter((el) => el !== revision && ["ins", "del"].includes(localNameOf3(el)));
|
|
17864
|
+
const invalidNested = nested.find((candidate) => {
|
|
17865
|
+
if (localNameOf3(revision) !== "ins" || localNameOf3(candidate) !== "del") return true;
|
|
17866
|
+
return candidate.parentNode !== revision;
|
|
17867
|
+
});
|
|
17868
|
+
if (invalidNested) {
|
|
17869
|
+
addIssue(
|
|
17870
|
+
"NESTED_REVISION",
|
|
17871
|
+
"error",
|
|
17872
|
+
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") contains invalid nested <${invalidNested.nodeName}>.`
|
|
17873
|
+
);
|
|
17874
|
+
}
|
|
17875
|
+
}
|
|
17876
|
+
for (const del of delElements) {
|
|
17877
|
+
const plainTextNodes = elementsByLocalName(del, "t");
|
|
17878
|
+
if (plainTextNodes.length > 0) {
|
|
17879
|
+
addIssue(
|
|
17880
|
+
"DEL_CONTAINS_T",
|
|
17881
|
+
"error",
|
|
17882
|
+
`<w:del> (w:id="${wordAttribute3(del, "id")}") contains <w:t>; deleted text must use <w:delText>.`
|
|
17883
|
+
);
|
|
17884
|
+
}
|
|
17885
|
+
}
|
|
17886
|
+
for (const revision of revisions) {
|
|
17887
|
+
const missing = [];
|
|
17888
|
+
if (!wordAttribute3(revision, "id")) missing.push("w:id");
|
|
17889
|
+
if (!wordAttribute3(revision, "author")) missing.push("w:author");
|
|
17890
|
+
if (!REVISION_DATE_PATTERN.test(wordAttribute3(revision, "date"))) missing.push("w:date");
|
|
17891
|
+
if (missing.length > 0) {
|
|
17892
|
+
addIssue(
|
|
17893
|
+
"MISSING_REVISION_METADATA",
|
|
17894
|
+
"error",
|
|
17895
|
+
`<${revision.nodeName}> is missing or has malformed ${missing.join(", ")}.`
|
|
17896
|
+
);
|
|
17897
|
+
}
|
|
17898
|
+
}
|
|
17899
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
17900
|
+
for (const node of Array.from(doc.getElementsByTagName("*"))) {
|
|
17901
|
+
if (!REVISION_ID_ELEMENTS.has(localNameOf3(node))) continue;
|
|
17902
|
+
const id = wordAttribute3(node, "id");
|
|
17903
|
+
if (!id) continue;
|
|
17904
|
+
if (seenIds.has(id)) {
|
|
17905
|
+
addIssue("DUPLICATE_REVISION_ID", "error", `Revision id ${id} appears more than once.`);
|
|
17906
|
+
}
|
|
17907
|
+
seenIds.add(id);
|
|
17908
|
+
}
|
|
17909
|
+
const textNodes = elementsByLocalName(doc, "t").concat(elementsByLocalName(doc, "delText"));
|
|
17910
|
+
for (const node of textNodes) {
|
|
17911
|
+
const text2 = node.textContent || "";
|
|
17912
|
+
if (/^\s|\s$/.test(text2) && xmlSpaceAttribute(node) !== "preserve") {
|
|
17913
|
+
addIssue(
|
|
17914
|
+
"MISSING_SPACE_PRESERVE",
|
|
17915
|
+
"error",
|
|
17916
|
+
`<${node.nodeName}> has boundary whitespace without xml:space="preserve".`
|
|
17917
|
+
);
|
|
17918
|
+
}
|
|
17919
|
+
if (text2 === "") {
|
|
17920
|
+
addIssue("EMPTY_TEXT_ELEMENT", "warning", `<${node.nodeName}> is empty.`);
|
|
17921
|
+
}
|
|
17922
|
+
}
|
|
17923
|
+
for (const revision of revisions) {
|
|
17924
|
+
if (isParagraphMarkRevision(revision)) continue;
|
|
17925
|
+
const hasElementChild = Array.from(revision.childNodes || []).some((child) => child.nodeType === 1);
|
|
17926
|
+
if (!hasElementChild) {
|
|
17927
|
+
addIssue(
|
|
17928
|
+
"EMPTY_REVISION_WRAPPER",
|
|
17929
|
+
"warning",
|
|
17930
|
+
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") wraps no content.`
|
|
17931
|
+
);
|
|
17932
|
+
}
|
|
17933
|
+
}
|
|
17934
|
+
for (const resurrection of findForeignDeletedParagraphResurrections(doc)) {
|
|
17935
|
+
const ownerAuthor = resurrection.ownerAuthor || "unattributed";
|
|
17936
|
+
addIssue(
|
|
17937
|
+
"FOREIGN_PARAGRAPH_MARK_DELETION",
|
|
17938
|
+
"warning",
|
|
17939
|
+
`Paragraph deleted by ${ownerAuthor} also contains non-empty insertion content from another author; Accept/Reject lifecycle may not preserve the apparent restoration.`
|
|
17940
|
+
);
|
|
17941
|
+
}
|
|
17942
|
+
return { valid: !issues.some((issue) => issue.severity === "error"), issues };
|
|
17943
|
+
}
|
|
17944
|
+
|
|
17945
|
+
// core/table-targeting.js
|
|
17946
|
+
function getDirectWordChildren(element, localName2) {
|
|
17947
|
+
if (!element) return [];
|
|
17948
|
+
return Array.from(element.childNodes || []).filter(
|
|
17949
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === WORD_MAIN_NS && node.localName === localName2
|
|
17950
|
+
);
|
|
17951
|
+
}
|
|
17952
|
+
function escapeMarkdownCell(text2) {
|
|
17953
|
+
return String(text2 || "").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
17954
|
+
}
|
|
17955
|
+
function extractTableMatrix(tableElement) {
|
|
17956
|
+
const rowElements = getDirectWordChildren(tableElement, "tr");
|
|
17957
|
+
const matrix = rowElements.map((row) => {
|
|
17958
|
+
const cellElements = getDirectWordChildren(row, "tc");
|
|
17959
|
+
return cellElements.map((cell) => {
|
|
17960
|
+
const paragraphs = getDirectWordChildren(cell, "p");
|
|
17961
|
+
if (paragraphs.length === 0) return normalizeWhitespaceForTargeting(getParagraphText(cell));
|
|
17962
|
+
const lines = paragraphs.map((p) => normalizeWhitespaceForTargeting(getParagraphText(p))).filter(Boolean);
|
|
17963
|
+
return lines.join("\n");
|
|
17964
|
+
});
|
|
17965
|
+
});
|
|
17966
|
+
const columnCount = matrix.reduce((max2, row) => Math.max(max2, row.length), 0);
|
|
17967
|
+
matrix.forEach((row) => {
|
|
17968
|
+
while (row.length < columnCount) row.push("");
|
|
17969
|
+
});
|
|
17970
|
+
return { matrix, rowElements, columnCount };
|
|
17971
|
+
}
|
|
17972
|
+
function tableMatrixToMarkdown(matrix, columnCount) {
|
|
17973
|
+
if (!Array.isArray(matrix) || matrix.length === 0 || columnCount <= 0) return null;
|
|
17974
|
+
const normalized = matrix.map((row) => {
|
|
17975
|
+
const copy = Array.isArray(row) ? row.slice(0, columnCount) : [];
|
|
17976
|
+
while (copy.length < columnCount) copy.push("");
|
|
17977
|
+
return copy;
|
|
17978
|
+
});
|
|
17979
|
+
const header = normalized[0];
|
|
17980
|
+
const separator = new Array(columnCount).fill("---");
|
|
17981
|
+
const bodyRows = normalized.slice(1);
|
|
17982
|
+
const toLine = (row) => `| ${row.map((cell) => escapeMarkdownCell(cell)).join(" | ")} |`;
|
|
17983
|
+
return [toLine(header), toLine(separator), ...bodyRows.map(toLine)].join("\n");
|
|
17984
|
+
}
|
|
17985
|
+
function isSymmetricLabelRow(rowValues) {
|
|
17986
|
+
if (!Array.isArray(rowValues) || rowValues.length < 2) return false;
|
|
17987
|
+
const normalized = rowValues.map((value) => normalizeWhitespaceForTargeting(value)).filter(Boolean);
|
|
17988
|
+
if (normalized.length < 2) return false;
|
|
17989
|
+
return normalized.every((value) => value === normalized[0]);
|
|
17990
|
+
}
|
|
17991
|
+
function isLikelyStructuredTableSourceParagraph(text2) {
|
|
17992
|
+
const normalized = String(text2 || "").trim();
|
|
17993
|
+
if (!normalized) return false;
|
|
17994
|
+
if (/^and$/i.test(normalized)) return true;
|
|
17995
|
+
if (/^\[.*\]$/.test(normalized)) return true;
|
|
17996
|
+
if (/^\(.*\)$/.test(normalized)) return true;
|
|
17997
|
+
if (/:\s*$/.test(normalized)) return true;
|
|
17998
|
+
if (normalized.length <= 90 && !/[.!?]$/.test(normalized) && /[:\[\]()]/.test(normalized)) return true;
|
|
17999
|
+
if (/^[\[(]/.test(normalized)) return true;
|
|
18000
|
+
return false;
|
|
18001
|
+
}
|
|
18002
|
+
function inferTableReplacementParagraphBlock(startParagraph, options = {}) {
|
|
18003
|
+
const maxScan = Number.isInteger(options?.maxScan) && options.maxScan > 0 ? options.maxScan : 10;
|
|
18004
|
+
const paragraphTextGetter = typeof options?.getParagraphText === "function" ? options.getParagraphText : getParagraphText;
|
|
18005
|
+
if (!startParagraph || !startParagraph.parentNode) return null;
|
|
18006
|
+
const block = [startParagraph];
|
|
18007
|
+
let cursor = startParagraph.nextSibling;
|
|
18008
|
+
let scanned = 0;
|
|
18009
|
+
while (cursor && scanned < maxScan) {
|
|
18010
|
+
scanned += 1;
|
|
18011
|
+
const nextCursor = cursor.nextSibling;
|
|
18012
|
+
if (cursor.nodeType !== 1 || cursor.namespaceURI !== WORD_MAIN_NS || cursor.localName !== "p") {
|
|
18013
|
+
cursor = nextCursor;
|
|
18014
|
+
continue;
|
|
18015
|
+
}
|
|
18016
|
+
const text2 = String(paragraphTextGetter(cursor) || "").trim();
|
|
18017
|
+
if (!text2) {
|
|
18018
|
+
if (block.length > 1) break;
|
|
18019
|
+
cursor = nextCursor;
|
|
18020
|
+
continue;
|
|
17833
18021
|
}
|
|
18022
|
+
if (!isLikelyStructuredTableSourceParagraph(text2)) break;
|
|
18023
|
+
block.push(cursor);
|
|
18024
|
+
cursor = nextCursor;
|
|
17834
18025
|
}
|
|
17835
|
-
return null;
|
|
17836
|
-
}
|
|
17837
|
-
function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startAt) {
|
|
17838
|
-
const safeNumId = String(numId);
|
|
17839
|
-
const safeAbstractNumId = String(abstractNumId);
|
|
17840
|
-
const safeStartAt = Number.isInteger(startAt) && startAt > 0 ? startAt : 1;
|
|
17841
|
-
const levelsXml = Array.from({ length: 9 }, (_, level) => {
|
|
17842
|
-
const lvlText = Array.from({ length: level + 1 }, (_2, i2) => `%${i2 + 1}`).join(".") + ".";
|
|
17843
|
-
const left = 720 * (level + 1);
|
|
17844
|
-
return `
|
|
17845
|
-
<w:lvl w:ilvl="${level}">
|
|
17846
|
-
<w:start w:val="1"/>
|
|
17847
|
-
<w:numFmt w:val="decimal"/>
|
|
17848
|
-
<w:lvlText w:val="${lvlText}"/>
|
|
17849
|
-
<w:lvlJc w:val="left"/>
|
|
17850
|
-
<w:pPr><w:ind w:left="${left}" w:hanging="360"/></w:pPr>
|
|
17851
|
-
</w:lvl>`;
|
|
17852
|
-
}).join("");
|
|
17853
|
-
return `
|
|
17854
|
-
<w:numbering xmlns:w="${WORD_MAIN_NS}">
|
|
17855
|
-
<w:abstractNum w:abstractNumId="${safeAbstractNumId}">
|
|
17856
|
-
<w:multiLevelType w:val="multilevel"/>
|
|
17857
|
-
${levelsXml}
|
|
17858
|
-
</w:abstractNum>
|
|
17859
|
-
<w:num w:numId="${safeNumId}">
|
|
17860
|
-
<w:abstractNumId w:val="${safeAbstractNumId}"/>
|
|
17861
|
-
<w:lvlOverride w:ilvl="0">
|
|
17862
|
-
<w:startOverride w:val="${safeStartAt}"/>
|
|
17863
|
-
</w:lvlOverride>
|
|
17864
|
-
</w:num>
|
|
17865
|
-
</w:numbering>`.trim();
|
|
18026
|
+
return block.length > 1 ? block : null;
|
|
17866
18027
|
}
|
|
17867
|
-
function
|
|
17868
|
-
const
|
|
17869
|
-
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
|
|
18028
|
+
function synthesizeTableMarkdownFromMultilineCellEdit(targetParagraph, modifiedText, options = {}) {
|
|
18029
|
+
const onInfo = typeof options.onInfo === "function" ? options.onInfo : () => {
|
|
18030
|
+
};
|
|
18031
|
+
const onWarn = typeof options.onWarn === "function" ? options.onWarn : () => {
|
|
18032
|
+
};
|
|
18033
|
+
const rawModified = String(modifiedText || "");
|
|
18034
|
+
if (!rawModified.includes("\n")) return null;
|
|
18035
|
+
if (isMarkdownTableText(rawModified)) return null;
|
|
18036
|
+
const lines = rawModified.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean);
|
|
18037
|
+
if (lines.length < 2) return null;
|
|
18038
|
+
const tableElement = options.tableElement || findContainingWordElement(targetParagraph, "tbl");
|
|
18039
|
+
const rowElement = findContainingWordElement(targetParagraph, "tr");
|
|
18040
|
+
const cellElement = findContainingWordElement(targetParagraph, "tc");
|
|
18041
|
+
if (!tableElement || !rowElement || !cellElement) return null;
|
|
18042
|
+
const currentParagraphText = normalizeWhitespaceForTargeting(
|
|
18043
|
+
options.currentParagraphText || getParagraphText(targetParagraph)
|
|
18044
|
+
);
|
|
18045
|
+
const firstLine = normalizeWhitespaceForTargeting(lines[0]);
|
|
18046
|
+
if (currentParagraphText && firstLine && firstLine !== currentParagraphText) {
|
|
18047
|
+
onWarn("[Table] Multiline cell text did not anchor to original cell text; skipping table-row synthesis heuristic.");
|
|
18048
|
+
return null;
|
|
17874
18049
|
}
|
|
17875
|
-
const
|
|
17876
|
-
|
|
17877
|
-
const
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
17882
|
-
|
|
17883
|
-
|
|
17884
|
-
|
|
18050
|
+
const { matrix, rowElements, columnCount } = extractTableMatrix(tableElement);
|
|
18051
|
+
if (matrix.length === 0 || columnCount === 0) return null;
|
|
18052
|
+
const rowIndex = rowElements.indexOf(rowElement);
|
|
18053
|
+
const cellElements = getDirectWordChildren(rowElement, "tc");
|
|
18054
|
+
const colIndex = cellElements.indexOf(cellElement);
|
|
18055
|
+
if (rowIndex < 0 || colIndex < 0 || colIndex >= columnCount) return null;
|
|
18056
|
+
matrix[rowIndex][colIndex] = lines[0];
|
|
18057
|
+
const mirrorAcrossColumns = isSymmetricLabelRow(matrix[rowIndex]);
|
|
18058
|
+
if (mirrorAcrossColumns) {
|
|
18059
|
+
onInfo("[Table] Symmetric row detected; mirroring inserted row values across columns.");
|
|
17885
18060
|
}
|
|
17886
|
-
|
|
17887
|
-
|
|
17888
|
-
const
|
|
17889
|
-
if (
|
|
17890
|
-
|
|
17891
|
-
|
|
17892
|
-
|
|
17893
|
-
|
|
17894
|
-
|
|
17895
|
-
|
|
17896
|
-
|
|
17897
|
-
|
|
17898
|
-
setElementVal2(abstractNumIdNode, abstractNumMap.get(oldAbsRef));
|
|
18061
|
+
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
18062
|
+
const insertIndex = rowIndex + i2;
|
|
18063
|
+
const extraValue = lines[i2];
|
|
18064
|
+
if (insertIndex < matrix.length && !normalizeWhitespaceForTargeting(matrix[insertIndex][colIndex])) {
|
|
18065
|
+
if (mirrorAcrossColumns) {
|
|
18066
|
+
for (let col = 0; col < columnCount; col++) {
|
|
18067
|
+
if (!normalizeWhitespaceForTargeting(matrix[insertIndex][col])) {
|
|
18068
|
+
matrix[insertIndex][col] = extraValue;
|
|
18069
|
+
}
|
|
18070
|
+
}
|
|
18071
|
+
} else {
|
|
18072
|
+
matrix[insertIndex][colIndex] = extraValue;
|
|
17899
18073
|
}
|
|
17900
|
-
}
|
|
17901
|
-
|
|
17902
|
-
|
|
17903
|
-
|
|
17904
|
-
|
|
17905
|
-
|
|
17906
|
-
const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17907
|
-
if (oldNumRef != null && numIdMap.has(oldNumRef)) {
|
|
17908
|
-
setElementVal2(numIdNode, numIdMap.get(oldNumRef));
|
|
18074
|
+
} else {
|
|
18075
|
+
const newRow = new Array(columnCount).fill("");
|
|
18076
|
+
if (mirrorAcrossColumns) {
|
|
18077
|
+
for (let col = 0; col < columnCount; col++) newRow[col] = extraValue;
|
|
18078
|
+
} else {
|
|
18079
|
+
newRow[colIndex] = extraValue;
|
|
17909
18080
|
}
|
|
18081
|
+
matrix.splice(Math.min(insertIndex, matrix.length), 0, newRow);
|
|
17910
18082
|
}
|
|
17911
18083
|
}
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
}
|
|
17917
|
-
function mergeNumberingXmlBySchemaOrder(existingNumberingXml, incomingNumberingXml) {
|
|
17918
|
-
const existingText = String(existingNumberingXml || "");
|
|
17919
|
-
const incomingText = String(incomingNumberingXml || "");
|
|
17920
|
-
if (!incomingText.trim()) return existingText;
|
|
17921
|
-
if (!existingText.trim()) return incomingText;
|
|
17922
|
-
try {
|
|
17923
|
-
const existingDoc = parseOoxml(existingText);
|
|
17924
|
-
const incomingDoc = parseOoxml(incomingText);
|
|
17925
|
-
if (hasXmlParseError(existingDoc) || hasXmlParseError(incomingDoc)) {
|
|
17926
|
-
return existingText;
|
|
17927
|
-
}
|
|
17928
|
-
const existingRoot = existingDoc.documentElement;
|
|
17929
|
-
const incomingRoot = incomingDoc.documentElement;
|
|
17930
|
-
if (!existingRoot || !incomingRoot) return existingText;
|
|
17931
|
-
const existingAbstractIds = new Set(
|
|
17932
|
-
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "abstractNum")).map((node) => parseIntegerAttribute(node, ["w:abstractNumId", "abstractNumId"])).filter((id) => id != null)
|
|
17933
|
-
);
|
|
17934
|
-
const existingNumIds = new Set(
|
|
17935
|
-
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "num")).map((node) => parseIntegerAttribute(node, ["w:numId", "numId"])).filter((id) => id != null)
|
|
17936
|
-
);
|
|
17937
|
-
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
17938
|
-
if (!isDirectWordChild(incomingNode, "abstractNum")) continue;
|
|
17939
|
-
const incomingId = parseIntegerAttribute(incomingNode, ["w:abstractNumId", "abstractNumId"]);
|
|
17940
|
-
if (incomingId == null || existingAbstractIds.has(incomingId)) continue;
|
|
17941
|
-
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "abstract");
|
|
17942
|
-
existingAbstractIds.add(incomingId);
|
|
17943
|
-
}
|
|
17944
|
-
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
17945
|
-
if (!isDirectWordChild(incomingNode, "num")) continue;
|
|
17946
|
-
const incomingId = parseIntegerAttribute(incomingNode, ["w:numId", "numId"]);
|
|
17947
|
-
if (incomingId == null || existingNumIds.has(incomingId)) continue;
|
|
17948
|
-
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "num");
|
|
17949
|
-
existingNumIds.add(incomingId);
|
|
17950
|
-
}
|
|
17951
|
-
return serializeOoxml(existingDoc);
|
|
17952
|
-
} catch {
|
|
17953
|
-
return existingText;
|
|
17954
|
-
}
|
|
18084
|
+
const markdown = tableMatrixToMarkdown(matrix, columnCount);
|
|
18085
|
+
if (!markdown) return null;
|
|
18086
|
+
onInfo("[Table] Synthesized full markdown table from multiline cell edit for table-scope reconciliation.");
|
|
18087
|
+
return markdown;
|
|
17955
18088
|
}
|
|
17956
18089
|
|
|
17957
18090
|
// pipeline/ingestion-export.js
|
|
@@ -18721,6 +18854,8 @@ function areRevisionTokensEqual(a, b) {
|
|
|
18721
18854
|
var attr2 = (node, name) => node?.getAttribute?.(`w:${name}`) || node?.getAttribute?.(name) || "";
|
|
18722
18855
|
var descendants = (node, name) => Array.from(node?.getElementsByTagNameNS?.(NS_W, name) || []);
|
|
18723
18856
|
var first = (node, name) => descendants(node, name)[0] || null;
|
|
18857
|
+
var directChild2 = (node, name) => Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.localName === name && (!child.namespaceURI || child.namespaceURI === NS_W)) || null;
|
|
18858
|
+
var currentPPr = (paragraph) => directChild2(paragraph, "pPr");
|
|
18724
18859
|
function hasAncestor(node, localName2) {
|
|
18725
18860
|
let cursor = node?.parentNode;
|
|
18726
18861
|
while (cursor) {
|
|
@@ -18736,11 +18871,11 @@ function parseXml2(xml, partName, required = false) {
|
|
|
18736
18871
|
return { doc: parsed.doc, warnings: parsed.warnings || [] };
|
|
18737
18872
|
}
|
|
18738
18873
|
function paragraphListProperties(paragraph) {
|
|
18739
|
-
const numPr =
|
|
18874
|
+
const numPr = directChild2(currentPPr(paragraph), "numPr");
|
|
18740
18875
|
if (!numPr) return null;
|
|
18741
|
-
const numId = attr2(
|
|
18876
|
+
const numId = attr2(directChild2(numPr, "numId"), "val");
|
|
18742
18877
|
if (!numId || numId === "0") return null;
|
|
18743
|
-
return { numId, level: Number.parseInt(attr2(
|
|
18878
|
+
return { numId, level: Number.parseInt(attr2(directChild2(numPr, "ilvl"), "val") || "0", 10) || 0 };
|
|
18744
18879
|
}
|
|
18745
18880
|
function parseNumbering(numberingDoc) {
|
|
18746
18881
|
const abstracts = /* @__PURE__ */ new Map();
|
|
@@ -18825,11 +18960,11 @@ function createNumberingResolver(numberingDoc) {
|
|
|
18825
18960
|
};
|
|
18826
18961
|
}
|
|
18827
18962
|
function headingLevel(paragraph) {
|
|
18828
|
-
const pPr =
|
|
18829
|
-
const style = attr2(
|
|
18963
|
+
const pPr = currentPPr(paragraph);
|
|
18964
|
+
const style = attr2(directChild2(pPr, "pStyle"), "val");
|
|
18830
18965
|
const match = style.match(/^heading\s*([1-9])$/i);
|
|
18831
18966
|
if (match) return Math.min(Number(match[1]), 6);
|
|
18832
|
-
const outline = Number.parseInt(attr2(
|
|
18967
|
+
const outline = Number.parseInt(attr2(directChild2(pPr, "outlineLvl"), "val"), 10);
|
|
18833
18968
|
return Number.isInteger(outline) ? Math.min(outline + 1, 6) : null;
|
|
18834
18969
|
}
|
|
18835
18970
|
function structuralContext(paragraph, text2) {
|
|
@@ -19008,7 +19143,7 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
19008
19143
|
const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
|
|
19009
19144
|
const authors = revisionAuthors(paragraph);
|
|
19010
19145
|
const list = resolveNumbering(paragraphListProperties(paragraph));
|
|
19011
|
-
const styleId = attr2(
|
|
19146
|
+
const styleId = attr2(directChild2(currentPPr(paragraph), "pStyle"), "val") || null;
|
|
19012
19147
|
const structure = structuralContext(paragraph, text2);
|
|
19013
19148
|
const index = zeroIndex + 1;
|
|
19014
19149
|
const provision = list?.label && list.format !== "bullet" ? list.label : null;
|
|
@@ -21599,7 +21734,7 @@ function validateDocumentOperation(operation) {
|
|
|
21599
21734
|
}
|
|
21600
21735
|
};
|
|
21601
21736
|
}
|
|
21602
|
-
if (target.revisionView !== "accepted" || target.captureRef || target.occurrence != null || normalized.captureKey || normalized.targetEndDescriptor || normalized.targetEndRef != null
|
|
21737
|
+
if (target.revisionView !== "accepted" || target.captureRef || target.occurrence != null || normalized.captureKey || normalized.targetEndDescriptor || normalized.targetEndRef != null) {
|
|
21603
21738
|
return {
|
|
21604
21739
|
valid: false,
|
|
21605
21740
|
error: {
|
|
@@ -21621,6 +21756,15 @@ function validateDocumentOperation(operation) {
|
|
|
21621
21756
|
}
|
|
21622
21757
|
};
|
|
21623
21758
|
}
|
|
21759
|
+
if (replacementValidation.replacements.some((replacement) => /\r|\n/.test(replacement.find))) {
|
|
21760
|
+
return {
|
|
21761
|
+
valid: false,
|
|
21762
|
+
error: {
|
|
21763
|
+
code: "INVALID_OPERATION",
|
|
21764
|
+
message: 'Localized replacements v1 cannot span a line break; use one replacement per line or a full "modified" paragraph.'
|
|
21765
|
+
}
|
|
21766
|
+
};
|
|
21767
|
+
}
|
|
21624
21768
|
normalized.replacements = replacementValidation.replacements;
|
|
21625
21769
|
}
|
|
21626
21770
|
}
|
|
@@ -21815,6 +21959,12 @@ function validateDocumentOperation(operation) {
|
|
|
21815
21959
|
error: { code: "INVALID_OPERATION", message: 'Format operations require a non-empty "textToFormat" field.' }
|
|
21816
21960
|
};
|
|
21817
21961
|
}
|
|
21962
|
+
if (normalized.textOccurrence != null && (!Number.isInteger(normalized.textOccurrence) || normalized.textOccurrence < 1)) {
|
|
21963
|
+
return {
|
|
21964
|
+
valid: false,
|
|
21965
|
+
error: { code: "INVALID_OPERATION", message: "textOccurrence must be a positive integer when provided." }
|
|
21966
|
+
};
|
|
21967
|
+
}
|
|
21818
21968
|
if (!isRecord(normalized.properties)) {
|
|
21819
21969
|
return {
|
|
21820
21970
|
valid: false,
|
|
@@ -22271,7 +22421,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
22271
22421
|
};
|
|
22272
22422
|
const compiled = { ...sourceOperation };
|
|
22273
22423
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
22274
|
-
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
22424
|
+
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor && !(Array.isArray(operation.replacements) && (operation.targetDescriptor.paragraphId || operation.targetDescriptor.fingerprint));
|
|
22275
22425
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
22276
22426
|
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
22277
22427
|
try {
|
|
@@ -22609,6 +22759,17 @@ function deriveSingleParagraphPlainAdjacencyInsertion(currentParagraphText, modi
|
|
|
22609
22759
|
}
|
|
22610
22760
|
return null;
|
|
22611
22761
|
}
|
|
22762
|
+
function deriveParagraphThenTableAppend(modifiedText) {
|
|
22763
|
+
const lines = String(modifiedText || "").split(/\r?\n/g).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
22764
|
+
if (lines.length < 3) return null;
|
|
22765
|
+
const tableStart = lines.findIndex((line) => line.trim().startsWith("|"));
|
|
22766
|
+
if (tableStart !== 1) return null;
|
|
22767
|
+
const tableLines = lines.slice(1);
|
|
22768
|
+
if (!tableLines.every((line) => line.trim().startsWith("|"))) return null;
|
|
22769
|
+
const separator = tableLines[1].replace(/\s+/g, "");
|
|
22770
|
+
if (!/^\|:?-{3,}:?(\|:?-{3,}:?)+\|?$/.test(separator)) return null;
|
|
22771
|
+
return { paragraphMarkdown: lines[0].trim(), tableText: tableLines.join("\n") };
|
|
22772
|
+
}
|
|
22612
22773
|
|
|
22613
22774
|
// services/capture-engine.js
|
|
22614
22775
|
function createCaptureError(code, message) {
|
|
@@ -24394,6 +24555,81 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24394
24555
|
numberingXml: null
|
|
24395
24556
|
};
|
|
24396
24557
|
}
|
|
24558
|
+
const tableAppendCandidate = !useTableScope && !hasExplicitRangeScope && !targetListInfo && !containingTable && !targetEndRef ? deriveParagraphThenTableAppend(effectiveModifiedText) : null;
|
|
24559
|
+
const tableAppendParagraph = tableAppendCandidate ? preprocessMarkdown(tableAppendCandidate.paragraphMarkdown) : null;
|
|
24560
|
+
if (tableAppendCandidate && normalizeWhitespaceForTargeting(tableAppendParagraph.cleanText) === normalizeWhitespaceForTargeting(currentParagraphText)) {
|
|
24561
|
+
const tableData = parseTable(tableAppendCandidate.tableText);
|
|
24562
|
+
if (tableData.headers.length > 0 || tableData.rows.length > 0) {
|
|
24563
|
+
onInfo("[Table] Applying retained-paragraph table append heuristic.");
|
|
24564
|
+
const parent2 = targetParagraph.parentNode;
|
|
24565
|
+
if (!parent2) throw new Error("Target paragraph has no parent for table append");
|
|
24566
|
+
const followingNode = targetParagraph.nextSibling;
|
|
24567
|
+
if (tableAppendParagraph.formatHints.length > 0) {
|
|
24568
|
+
const formatted = await applyRedlineToOxml2(
|
|
24569
|
+
serializer.serializeToString(targetParagraph),
|
|
24570
|
+
currentParagraphText || targetText,
|
|
24571
|
+
tableAppendCandidate.paragraphMarkdown,
|
|
24572
|
+
{
|
|
24573
|
+
author,
|
|
24574
|
+
generateRedlines,
|
|
24575
|
+
existingRevisions: options.existingRevisions || "merge-same-author",
|
|
24576
|
+
structuredContent: false,
|
|
24577
|
+
_revisionIdAllocator: revisionIdAllocator
|
|
24578
|
+
}
|
|
24579
|
+
);
|
|
24580
|
+
if (formatted?.status === "error") {
|
|
24581
|
+
return {
|
|
24582
|
+
documentXml,
|
|
24583
|
+
hasChanges: false,
|
|
24584
|
+
numberingXml: null,
|
|
24585
|
+
status: "error",
|
|
24586
|
+
error: formatted.error
|
|
24587
|
+
};
|
|
24588
|
+
}
|
|
24589
|
+
if (formatted?.hasChanges) {
|
|
24590
|
+
if (typeof formatted.oxml !== "string") {
|
|
24591
|
+
throw new Error("Reconciliation engine did not return OOXML for a changed paragraph format");
|
|
24592
|
+
}
|
|
24593
|
+
for (const node of extractReplacementNodes(formatted.oxml).replacementNodes) {
|
|
24594
|
+
const imported = xmlDoc.importNode(node, true);
|
|
24595
|
+
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
24596
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(imported, targetParagraph));
|
|
24597
|
+
}
|
|
24598
|
+
options?._mutationRemovedNodes?.push(targetParagraph);
|
|
24599
|
+
parent2.removeChild(targetParagraph);
|
|
24600
|
+
}
|
|
24601
|
+
}
|
|
24602
|
+
const tableOoxml = generateTableOoxml(tableData, {
|
|
24603
|
+
generateRedlines,
|
|
24604
|
+
author,
|
|
24605
|
+
revisionIdAllocator,
|
|
24606
|
+
trackAsBlock: true
|
|
24607
|
+
});
|
|
24608
|
+
for (const node of extractReplacementNodes(tableOoxml).replacementNodes) {
|
|
24609
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(xmlDoc.importNode(node, true), followingNode));
|
|
24610
|
+
}
|
|
24611
|
+
const endsContainer = !followingNode || followingNode.nodeType === 1 && followingNode.namespaceURI === NS_W9 && followingNode.localName === "sectPr";
|
|
24612
|
+
if (endsContainer) {
|
|
24613
|
+
const trailing = createWordElement(xmlDoc, "w:p");
|
|
24614
|
+
const trailingPPr = clonePropertiesWithoutRevisionHistory(getDirectWordChild4(targetParagraph, "pPr"));
|
|
24615
|
+
if (trailingPPr) {
|
|
24616
|
+
for (const child of Array.from(trailingPPr.childNodes)) {
|
|
24617
|
+
if (child.nodeType === 1 && ["sectPr", "numPr"].includes(child.localName)) trailingPPr.removeChild(child);
|
|
24618
|
+
}
|
|
24619
|
+
trailing.appendChild(trailingPPr);
|
|
24620
|
+
}
|
|
24621
|
+
if (generateRedlines) markParagraphMarkInserted(xmlDoc, trailing, author);
|
|
24622
|
+
ensureParagraphIdsOnImportedNode(trailing, operationSession);
|
|
24623
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(trailing, followingNode));
|
|
24624
|
+
}
|
|
24625
|
+
normalizeBodySectionOrder(xmlDoc);
|
|
24626
|
+
return {
|
|
24627
|
+
documentXml: completedDocumentXml(xmlDoc, serializer, documentXml, operationSession),
|
|
24628
|
+
hasChanges: true,
|
|
24629
|
+
numberingXml: null
|
|
24630
|
+
};
|
|
24631
|
+
}
|
|
24632
|
+
}
|
|
24397
24633
|
const plainAdjacencyInsertionCandidate = !bypassSingleParagraphHeuristics && !useTableScope && !hasExplicitRangeScope && !targetListInfo ? deriveSingleParagraphPlainAdjacencyInsertion(currentParagraphText, effectiveModifiedText) : null;
|
|
24398
24634
|
if (plainAdjacencyInsertionCandidate) {
|
|
24399
24635
|
onInfo(
|
|
@@ -24409,7 +24645,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24409
24645
|
paragraphText,
|
|
24410
24646
|
generateRedlines ? createRevisionMetadata(author, xmlDoc) : null,
|
|
24411
24647
|
author,
|
|
24412
|
-
|
|
24648
|
+
// A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
|
|
24649
|
+
// Reject All removes the text but leaves an empty paragraph behind.
|
|
24650
|
+
{ generateRedlines, trackParagraphMark: true }
|
|
24413
24651
|
);
|
|
24414
24652
|
const imported = xmlDoc.importNode(plainParagraph, true);
|
|
24415
24653
|
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
@@ -24517,6 +24755,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24517
24755
|
explicitStructuredContent: options.explicitStructuredContent === true,
|
|
24518
24756
|
pairReplacements: options.pairReplacements === true,
|
|
24519
24757
|
insertionAffinity: options.insertionAffinity || null,
|
|
24758
|
+
literalTextEdit: options.literalTextEdit === true,
|
|
24520
24759
|
_revisionIdAllocator: revisionIdAllocator,
|
|
24521
24760
|
_isolatedTableCell: useTableScope
|
|
24522
24761
|
});
|
|
@@ -24540,6 +24779,14 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24540
24779
|
const extracted = extractReplacementNodes(result.oxml);
|
|
24541
24780
|
let replacementNodes = removeListPackagingSentinel(extracted.replacementNodes, result.warnings);
|
|
24542
24781
|
let numberingXml = extracted.numberingXml;
|
|
24782
|
+
if (numberingXml && runtimeContext?.numberingIdState && !useTableScope) {
|
|
24783
|
+
const sourceParagraphs = useListScope ? listScopeEdit.paragraphs : explicitRangeParagraphs || [targetParagraph];
|
|
24784
|
+
const continued = reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, runtimeContext.numberingIdState);
|
|
24785
|
+
if (continued) {
|
|
24786
|
+
replacementNodes = continued.replacementNodes;
|
|
24787
|
+
numberingXml = null;
|
|
24788
|
+
}
|
|
24789
|
+
}
|
|
24543
24790
|
if (numberingXml && runtimeContext?.numberingIdState) {
|
|
24544
24791
|
const normalizedNumbering = remapNumberingPayloadForDocument(numberingXml, replacementNodes, runtimeContext.numberingIdState);
|
|
24545
24792
|
replacementNodes = normalizedNumbering.replacementNodes;
|
|
@@ -24796,7 +25043,8 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24796
25043
|
spanRanges.push({ ...entry, start, end });
|
|
24797
25044
|
}
|
|
24798
25045
|
const targetDescriptor = options.targetDescriptor;
|
|
24799
|
-
const
|
|
25046
|
+
const explicitTextOccurrence = Number.isInteger(options.textOccurrence) && options.textOccurrence > 0 ? options.textOccurrence : null;
|
|
25047
|
+
const occurrence = explicitTextOccurrence ?? targetDescriptor?.occurrence ?? null;
|
|
24800
25048
|
let matchStart = -1;
|
|
24801
25049
|
if (occurrence != null && occurrence > 0) {
|
|
24802
25050
|
let count = 0;
|
|
@@ -24814,6 +25062,17 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24814
25062
|
} else {
|
|
24815
25063
|
matchStart = fullParaText.indexOf(textToFormat);
|
|
24816
25064
|
}
|
|
25065
|
+
if (matchStart === -1 && explicitTextOccurrence != null && fullParaText.includes(textToFormat)) {
|
|
25066
|
+
return {
|
|
25067
|
+
documentXml,
|
|
25068
|
+
hasChanges: false,
|
|
25069
|
+
status: "error",
|
|
25070
|
+
error: {
|
|
25071
|
+
code: "PATCH_SOURCE_NOT_FOUND",
|
|
25072
|
+
message: `Occurrence ${explicitTextOccurrence} of text to format was not found in target paragraph: "${textToFormat}".`
|
|
25073
|
+
}
|
|
25074
|
+
};
|
|
25075
|
+
}
|
|
24817
25076
|
if (matchStart === -1) {
|
|
24818
25077
|
return {
|
|
24819
25078
|
documentXml,
|
|
@@ -25096,9 +25355,11 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
25096
25355
|
...operation.existingRevisions ? { existingRevisions: operation.existingRevisions } : {},
|
|
25097
25356
|
structuredContent: typeof operation.structuredContent === "boolean" ? operation.structuredContent : options.structuredContent !== false,
|
|
25098
25357
|
explicitStructuredContent: operation.structuredContent === true,
|
|
25358
|
+
...operation._localizedReplacementCompilation && operation.structuredContent !== true ? { literalTextEdit: true } : {},
|
|
25099
25359
|
pairReplacements: typeof operation.pairReplacements === "boolean" ? operation.pairReplacements : options.pairReplacements !== false,
|
|
25100
25360
|
...operation.insertionAffinity ? { insertionAffinity: operation.insertionAffinity } : {},
|
|
25101
25361
|
...operation.formattingRevisionPolicy ? { formattingRevisionPolicy: operation.formattingRevisionPolicy } : {},
|
|
25362
|
+
...operation.operationKind === "format" && Number.isInteger(operation.textOccurrence) ? { textOccurrence: operation.textOccurrence } : {},
|
|
25102
25363
|
targetDescriptor: operation.targetDescriptor,
|
|
25103
25364
|
targetEndDescriptor: operation.targetEndDescriptor,
|
|
25104
25365
|
_compiledSourceId: operation._compiledSourceId || null,
|
|
@@ -25722,6 +25983,9 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25722
25983
|
const scheduled = dependencyPlan.scheduled;
|
|
25723
25984
|
const atomic = options.atomic === true;
|
|
25724
25985
|
const continueOnError = options.continueOnError !== false;
|
|
25986
|
+
if (runtimeContext && typeof runtimeContext === "object" && !runtimeContext.numberingIdState && typeof runtimeContext.numberingXml === "string" && runtimeContext.numberingXml.trim()) {
|
|
25987
|
+
runtimeContext.numberingIdState = createDynamicNumberingIdState(runtimeContext.numberingXml);
|
|
25988
|
+
}
|
|
25725
25989
|
const context = cloneBatchRuntimeContext(runtimeContext);
|
|
25726
25990
|
if (!(context.targetRefSnapshot instanceof Map)) {
|
|
25727
25991
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
@@ -28147,8 +28411,32 @@ function openDocx(input) {
|
|
|
28147
28411
|
}
|
|
28148
28412
|
|
|
28149
28413
|
// index.js
|
|
28414
|
+
function remapGeneratedNumberingForSource(result, options) {
|
|
28415
|
+
const sourceNumbering = typeof options?.numberingXml === "string" && options.numberingXml.trim() ? options.numberingXml : null;
|
|
28416
|
+
const state = options?.numberingIdState && typeof options.numberingIdState === "object" ? options.numberingIdState : sourceNumbering ? createDynamicNumberingIdState(sourceNumbering) : null;
|
|
28417
|
+
if (!state || !result?.hasChanges || typeof result.oxml !== "string" || !result.oxml.includes("/word/numbering.xml")) {
|
|
28418
|
+
return result;
|
|
28419
|
+
}
|
|
28420
|
+
const pkgDoc = parseOoxml(result.oxml);
|
|
28421
|
+
const parts = Array.from(pkgDoc.getElementsByTagNameNS("*", "part"));
|
|
28422
|
+
const payloadOf = (name) => {
|
|
28423
|
+
const part = parts.find((candidate) => (candidate.getAttribute("pkg:name") || candidate.getAttribute("name")) === name);
|
|
28424
|
+
const xmlData = part?.getElementsByTagNameNS("*", "xmlData")[0];
|
|
28425
|
+
return xmlData ? Array.from(xmlData.childNodes).find((node) => node.nodeType === 1) || null : null;
|
|
28426
|
+
};
|
|
28427
|
+
const numberingNode = payloadOf("/word/numbering.xml");
|
|
28428
|
+
const body = payloadOf("/word/document.xml")?.getElementsByTagNameNS("*", "body")[0];
|
|
28429
|
+
if (!numberingNode || !body) return result;
|
|
28430
|
+
const bodyNodes = Array.from(body.childNodes).filter((node) => node.nodeType === 1 && node.localName !== "sectPr");
|
|
28431
|
+
const remapped = remapNumberingPayloadForDocument(serializeOoxml(numberingNode), bodyNodes, state);
|
|
28432
|
+
const newNumbering = pkgDoc.importNode(parseOoxml(remapped.numberingXml).documentElement, true);
|
|
28433
|
+
numberingNode.parentNode.replaceChild(newNumbering, numberingNode);
|
|
28434
|
+
bodyNodes.forEach((node, index) => body.replaceChild(remapped.replacementNodes[index], node));
|
|
28435
|
+
return { ...result, oxml: serializeOoxml(pkgDoc) };
|
|
28436
|
+
}
|
|
28150
28437
|
async function applyRedlineToOxml3(oxml, originalText, modifiedText, options = {}) {
|
|
28151
|
-
const
|
|
28438
|
+
const rawResult = await applyRedlineToOxml(oxml, originalText, modifiedText, options);
|
|
28439
|
+
const result = remapGeneratedNumberingForSource(rawResult, options);
|
|
28152
28440
|
if (result?.useNativeApi && typeof result?.oxml !== "string") {
|
|
28153
28441
|
const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
|
|
28154
28442
|
return withOoxmlSourceType({
|