@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 CommonJS bundle v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone CommonJS 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;
|
|
@@ -9871,7 +9871,14 @@ function buildRunXmlWithHints(item, formatHints, options = {}) {
|
|
|
9871
9871
|
function buildSimpleRun(text2, rPrXml) {
|
|
9872
9872
|
if (!text2) return "";
|
|
9873
9873
|
const rPr = rPrXml || "";
|
|
9874
|
-
return `<w:r>${rPr}
|
|
9874
|
+
return `<w:r>${rPr}${buildRunContent(text2, "w:t")}</w:r>`;
|
|
9875
|
+
}
|
|
9876
|
+
function buildRunContent(text2, textTag) {
|
|
9877
|
+
return String(text2).split(/(\r\n|[\n\r\v\t])/).map((part) => {
|
|
9878
|
+
if (part === " ") return "<w:tab/>";
|
|
9879
|
+
if (part === "\n" || part === "\r" || part === "\r\n" || part === "\v") return "<w:br/>";
|
|
9880
|
+
return part ? `<${textTag} xml:space="preserve">${escapeXml(part)}</${textTag}>` : "";
|
|
9881
|
+
}).join("");
|
|
9875
9882
|
}
|
|
9876
9883
|
function buildDeletionXml(item, options = {}) {
|
|
9877
9884
|
const metadata = createRevisionMetadata(
|
|
@@ -9884,7 +9891,7 @@ function buildDeletionXml(item, options = {}) {
|
|
|
9884
9891
|
if (font) {
|
|
9885
9892
|
rPr = applyFont(rPr, font);
|
|
9886
9893
|
}
|
|
9887
|
-
return `<w:del w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}"><w:r>${rPr}
|
|
9894
|
+
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>`;
|
|
9888
9895
|
}
|
|
9889
9896
|
function buildInsertionXml(item, formatHints, options = {}) {
|
|
9890
9897
|
const metadata = createRevisionMetadata(
|
|
@@ -10225,7 +10232,7 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10225
10232
|
revisionIdAllocator = null,
|
|
10226
10233
|
trackAsBlock = false
|
|
10227
10234
|
} = options;
|
|
10228
|
-
const
|
|
10235
|
+
const trackRows = generateRedlines && trackAsBlock;
|
|
10229
10236
|
const numCols = tableData.headers?.length || (tableData.rows?.[0]?.length || 1);
|
|
10230
10237
|
const tblPr = `
|
|
10231
10238
|
<w:tblPr>
|
|
@@ -10253,14 +10260,16 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10253
10260
|
const cellText = row[c] || "";
|
|
10254
10261
|
const { cleanText, formatHints } = preprocessMarkdown(cellText);
|
|
10255
10262
|
const runModel = [{
|
|
10256
|
-
kind: generateRedlines
|
|
10263
|
+
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
10257
10264
|
text: cleanText,
|
|
10258
10265
|
rPrXml: isHeaderRow ? "<w:rPr><w:b/></w:rPr>" : "",
|
|
10259
10266
|
author,
|
|
10260
10267
|
startOffset: 0,
|
|
10261
10268
|
endOffset: cleanText.length
|
|
10262
10269
|
}];
|
|
10263
|
-
const
|
|
10270
|
+
const cellMarkMeta = trackRows ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
10271
|
+
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;
|
|
10272
|
+
const runsOoxml = serializeToOoxml(runModel, cellPPr, formatHints, {
|
|
10264
10273
|
author,
|
|
10265
10274
|
generateRedlines,
|
|
10266
10275
|
revisionIdAllocator
|
|
@@ -10269,13 +10278,11 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
10269
10278
|
cellsXml += `<w:tc>${tcPr}${runsOoxml}</w:tc>`;
|
|
10270
10279
|
}
|
|
10271
10280
|
const trPr = isHeaderRow ? "<w:trPr><w:tblHeader/><w:cantSplit/></w:trPr>" : "<w:trPr><w:cantSplit/></w:trPr>";
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
if (tableInsertMeta) {
|
|
10276
|
-
tableXml = `<w:ins w:id="${tableInsertMeta.id}" w:author="${escapeXml(tableInsertMeta.author)}" w:date="${tableInsertMeta.date}">${tableXml}</w:ins>`;
|
|
10281
|
+
const rowMeta = trackRows ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
10282
|
+
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;
|
|
10283
|
+
rowsXml += `<w:tr>${trackedTrPr}${cellsXml}</w:tr>`;
|
|
10277
10284
|
}
|
|
10278
|
-
return
|
|
10285
|
+
return `<w:tbl>${tblPr}${tblGrid}${rowsXml}</w:tbl>`;
|
|
10279
10286
|
}
|
|
10280
10287
|
function diffTablesWithVirtualGrid(oldGrid, newTableData) {
|
|
10281
10288
|
const operations = [];
|
|
@@ -10590,7 +10597,37 @@ async function executeListGeneration(options) {
|
|
|
10590
10597
|
}];
|
|
10591
10598
|
}
|
|
10592
10599
|
}
|
|
10593
|
-
|
|
10600
|
+
const sourceParagraphCount = (originalRunModel || []).filter((run) => run.kind === RunKind.PARAGRAPH_START).length;
|
|
10601
|
+
if (generateRedlines && sourceParagraphCount > 0 && (sourceParagraphCount > 1 || deletionRuns.length === 0)) {
|
|
10602
|
+
let segment = null;
|
|
10603
|
+
const flushSegment = () => {
|
|
10604
|
+
if (!segment) return;
|
|
10605
|
+
results.push(serializeToOoxml(segment, null, [], {
|
|
10606
|
+
author,
|
|
10607
|
+
generateRedlines,
|
|
10608
|
+
revisionIdAllocator
|
|
10609
|
+
}));
|
|
10610
|
+
segment = null;
|
|
10611
|
+
};
|
|
10612
|
+
for (const run of originalRunModel) {
|
|
10613
|
+
if (run.kind === RunKind.PARAGRAPH_START) {
|
|
10614
|
+
flushSegment();
|
|
10615
|
+
segment = [{
|
|
10616
|
+
...run,
|
|
10617
|
+
pPrElement: null,
|
|
10618
|
+
pPrXml: addParagraphMarkRevision(
|
|
10619
|
+
run.pPrElement || run.pPrXml || null,
|
|
10620
|
+
"del",
|
|
10621
|
+
author,
|
|
10622
|
+
revisionIdAllocator
|
|
10623
|
+
)
|
|
10624
|
+
}];
|
|
10625
|
+
} else if (segment && (run.kind === "text" || run.kind === "run")) {
|
|
10626
|
+
segment.push({ ...run, kind: "deletion", author });
|
|
10627
|
+
}
|
|
10628
|
+
}
|
|
10629
|
+
flushSegment();
|
|
10630
|
+
} else if (generateRedlines && deletionRuns.length > 0) {
|
|
10594
10631
|
const deletedPPr = addParagraphMarkRevision(sourcePPr, "del", author, revisionIdAllocator);
|
|
10595
10632
|
const deletedParagraph = serializeToOoxml(deletionRuns, deletedPPr, [], {
|
|
10596
10633
|
author,
|
|
@@ -15169,6 +15206,11 @@ function rejectTrackedChangesInOoxml(oxml, options = {}) {
|
|
|
15169
15206
|
}
|
|
15170
15207
|
if (removeNode(insNode)) rejectedCount += 1;
|
|
15171
15208
|
}
|
|
15209
|
+
for (const tableNode of getWordElementsByLocalName(xmlDoc, "tbl")) {
|
|
15210
|
+
if (!tableNode.parentNode) continue;
|
|
15211
|
+
const hasRow = Array.from(tableNode.childNodes || []).some((child) => isWordElement5(child, "tr"));
|
|
15212
|
+
if (!hasRow && removeNode(tableNode)) rejectedCount += 1;
|
|
15213
|
+
}
|
|
15172
15214
|
for (const delNode of getWordElementsByLocalName(xmlDoc, "del")) {
|
|
15173
15215
|
if (!delNode.parentNode || !authorMatchesNode(delNode, filter)) continue;
|
|
15174
15216
|
if (isParagraphMarkRevisionMarker(delNode)) {
|
|
@@ -15798,7 +15840,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15798
15840
|
operationWarnings.push("Input was sanitized; pass sanitizeInput: false to disable.");
|
|
15799
15841
|
}
|
|
15800
15842
|
let structuredAnalysis = null;
|
|
15801
|
-
if (options.structuredContent !== false) {
|
|
15843
|
+
if (options.structuredContent !== false && options.literalTextEdit !== true) {
|
|
15802
15844
|
structuredAnalysis = analyzeStructuredContent(sanitizedText);
|
|
15803
15845
|
if (!structuredAnalysis.valid) {
|
|
15804
15846
|
if (options.explicitStructuredContent === true) {
|
|
@@ -15817,13 +15859,16 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15817
15859
|
}
|
|
15818
15860
|
}
|
|
15819
15861
|
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
15820
|
-
const hasTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
15821
|
-
const hasFormatHints = formatHints.length > 0;
|
|
15822
15862
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
15863
|
+
const hasLiteralTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
15864
|
+
const hasListStructureChange = !hasLiteralTextChanges && options.explicitStructuredContent === true && isListTargetLoose(cleanModifiedText) && !sourceMatchesListShape(paragraphs, cleanModifiedText);
|
|
15865
|
+
const hasTextChanges = hasLiteralTextChanges || hasListStructureChange;
|
|
15866
|
+
const hasFormatHints = formatHints.length > 0;
|
|
15823
15867
|
const hasExistingFormatting = existingFormatHints.length > 0;
|
|
15824
|
-
const
|
|
15868
|
+
const { textSpans: targetSpans } = buildTextSpansFromParagraphs(paragraphs);
|
|
15869
|
+
const visibleText = targetSpans.map((span) => textSpanVisibleText(span)).join("");
|
|
15825
15870
|
const targetFound = originalText.includes("\n") || originalText.includes("\r") ? originalText.split(/\r?\n/).map(normalizeTargetText).filter(Boolean).every((line) => paragraphs.some((paragraph) => {
|
|
15826
|
-
const paragraphText =
|
|
15871
|
+
const paragraphText = targetSpans.filter((span) => span.paragraph === paragraph).map(textSpanVisibleText).join("");
|
|
15827
15872
|
return normalizeTargetText(paragraphText).includes(line);
|
|
15828
15873
|
})) : visibleText.includes(originalText.trim()) || visibleText.replace(/[\t\n\u2011]/g, "").includes(originalText.trim().replace(/[\t\n\u2011]/g, "")) || normalizeTargetText(visibleText).includes(normalizeTargetText(originalText));
|
|
15829
15874
|
if (hasTextChanges && typeof originalText === "string" && originalText.trim() && !targetFound) {
|
|
@@ -15937,7 +15982,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15937
15982
|
const tables = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
15938
15983
|
const hasTables = tables.length > 0;
|
|
15939
15984
|
const isMarkdownTable = /^\|.+\|/.test(cleanModifiedText.trim()) && cleanModifiedText.includes("\n");
|
|
15940
|
-
const isTargetList = isListTargetLoose(cleanModifiedText);
|
|
15985
|
+
const isTargetList = options.literalTextEdit !== true && isListTargetLoose(cleanModifiedText);
|
|
15941
15986
|
const isStructuredContent = options.structuredContent !== false && structuredAnalysis?.requiresStructuredContent === true;
|
|
15942
15987
|
const tableCellContext = initialTableCellContext;
|
|
15943
15988
|
const usesSurgicalTextMode = hasTables || existingRevisionsPolicy === "slice-cross-author";
|
|
@@ -16052,6 +16097,10 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16052
16097
|
throw caught;
|
|
16053
16098
|
}
|
|
16054
16099
|
}
|
|
16100
|
+
function sourceMatchesListShape(paragraphs, listText) {
|
|
16101
|
+
const itemCount = String(listText).split(/\r?\n/).filter((line) => line.trim()).length;
|
|
16102
|
+
return paragraphs.length === itemCount && paragraphs.every((paragraph) => getElementsByTagNSOrTag(paragraph, NS_W, "numPr").length > 0);
|
|
16103
|
+
}
|
|
16055
16104
|
function normalizeTargetText(text2) {
|
|
16056
16105
|
return String(text2 || "").replace(/[\t\n\u2011]/g, " ").replace(/\s+/g, " ").trim();
|
|
16057
16106
|
}
|
|
@@ -16788,12 +16837,12 @@ function validateParagraphBoundaryMutation(targetParagraph, modifiedText, option
|
|
|
16788
16837
|
}
|
|
16789
16838
|
|
|
16790
16839
|
// core/list-targeting.js
|
|
16791
|
-
function
|
|
16792
|
-
if (!node
|
|
16793
|
-
const
|
|
16794
|
-
|
|
16795
|
-
|
|
16796
|
-
return
|
|
16840
|
+
function getDirectChildByLocalName(node, localName2) {
|
|
16841
|
+
if (!node) return null;
|
|
16842
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
16843
|
+
if (child?.nodeType === 1 && child.localName === localName2) return child;
|
|
16844
|
+
}
|
|
16845
|
+
return null;
|
|
16797
16846
|
}
|
|
16798
16847
|
function readValAttribute(element) {
|
|
16799
16848
|
if (!element) return null;
|
|
@@ -16910,15 +16959,15 @@ function buildListEntriesForInsertion(parsedItems, normalizedTargetText, anchorL
|
|
|
16910
16959
|
}
|
|
16911
16960
|
function getParagraphListInfo(paragraph) {
|
|
16912
16961
|
if (!paragraph) return null;
|
|
16913
|
-
const pPr =
|
|
16962
|
+
const pPr = getDirectChildByLocalName(paragraph, "pPr");
|
|
16914
16963
|
if (!pPr) return null;
|
|
16915
|
-
const numPr =
|
|
16964
|
+
const numPr = getDirectChildByLocalName(pPr, "numPr");
|
|
16916
16965
|
if (!numPr) return null;
|
|
16917
|
-
const numIdEl =
|
|
16966
|
+
const numIdEl = getDirectChildByLocalName(numPr, "numId");
|
|
16918
16967
|
if (!numIdEl) return null;
|
|
16919
16968
|
const numId = readValAttribute(numIdEl);
|
|
16920
16969
|
if (!numId || numId === "0") return null;
|
|
16921
|
-
const ilvlEl =
|
|
16970
|
+
const ilvlEl = getDirectChildByLocalName(numPr, "ilvl");
|
|
16922
16971
|
const ilvlRaw = readValAttribute(ilvlEl);
|
|
16923
16972
|
const ilvl = Number.parseInt(ilvlRaw || "0", 10);
|
|
16924
16973
|
return {
|
|
@@ -17328,8 +17377,13 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
17328
17377
|
}
|
|
17329
17378
|
return updated;
|
|
17330
17379
|
}
|
|
17380
|
+
function parseOxmlFragment(oxml) {
|
|
17381
|
+
const text2 = String(oxml || "");
|
|
17382
|
+
const declared = /xmlns:w\s*=/.test(text2);
|
|
17383
|
+
return parseOoxmlSafe(declared ? text2 : `<w:root xmlns:w="${NS_W}">${text2}</w:root>`, "application/xml").doc;
|
|
17384
|
+
}
|
|
17331
17385
|
function getFirstParagraphFromOxml(oxml) {
|
|
17332
|
-
const doc =
|
|
17386
|
+
const doc = parseOxmlFragment(oxml);
|
|
17333
17387
|
if (!doc) return null;
|
|
17334
17388
|
const parseError = getXmlParseError(doc);
|
|
17335
17389
|
if (parseError) return null;
|
|
@@ -17356,17 +17410,17 @@ function setElementVal(element, value) {
|
|
|
17356
17410
|
element.setAttribute("w:val", String(value));
|
|
17357
17411
|
}
|
|
17358
17412
|
function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
17359
|
-
const doc =
|
|
17413
|
+
const doc = parseOxmlFragment(oxml);
|
|
17360
17414
|
if (!doc) return null;
|
|
17361
17415
|
const parseError = getXmlParseError(doc);
|
|
17362
17416
|
if (parseError) return null;
|
|
17363
17417
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
17364
|
-
const
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17418
|
+
for (const paragraph of paragraphs) {
|
|
17419
|
+
const numIdNodes = Array.from(paragraph.getElementsByTagNameNS("*", "numId"));
|
|
17420
|
+
for (const numIdNode of numIdNodes) {
|
|
17421
|
+
const numId = getElementId(numIdNode, ["w:val", "val"]);
|
|
17422
|
+
if (numId != null) return String(numId);
|
|
17423
|
+
}
|
|
17370
17424
|
}
|
|
17371
17425
|
return null;
|
|
17372
17426
|
}
|
|
@@ -17516,400 +17570,106 @@ async function executeSingleLineListStructuralFallback(plan, options = {}) {
|
|
|
17516
17570
|
};
|
|
17517
17571
|
}
|
|
17518
17572
|
|
|
17519
|
-
//
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
}
|
|
17528
|
-
function wordAttribute3(node, name) {
|
|
17529
|
-
return node.getAttribute(`w:${name}`) || node.getAttribute(name) || "";
|
|
17530
|
-
}
|
|
17531
|
-
function xmlSpaceAttribute(node) {
|
|
17532
|
-
return node.getAttribute("xml:space") || node.getAttribute("space") || node.getAttributeNS?.("http://www.w3.org/XML/1998/namespace", "space") || "";
|
|
17533
|
-
}
|
|
17534
|
-
function isParagraphMarkRevision(node) {
|
|
17535
|
-
return localNameOf3(node.parentNode) === "rPr";
|
|
17536
|
-
}
|
|
17537
|
-
function parseOoxmlForValidation(oxml) {
|
|
17538
|
-
const attempt = (xml) => {
|
|
17539
|
-
const doc = parseXml(xml);
|
|
17540
|
-
const parseError = doc.getElementsByTagName("parsererror")[0];
|
|
17541
|
-
if (parseError) {
|
|
17542
|
-
throw new Error(parseError.textContent || "XML parse error");
|
|
17543
|
-
}
|
|
17544
|
-
return doc;
|
|
17545
|
-
};
|
|
17546
|
-
try {
|
|
17547
|
-
return { doc: attempt(oxml) };
|
|
17548
|
-
} catch {
|
|
17549
|
-
try {
|
|
17550
|
-
return { doc: attempt(`<w:root xmlns:w="${NS_W}">${oxml}</w:root>`) };
|
|
17551
|
-
} catch (error2) {
|
|
17552
|
-
return { error: error2?.message || "XML parse error" };
|
|
17553
|
-
}
|
|
17573
|
+
// services/numbering-helpers.js
|
|
17574
|
+
function parseIntegerAttribute(element, names) {
|
|
17575
|
+
if (!element || !Array.isArray(names)) return null;
|
|
17576
|
+
for (const name of names) {
|
|
17577
|
+
const raw = element.getAttribute(name);
|
|
17578
|
+
if (raw == null || raw === "") continue;
|
|
17579
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
17580
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
17554
17581
|
}
|
|
17582
|
+
return null;
|
|
17555
17583
|
}
|
|
17556
|
-
function
|
|
17557
|
-
|
|
17558
|
-
const
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
addIssue("PARSE_ERROR", "error", "Input is not non-empty OOXML or a parsed XML DOM.");
|
|
17562
|
-
return { valid: false, issues };
|
|
17563
|
-
}
|
|
17564
|
-
const { doc, error: error2 } = isDomNode ? { doc: oxml.nodeType === 9 ? oxml : oxml.ownerDocument } : parseOoxmlForValidation(oxml);
|
|
17565
|
-
if (!doc) {
|
|
17566
|
-
addIssue("PARSE_ERROR", "error", `OOXML does not parse as XML: ${error2}`);
|
|
17567
|
-
return { valid: false, issues };
|
|
17568
|
-
}
|
|
17569
|
-
const insElements = elementsByLocalName(doc, "ins");
|
|
17570
|
-
const delElements = elementsByLocalName(doc, "del");
|
|
17571
|
-
const revisions = insElements.concat(delElements);
|
|
17572
|
-
for (const paragraph of elementsByLocalName(doc, "p")) {
|
|
17573
|
-
const nested = Array.from(paragraph.getElementsByTagName("*")).find((el) => el !== paragraph && localNameOf3(el) === "p");
|
|
17574
|
-
if (nested) {
|
|
17575
|
-
addIssue("NESTED_PARAGRAPH", "error", `<${paragraph.nodeName}> contains nested <${nested.nodeName}>.`);
|
|
17576
|
-
}
|
|
17577
|
-
}
|
|
17578
|
-
for (const body of elementsByLocalName(doc, "body")) {
|
|
17579
|
-
const children = Array.from(body.childNodes || []).filter((child) => child.nodeType === 1);
|
|
17580
|
-
const sectPrIndexes = children.map((child, index) => localNameOf3(child) === "sectPr" ? index : -1).filter((index) => index >= 0);
|
|
17581
|
-
if (sectPrIndexes.length > 1) {
|
|
17582
|
-
addIssue("MULTIPLE_BODY_SECTPR", "error", "<w:body> contains multiple direct <w:sectPr> elements.");
|
|
17583
|
-
} else if (sectPrIndexes.length === 1 && sectPrIndexes[0] !== children.length - 1) {
|
|
17584
|
-
addIssue("SECTPR_NOT_LAST", "error", "<w:sectPr> is not the last element child of <w:body>.");
|
|
17585
|
-
}
|
|
17584
|
+
function nextAvailableId(startId, occupiedIds, maxPreferred = null) {
|
|
17585
|
+
let candidate = Number.isInteger(startId) && startId > 0 ? startId : 1;
|
|
17586
|
+
const occupied = occupiedIds instanceof Set ? occupiedIds : /* @__PURE__ */ new Set();
|
|
17587
|
+
while (occupied.has(candidate)) {
|
|
17588
|
+
candidate += 1;
|
|
17586
17589
|
}
|
|
17587
|
-
|
|
17588
|
-
|
|
17589
|
-
|
|
17590
|
-
if (localNameOf3(revision) !== "ins" || localNameOf3(candidate) !== "del") return true;
|
|
17591
|
-
return candidate.parentNode !== revision;
|
|
17592
|
-
});
|
|
17593
|
-
if (invalidNested) {
|
|
17594
|
-
addIssue(
|
|
17595
|
-
"NESTED_REVISION",
|
|
17596
|
-
"error",
|
|
17597
|
-
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") contains invalid nested <${invalidNested.nodeName}>.`
|
|
17598
|
-
);
|
|
17590
|
+
if (Number.isInteger(maxPreferred) && maxPreferred > 0 && candidate > maxPreferred) {
|
|
17591
|
+
for (let probe = 1; probe <= maxPreferred; probe += 1) {
|
|
17592
|
+
if (!occupied.has(probe)) return probe;
|
|
17599
17593
|
}
|
|
17600
17594
|
}
|
|
17601
|
-
|
|
17602
|
-
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17595
|
+
return candidate;
|
|
17596
|
+
}
|
|
17597
|
+
function createDynamicNumberingIdState(numberingXml, options = {}) {
|
|
17598
|
+
const minId = Number.isInteger(options?.minId) && options.minId > 0 ? options.minId : 1;
|
|
17599
|
+
const maxPreferred = Number.isInteger(options?.maxPreferred) && options.maxPreferred >= minId ? options.maxPreferred : 32767;
|
|
17600
|
+
const usedNumIds = /* @__PURE__ */ new Set();
|
|
17601
|
+
const usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
17602
|
+
if (String(numberingXml || "").trim()) {
|
|
17603
|
+
try {
|
|
17604
|
+
const numberingDoc = parseOoxml(numberingXml);
|
|
17605
|
+
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
17606
|
+
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
17607
|
+
for (const abstractNum of abstractNums) {
|
|
17608
|
+
const id = parseIntegerAttribute(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
17609
|
+
if (id != null) usedAbstractNumIds.add(id);
|
|
17610
|
+
}
|
|
17611
|
+
for (const num of nums) {
|
|
17612
|
+
const id = parseIntegerAttribute(num, ["w:numId", "numId"]);
|
|
17613
|
+
if (id != null) usedNumIds.add(id);
|
|
17614
|
+
}
|
|
17615
|
+
} catch {
|
|
17609
17616
|
}
|
|
17610
17617
|
}
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
|
|
17621
|
-
|
|
17622
|
-
|
|
17618
|
+
const maxUsedNumId = usedNumIds.size > 0 ? Math.max(...usedNumIds) : 0;
|
|
17619
|
+
const maxUsedAbstractNumId = usedAbstractNumIds.size > 0 ? Math.max(...usedAbstractNumIds) : 0;
|
|
17620
|
+
const baseNumId = Math.max(minId, maxUsedNumId + 1);
|
|
17621
|
+
const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
|
|
17622
|
+
return {
|
|
17623
|
+
// Kept so list edits can recognise which existing definition a source list uses and continue it.
|
|
17624
|
+
sourceNumberingXml: String(numberingXml || ""),
|
|
17625
|
+
nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
|
|
17626
|
+
nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
|
|
17627
|
+
usedNumIds,
|
|
17628
|
+
usedAbstractNumIds,
|
|
17629
|
+
minId,
|
|
17630
|
+
maxPreferred
|
|
17631
|
+
};
|
|
17632
|
+
}
|
|
17633
|
+
function normalizeNumberingIdState(state) {
|
|
17634
|
+
if (!state || typeof state !== "object") return null;
|
|
17635
|
+
if (!(state.usedNumIds instanceof Set)) state.usedNumIds = /* @__PURE__ */ new Set();
|
|
17636
|
+
if (!(state.usedAbstractNumIds instanceof Set)) state.usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
17637
|
+
if (!Number.isInteger(state.minId) || state.minId < 1) {
|
|
17638
|
+
state.minId = 1;
|
|
17623
17639
|
}
|
|
17624
|
-
|
|
17625
|
-
|
|
17626
|
-
if (!REVISION_ID_ELEMENTS.has(localNameOf3(node))) continue;
|
|
17627
|
-
const id = wordAttribute3(node, "id");
|
|
17628
|
-
if (!id) continue;
|
|
17629
|
-
if (seenIds.has(id)) {
|
|
17630
|
-
addIssue("DUPLICATE_REVISION_ID", "error", `Revision id ${id} appears more than once.`);
|
|
17631
|
-
}
|
|
17632
|
-
seenIds.add(id);
|
|
17640
|
+
if (!Number.isInteger(state.maxPreferred) || state.maxPreferred < state.minId) {
|
|
17641
|
+
state.maxPreferred = 32767;
|
|
17633
17642
|
}
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
const text2 = node.textContent || "";
|
|
17637
|
-
if (/^\s|\s$/.test(text2) && xmlSpaceAttribute(node) !== "preserve") {
|
|
17638
|
-
addIssue(
|
|
17639
|
-
"MISSING_SPACE_PRESERVE",
|
|
17640
|
-
"error",
|
|
17641
|
-
`<${node.nodeName}> has boundary whitespace without xml:space="preserve".`
|
|
17642
|
-
);
|
|
17643
|
-
}
|
|
17644
|
-
if (text2 === "") {
|
|
17645
|
-
addIssue("EMPTY_TEXT_ELEMENT", "warning", `<${node.nodeName}> is empty.`);
|
|
17646
|
-
}
|
|
17643
|
+
if (!Number.isInteger(state.nextNumId) || state.nextNumId < state.minId) {
|
|
17644
|
+
state.nextNumId = state.minId;
|
|
17647
17645
|
}
|
|
17648
|
-
|
|
17649
|
-
|
|
17650
|
-
const hasElementChild = Array.from(revision.childNodes || []).some((child) => child.nodeType === 1);
|
|
17651
|
-
if (!hasElementChild) {
|
|
17652
|
-
addIssue(
|
|
17653
|
-
"EMPTY_REVISION_WRAPPER",
|
|
17654
|
-
"warning",
|
|
17655
|
-
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") wraps no content.`
|
|
17656
|
-
);
|
|
17657
|
-
}
|
|
17646
|
+
if (!Number.isInteger(state.nextAbstractNumId) || state.nextAbstractNumId < state.minId) {
|
|
17647
|
+
state.nextAbstractNumId = state.minId;
|
|
17658
17648
|
}
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17649
|
+
state.nextNumId = nextAvailableId(state.nextNumId, state.usedNumIds, state.maxPreferred);
|
|
17650
|
+
state.nextAbstractNumId = nextAvailableId(state.nextAbstractNumId, state.usedAbstractNumIds, state.maxPreferred);
|
|
17651
|
+
return state;
|
|
17652
|
+
}
|
|
17653
|
+
function reserveNextNumberingId(state, kind = "num") {
|
|
17654
|
+
const normalized = normalizeNumberingIdState(state);
|
|
17655
|
+
if (!normalized) return null;
|
|
17656
|
+
const useAbstract = kind === "abstract";
|
|
17657
|
+
const id = useAbstract ? normalized.nextAbstractNumId : normalized.nextNumId;
|
|
17658
|
+
if (!Number.isInteger(id) || id < 1) return null;
|
|
17659
|
+
if (useAbstract) {
|
|
17660
|
+
normalized.usedAbstractNumIds.add(id);
|
|
17661
|
+
normalized.nextAbstractNumId = nextAvailableId(id + 1, normalized.usedAbstractNumIds, normalized.maxPreferred);
|
|
17662
|
+
} else {
|
|
17663
|
+
normalized.usedNumIds.add(id);
|
|
17664
|
+
normalized.nextNumId = nextAvailableId(id + 1, normalized.usedNumIds, normalized.maxPreferred);
|
|
17666
17665
|
}
|
|
17667
|
-
return
|
|
17666
|
+
return id;
|
|
17668
17667
|
}
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
if (
|
|
17673
|
-
return
|
|
17674
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI === WORD_MAIN_NS && node.localName === localName2
|
|
17675
|
-
);
|
|
17676
|
-
}
|
|
17677
|
-
function escapeMarkdownCell(text2) {
|
|
17678
|
-
return String(text2 || "").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
17679
|
-
}
|
|
17680
|
-
function extractTableMatrix(tableElement) {
|
|
17681
|
-
const rowElements = getDirectWordChildren(tableElement, "tr");
|
|
17682
|
-
const matrix = rowElements.map((row) => {
|
|
17683
|
-
const cellElements = getDirectWordChildren(row, "tc");
|
|
17684
|
-
return cellElements.map((cell) => {
|
|
17685
|
-
const paragraphs = getDirectWordChildren(cell, "p");
|
|
17686
|
-
if (paragraphs.length === 0) return normalizeWhitespaceForTargeting(getParagraphText(cell));
|
|
17687
|
-
const lines = paragraphs.map((p) => normalizeWhitespaceForTargeting(getParagraphText(p))).filter(Boolean);
|
|
17688
|
-
return lines.join("\n");
|
|
17689
|
-
});
|
|
17690
|
-
});
|
|
17691
|
-
const columnCount = matrix.reduce((max2, row) => Math.max(max2, row.length), 0);
|
|
17692
|
-
matrix.forEach((row) => {
|
|
17693
|
-
while (row.length < columnCount) row.push("");
|
|
17694
|
-
});
|
|
17695
|
-
return { matrix, rowElements, columnCount };
|
|
17696
|
-
}
|
|
17697
|
-
function tableMatrixToMarkdown(matrix, columnCount) {
|
|
17698
|
-
if (!Array.isArray(matrix) || matrix.length === 0 || columnCount <= 0) return null;
|
|
17699
|
-
const normalized = matrix.map((row) => {
|
|
17700
|
-
const copy = Array.isArray(row) ? row.slice(0, columnCount) : [];
|
|
17701
|
-
while (copy.length < columnCount) copy.push("");
|
|
17702
|
-
return copy;
|
|
17703
|
-
});
|
|
17704
|
-
const header = normalized[0];
|
|
17705
|
-
const separator = new Array(columnCount).fill("---");
|
|
17706
|
-
const bodyRows = normalized.slice(1);
|
|
17707
|
-
const toLine = (row) => `| ${row.map((cell) => escapeMarkdownCell(cell)).join(" | ")} |`;
|
|
17708
|
-
return [toLine(header), toLine(separator), ...bodyRows.map(toLine)].join("\n");
|
|
17709
|
-
}
|
|
17710
|
-
function isSymmetricLabelRow(rowValues) {
|
|
17711
|
-
if (!Array.isArray(rowValues) || rowValues.length < 2) return false;
|
|
17712
|
-
const normalized = rowValues.map((value) => normalizeWhitespaceForTargeting(value)).filter(Boolean);
|
|
17713
|
-
if (normalized.length < 2) return false;
|
|
17714
|
-
return normalized.every((value) => value === normalized[0]);
|
|
17715
|
-
}
|
|
17716
|
-
function isLikelyStructuredTableSourceParagraph(text2) {
|
|
17717
|
-
const normalized = String(text2 || "").trim();
|
|
17718
|
-
if (!normalized) return false;
|
|
17719
|
-
if (/^and$/i.test(normalized)) return true;
|
|
17720
|
-
if (/^\[.*\]$/.test(normalized)) return true;
|
|
17721
|
-
if (/^\(.*\)$/.test(normalized)) return true;
|
|
17722
|
-
if (/:\s*$/.test(normalized)) return true;
|
|
17723
|
-
if (normalized.length <= 90 && !/[.!?]$/.test(normalized) && /[:\[\]()]/.test(normalized)) return true;
|
|
17724
|
-
if (/^[\[(]/.test(normalized)) return true;
|
|
17725
|
-
return false;
|
|
17726
|
-
}
|
|
17727
|
-
function inferTableReplacementParagraphBlock(startParagraph, options = {}) {
|
|
17728
|
-
const maxScan = Number.isInteger(options?.maxScan) && options.maxScan > 0 ? options.maxScan : 10;
|
|
17729
|
-
const paragraphTextGetter = typeof options?.getParagraphText === "function" ? options.getParagraphText : getParagraphText;
|
|
17730
|
-
if (!startParagraph || !startParagraph.parentNode) return null;
|
|
17731
|
-
const block = [startParagraph];
|
|
17732
|
-
let cursor = startParagraph.nextSibling;
|
|
17733
|
-
let scanned = 0;
|
|
17734
|
-
while (cursor && scanned < maxScan) {
|
|
17735
|
-
scanned += 1;
|
|
17736
|
-
const nextCursor = cursor.nextSibling;
|
|
17737
|
-
if (cursor.nodeType !== 1 || cursor.namespaceURI !== WORD_MAIN_NS || cursor.localName !== "p") {
|
|
17738
|
-
cursor = nextCursor;
|
|
17739
|
-
continue;
|
|
17740
|
-
}
|
|
17741
|
-
const text2 = String(paragraphTextGetter(cursor) || "").trim();
|
|
17742
|
-
if (!text2) {
|
|
17743
|
-
if (block.length > 1) break;
|
|
17744
|
-
cursor = nextCursor;
|
|
17745
|
-
continue;
|
|
17746
|
-
}
|
|
17747
|
-
if (!isLikelyStructuredTableSourceParagraph(text2)) break;
|
|
17748
|
-
block.push(cursor);
|
|
17749
|
-
cursor = nextCursor;
|
|
17750
|
-
}
|
|
17751
|
-
return block.length > 1 ? block : null;
|
|
17752
|
-
}
|
|
17753
|
-
function synthesizeTableMarkdownFromMultilineCellEdit(targetParagraph, modifiedText, options = {}) {
|
|
17754
|
-
const onInfo = typeof options.onInfo === "function" ? options.onInfo : () => {
|
|
17755
|
-
};
|
|
17756
|
-
const onWarn = typeof options.onWarn === "function" ? options.onWarn : () => {
|
|
17757
|
-
};
|
|
17758
|
-
const rawModified = String(modifiedText || "");
|
|
17759
|
-
if (!rawModified.includes("\n")) return null;
|
|
17760
|
-
if (isMarkdownTableText(rawModified)) return null;
|
|
17761
|
-
const lines = rawModified.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean);
|
|
17762
|
-
if (lines.length < 2) return null;
|
|
17763
|
-
const tableElement = options.tableElement || findContainingWordElement(targetParagraph, "tbl");
|
|
17764
|
-
const rowElement = findContainingWordElement(targetParagraph, "tr");
|
|
17765
|
-
const cellElement = findContainingWordElement(targetParagraph, "tc");
|
|
17766
|
-
if (!tableElement || !rowElement || !cellElement) return null;
|
|
17767
|
-
const currentParagraphText = normalizeWhitespaceForTargeting(
|
|
17768
|
-
options.currentParagraphText || getParagraphText(targetParagraph)
|
|
17769
|
-
);
|
|
17770
|
-
const firstLine = normalizeWhitespaceForTargeting(lines[0]);
|
|
17771
|
-
if (currentParagraphText && firstLine && firstLine !== currentParagraphText) {
|
|
17772
|
-
onWarn("[Table] Multiline cell text did not anchor to original cell text; skipping table-row synthesis heuristic.");
|
|
17773
|
-
return null;
|
|
17774
|
-
}
|
|
17775
|
-
const { matrix, rowElements, columnCount } = extractTableMatrix(tableElement);
|
|
17776
|
-
if (matrix.length === 0 || columnCount === 0) return null;
|
|
17777
|
-
const rowIndex = rowElements.indexOf(rowElement);
|
|
17778
|
-
const cellElements = getDirectWordChildren(rowElement, "tc");
|
|
17779
|
-
const colIndex = cellElements.indexOf(cellElement);
|
|
17780
|
-
if (rowIndex < 0 || colIndex < 0 || colIndex >= columnCount) return null;
|
|
17781
|
-
matrix[rowIndex][colIndex] = lines[0];
|
|
17782
|
-
const mirrorAcrossColumns = isSymmetricLabelRow(matrix[rowIndex]);
|
|
17783
|
-
if (mirrorAcrossColumns) {
|
|
17784
|
-
onInfo("[Table] Symmetric row detected; mirroring inserted row values across columns.");
|
|
17785
|
-
}
|
|
17786
|
-
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
17787
|
-
const insertIndex = rowIndex + i2;
|
|
17788
|
-
const extraValue = lines[i2];
|
|
17789
|
-
if (insertIndex < matrix.length && !normalizeWhitespaceForTargeting(matrix[insertIndex][colIndex])) {
|
|
17790
|
-
if (mirrorAcrossColumns) {
|
|
17791
|
-
for (let col = 0; col < columnCount; col++) {
|
|
17792
|
-
if (!normalizeWhitespaceForTargeting(matrix[insertIndex][col])) {
|
|
17793
|
-
matrix[insertIndex][col] = extraValue;
|
|
17794
|
-
}
|
|
17795
|
-
}
|
|
17796
|
-
} else {
|
|
17797
|
-
matrix[insertIndex][colIndex] = extraValue;
|
|
17798
|
-
}
|
|
17799
|
-
} else {
|
|
17800
|
-
const newRow = new Array(columnCount).fill("");
|
|
17801
|
-
if (mirrorAcrossColumns) {
|
|
17802
|
-
for (let col = 0; col < columnCount; col++) newRow[col] = extraValue;
|
|
17803
|
-
} else {
|
|
17804
|
-
newRow[colIndex] = extraValue;
|
|
17805
|
-
}
|
|
17806
|
-
matrix.splice(Math.min(insertIndex, matrix.length), 0, newRow);
|
|
17807
|
-
}
|
|
17808
|
-
}
|
|
17809
|
-
const markdown = tableMatrixToMarkdown(matrix, columnCount);
|
|
17810
|
-
if (!markdown) return null;
|
|
17811
|
-
onInfo("[Table] Synthesized full markdown table from multiline cell edit for table-scope reconciliation.");
|
|
17812
|
-
return markdown;
|
|
17813
|
-
}
|
|
17814
|
-
|
|
17815
|
-
// services/numbering-helpers.js
|
|
17816
|
-
function parseIntegerAttribute(element, names) {
|
|
17817
|
-
if (!element || !Array.isArray(names)) return null;
|
|
17818
|
-
for (const name of names) {
|
|
17819
|
-
const raw = element.getAttribute(name);
|
|
17820
|
-
if (raw == null || raw === "") continue;
|
|
17821
|
-
const parsed = Number.parseInt(String(raw), 10);
|
|
17822
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
17823
|
-
}
|
|
17824
|
-
return null;
|
|
17825
|
-
}
|
|
17826
|
-
function nextAvailableId(startId, occupiedIds, maxPreferred = null) {
|
|
17827
|
-
let candidate = Number.isInteger(startId) && startId > 0 ? startId : 1;
|
|
17828
|
-
const occupied = occupiedIds instanceof Set ? occupiedIds : /* @__PURE__ */ new Set();
|
|
17829
|
-
while (occupied.has(candidate)) {
|
|
17830
|
-
candidate += 1;
|
|
17831
|
-
}
|
|
17832
|
-
if (Number.isInteger(maxPreferred) && maxPreferred > 0 && candidate > maxPreferred) {
|
|
17833
|
-
for (let probe = 1; probe <= maxPreferred; probe += 1) {
|
|
17834
|
-
if (!occupied.has(probe)) return probe;
|
|
17835
|
-
}
|
|
17836
|
-
}
|
|
17837
|
-
return candidate;
|
|
17838
|
-
}
|
|
17839
|
-
function createDynamicNumberingIdState(numberingXml, options = {}) {
|
|
17840
|
-
const minId = Number.isInteger(options?.minId) && options.minId > 0 ? options.minId : 1;
|
|
17841
|
-
const maxPreferred = Number.isInteger(options?.maxPreferred) && options.maxPreferred >= minId ? options.maxPreferred : 32767;
|
|
17842
|
-
const usedNumIds = /* @__PURE__ */ new Set();
|
|
17843
|
-
const usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
17844
|
-
if (String(numberingXml || "").trim()) {
|
|
17845
|
-
try {
|
|
17846
|
-
const numberingDoc = parseOoxml(numberingXml);
|
|
17847
|
-
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
17848
|
-
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
17849
|
-
for (const abstractNum of abstractNums) {
|
|
17850
|
-
const id = parseIntegerAttribute(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
17851
|
-
if (id != null) usedAbstractNumIds.add(id);
|
|
17852
|
-
}
|
|
17853
|
-
for (const num of nums) {
|
|
17854
|
-
const id = parseIntegerAttribute(num, ["w:numId", "numId"]);
|
|
17855
|
-
if (id != null) usedNumIds.add(id);
|
|
17856
|
-
}
|
|
17857
|
-
} catch {
|
|
17858
|
-
}
|
|
17859
|
-
}
|
|
17860
|
-
const maxUsedNumId = usedNumIds.size > 0 ? Math.max(...usedNumIds) : 0;
|
|
17861
|
-
const maxUsedAbstractNumId = usedAbstractNumIds.size > 0 ? Math.max(...usedAbstractNumIds) : 0;
|
|
17862
|
-
const baseNumId = Math.max(minId, maxUsedNumId + 1);
|
|
17863
|
-
const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
|
|
17864
|
-
return {
|
|
17865
|
-
nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
|
|
17866
|
-
nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
|
|
17867
|
-
usedNumIds,
|
|
17868
|
-
usedAbstractNumIds,
|
|
17869
|
-
minId,
|
|
17870
|
-
maxPreferred
|
|
17871
|
-
};
|
|
17872
|
-
}
|
|
17873
|
-
function normalizeNumberingIdState(state) {
|
|
17874
|
-
if (!state || typeof state !== "object") return null;
|
|
17875
|
-
if (!(state.usedNumIds instanceof Set)) state.usedNumIds = /* @__PURE__ */ new Set();
|
|
17876
|
-
if (!(state.usedAbstractNumIds instanceof Set)) state.usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
17877
|
-
if (!Number.isInteger(state.minId) || state.minId < 1) {
|
|
17878
|
-
state.minId = 1;
|
|
17879
|
-
}
|
|
17880
|
-
if (!Number.isInteger(state.maxPreferred) || state.maxPreferred < state.minId) {
|
|
17881
|
-
state.maxPreferred = 32767;
|
|
17882
|
-
}
|
|
17883
|
-
if (!Number.isInteger(state.nextNumId) || state.nextNumId < state.minId) {
|
|
17884
|
-
state.nextNumId = state.minId;
|
|
17885
|
-
}
|
|
17886
|
-
if (!Number.isInteger(state.nextAbstractNumId) || state.nextAbstractNumId < state.minId) {
|
|
17887
|
-
state.nextAbstractNumId = state.minId;
|
|
17888
|
-
}
|
|
17889
|
-
state.nextNumId = nextAvailableId(state.nextNumId, state.usedNumIds, state.maxPreferred);
|
|
17890
|
-
state.nextAbstractNumId = nextAvailableId(state.nextAbstractNumId, state.usedAbstractNumIds, state.maxPreferred);
|
|
17891
|
-
return state;
|
|
17892
|
-
}
|
|
17893
|
-
function reserveNextNumberingId(state, kind = "num") {
|
|
17894
|
-
const normalized = normalizeNumberingIdState(state);
|
|
17895
|
-
if (!normalized) return null;
|
|
17896
|
-
const useAbstract = kind === "abstract";
|
|
17897
|
-
const id = useAbstract ? normalized.nextAbstractNumId : normalized.nextNumId;
|
|
17898
|
-
if (!Number.isInteger(id) || id < 1) return null;
|
|
17899
|
-
if (useAbstract) {
|
|
17900
|
-
normalized.usedAbstractNumIds.add(id);
|
|
17901
|
-
normalized.nextAbstractNumId = nextAvailableId(id + 1, normalized.usedAbstractNumIds, normalized.maxPreferred);
|
|
17902
|
-
} else {
|
|
17903
|
-
normalized.usedNumIds.add(id);
|
|
17904
|
-
normalized.nextNumId = nextAvailableId(id + 1, normalized.usedNumIds, normalized.maxPreferred);
|
|
17905
|
-
}
|
|
17906
|
-
return id;
|
|
17907
|
-
}
|
|
17908
|
-
function reserveNextNumberingIdPair(state) {
|
|
17909
|
-
const numId = reserveNextNumberingId(state, "num");
|
|
17910
|
-
const abstractNumId = reserveNextNumberingId(state, "abstract");
|
|
17911
|
-
if (numId == null || abstractNumId == null) return null;
|
|
17912
|
-
return { numId, abstractNumId };
|
|
17668
|
+
function reserveNextNumberingIdPair(state) {
|
|
17669
|
+
const numId = reserveNextNumberingId(state, "num");
|
|
17670
|
+
const abstractNumId = reserveNextNumberingId(state, "abstract");
|
|
17671
|
+
if (numId == null || abstractNumId == null) return null;
|
|
17672
|
+
return { numId, abstractNumId };
|
|
17913
17673
|
}
|
|
17914
17674
|
function hasXmlParseError(doc) {
|
|
17915
17675
|
if (!doc || !doc.documentElement) return true;
|
|
@@ -17962,134 +17722,507 @@ function overwriteParagraphNumIds(paragraphNodes, targetNumId) {
|
|
|
17962
17722
|
}
|
|
17963
17723
|
}
|
|
17964
17724
|
}
|
|
17965
|
-
function extractFirstParagraphNumId(paragraphNodes) {
|
|
17966
|
-
for (const node of paragraphNodes || []) {
|
|
17967
|
-
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
17968
|
-
for (const numIdNode of numIdNodes) {
|
|
17969
|
-
const numId = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17970
|
-
if (numId != null) return String(numId);
|
|
17725
|
+
function extractFirstParagraphNumId(paragraphNodes) {
|
|
17726
|
+
for (const node of paragraphNodes || []) {
|
|
17727
|
+
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
17728
|
+
for (const numIdNode of numIdNodes) {
|
|
17729
|
+
const numId = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17730
|
+
if (numId != null) return String(numId);
|
|
17731
|
+
}
|
|
17732
|
+
}
|
|
17733
|
+
return null;
|
|
17734
|
+
}
|
|
17735
|
+
function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startAt) {
|
|
17736
|
+
const safeNumId = String(numId);
|
|
17737
|
+
const safeAbstractNumId = String(abstractNumId);
|
|
17738
|
+
const safeStartAt = Number.isInteger(startAt) && startAt > 0 ? startAt : 1;
|
|
17739
|
+
const levelsXml = Array.from({ length: 9 }, (_, level) => {
|
|
17740
|
+
const lvlText = Array.from({ length: level + 1 }, (_2, i2) => `%${i2 + 1}`).join(".") + ".";
|
|
17741
|
+
const left = 720 * (level + 1);
|
|
17742
|
+
return `
|
|
17743
|
+
<w:lvl w:ilvl="${level}">
|
|
17744
|
+
<w:start w:val="1"/>
|
|
17745
|
+
<w:numFmt w:val="decimal"/>
|
|
17746
|
+
<w:lvlText w:val="${lvlText}"/>
|
|
17747
|
+
<w:lvlJc w:val="left"/>
|
|
17748
|
+
<w:pPr><w:ind w:left="${left}" w:hanging="360"/></w:pPr>
|
|
17749
|
+
</w:lvl>`;
|
|
17750
|
+
}).join("");
|
|
17751
|
+
return `
|
|
17752
|
+
<w:numbering xmlns:w="${WORD_MAIN_NS}">
|
|
17753
|
+
<w:abstractNum w:abstractNumId="${safeAbstractNumId}">
|
|
17754
|
+
<w:multiLevelType w:val="multilevel"/>
|
|
17755
|
+
${levelsXml}
|
|
17756
|
+
</w:abstractNum>
|
|
17757
|
+
<w:num w:numId="${safeNumId}">
|
|
17758
|
+
<w:abstractNumId w:val="${safeAbstractNumId}"/>
|
|
17759
|
+
<w:lvlOverride w:ilvl="0">
|
|
17760
|
+
<w:startOverride w:val="${safeStartAt}"/>
|
|
17761
|
+
</w:lvlOverride>
|
|
17762
|
+
</w:num>
|
|
17763
|
+
</w:numbering>`.trim();
|
|
17764
|
+
}
|
|
17765
|
+
function isTrackedDeletedParagraph(node) {
|
|
17766
|
+
if (!node || node.nodeType !== 1 || node.localName !== "p") return false;
|
|
17767
|
+
const pPr = Array.from(node.childNodes || []).find((child) => isDirectWordChild(child, "pPr"));
|
|
17768
|
+
const rPr = pPr && Array.from(pPr.childNodes || []).find((child) => isDirectWordChild(child, "rPr"));
|
|
17769
|
+
return !!rPr && Array.from(rPr.childNodes || []).some((child) => isDirectWordChild(child, "del"));
|
|
17770
|
+
}
|
|
17771
|
+
function paragraphNumIdNodes(node) {
|
|
17772
|
+
return Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []).filter((numIdNode) => numIdNode.parentNode?.localName === "numPr");
|
|
17773
|
+
}
|
|
17774
|
+
function levelFormats(abstractNum) {
|
|
17775
|
+
const formats = /* @__PURE__ */ new Map();
|
|
17776
|
+
for (const lvl of Array.from(abstractNum?.getElementsByTagNameNS?.("*", "lvl") || [])) {
|
|
17777
|
+
const ilvl = getElementId2(lvl, ["w:ilvl", "ilvl"]);
|
|
17778
|
+
const fmt = Array.from(lvl.getElementsByTagNameNS("*", "numFmt"))[0]?.getAttribute("w:val") || null;
|
|
17779
|
+
if (ilvl != null && fmt) formats.set(ilvl, fmt);
|
|
17780
|
+
}
|
|
17781
|
+
return formats;
|
|
17782
|
+
}
|
|
17783
|
+
function normalizeListFormat(format) {
|
|
17784
|
+
return format === "bullet" || format === "circle" || format === "square" ? "bullet" : format;
|
|
17785
|
+
}
|
|
17786
|
+
function abstractNumForNumId(numberingDoc, numId) {
|
|
17787
|
+
const num = Array.from(numberingDoc.getElementsByTagNameNS("*", "num")).find((candidate) => getElementId2(candidate, ["w:numId", "numId"]) === numId);
|
|
17788
|
+
const abstractId = num && getElementId2(Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0], ["w:val", "val"]);
|
|
17789
|
+
if (abstractId == null) return null;
|
|
17790
|
+
return Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum")).find((candidate) => getElementId2(candidate, ["w:abstractNumId", "abstractNumId"]) === abstractId) || null;
|
|
17791
|
+
}
|
|
17792
|
+
function reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, numberingIdState) {
|
|
17793
|
+
const sourceXml = numberingIdState?.sourceNumberingXml;
|
|
17794
|
+
if (!numberingXml || !String(sourceXml || "").trim() || !Array.isArray(replacementNodes)) return null;
|
|
17795
|
+
const sourceNumIds = /* @__PURE__ */ new Set();
|
|
17796
|
+
for (const paragraph of sourceParagraphs || []) {
|
|
17797
|
+
for (const numIdNode of paragraphNumIdNodes(paragraph)) {
|
|
17798
|
+
const id = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17799
|
+
if (id != null && id !== 0) sourceNumIds.add(id);
|
|
17800
|
+
}
|
|
17801
|
+
}
|
|
17802
|
+
if (sourceNumIds.size !== 1) return null;
|
|
17803
|
+
const [sourceNumId] = sourceNumIds;
|
|
17804
|
+
const sourceDoc = parseOoxml(sourceXml);
|
|
17805
|
+
const payloadDoc = parseOoxml(numberingXml);
|
|
17806
|
+
if (hasXmlParseError(sourceDoc) || hasXmlParseError(payloadDoc)) return null;
|
|
17807
|
+
const sourceAbstract = abstractNumForNumId(sourceDoc, sourceNumId);
|
|
17808
|
+
if (!sourceAbstract) return null;
|
|
17809
|
+
const sourceFormats = levelFormats(sourceAbstract);
|
|
17810
|
+
const newParagraphNumIds = [];
|
|
17811
|
+
for (const node of replacementNodes) {
|
|
17812
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17813
|
+
for (const numIdNode of paragraphNumIdNodes(node)) {
|
|
17814
|
+
const ilvlNode = Array.from(numIdNode.parentNode.childNodes || []).find((child) => child.localName === "ilvl");
|
|
17815
|
+
newParagraphNumIds.push({
|
|
17816
|
+
numId: getElementId2(numIdNode, ["w:val", "val"]),
|
|
17817
|
+
ilvl: ilvlNode ? getElementId2(ilvlNode, ["w:val", "val"]) ?? 0 : 0
|
|
17818
|
+
});
|
|
17819
|
+
}
|
|
17820
|
+
}
|
|
17821
|
+
if (newParagraphNumIds.length === 0) return null;
|
|
17822
|
+
const payloadFormats = /* @__PURE__ */ new Map();
|
|
17823
|
+
for (const { numId, ilvl } of newParagraphNumIds) {
|
|
17824
|
+
if (numId == null) return null;
|
|
17825
|
+
if (!payloadFormats.has(numId)) {
|
|
17826
|
+
const abstract = abstractNumForNumId(payloadDoc, numId);
|
|
17827
|
+
if (!abstract) return null;
|
|
17828
|
+
payloadFormats.set(numId, levelFormats(abstract));
|
|
17829
|
+
}
|
|
17830
|
+
const generated = payloadFormats.get(numId).get(ilvl);
|
|
17831
|
+
const existing = sourceFormats.get(ilvl);
|
|
17832
|
+
if (!generated || !existing || normalizeListFormat(generated) !== normalizeListFormat(existing)) return null;
|
|
17833
|
+
}
|
|
17834
|
+
const clonedNodes = replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node);
|
|
17835
|
+
for (const node of clonedNodes) {
|
|
17836
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17837
|
+
for (const numIdNode of paragraphNumIdNodes(node)) setElementVal2(numIdNode, sourceNumId);
|
|
17838
|
+
}
|
|
17839
|
+
return { numId: sourceNumId, replacementNodes: clonedNodes };
|
|
17840
|
+
}
|
|
17841
|
+
function remapNumberingPayloadForDocument(numberingXml, replacementNodes, numberingIdState) {
|
|
17842
|
+
const numberingDoc = parseOoxml(numberingXml);
|
|
17843
|
+
if (hasXmlParseError(numberingDoc)) {
|
|
17844
|
+
return {
|
|
17845
|
+
numberingXml: String(numberingXml || ""),
|
|
17846
|
+
replacementNodes: Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : []
|
|
17847
|
+
};
|
|
17848
|
+
}
|
|
17849
|
+
const abstractNumMap = /* @__PURE__ */ new Map();
|
|
17850
|
+
const numIdMap = /* @__PURE__ */ new Map();
|
|
17851
|
+
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
17852
|
+
for (const abstractNum of abstractNums) {
|
|
17853
|
+
const oldId = getElementId2(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
17854
|
+
if (oldId == null) continue;
|
|
17855
|
+
const newId = reserveNextNumberingId(numberingIdState, "abstract");
|
|
17856
|
+
if (newId == null) continue;
|
|
17857
|
+
abstractNumMap.set(oldId, newId);
|
|
17858
|
+
setElementId(abstractNum, "w:abstractNumId", newId);
|
|
17859
|
+
}
|
|
17860
|
+
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
17861
|
+
for (const num of nums) {
|
|
17862
|
+
const oldNumId = getElementId2(num, ["w:numId", "numId"]);
|
|
17863
|
+
if (oldNumId == null) continue;
|
|
17864
|
+
const newNumId = reserveNextNumberingId(numberingIdState, "num");
|
|
17865
|
+
if (newNumId == null) continue;
|
|
17866
|
+
numIdMap.set(oldNumId, newNumId);
|
|
17867
|
+
setElementId(num, "w:numId", newNumId);
|
|
17868
|
+
const abstractNumIdNode = Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0] || null;
|
|
17869
|
+
if (abstractNumIdNode) {
|
|
17870
|
+
const oldAbsRef = getElementId2(abstractNumIdNode, ["w:val", "val"]);
|
|
17871
|
+
if (oldAbsRef != null && abstractNumMap.has(oldAbsRef)) {
|
|
17872
|
+
setElementVal2(abstractNumIdNode, abstractNumMap.get(oldAbsRef));
|
|
17873
|
+
}
|
|
17874
|
+
}
|
|
17875
|
+
}
|
|
17876
|
+
const clonedNodes = Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : [];
|
|
17877
|
+
for (const node of clonedNodes) {
|
|
17878
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17879
|
+
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
17880
|
+
for (const numIdNode of numIdNodes) {
|
|
17881
|
+
const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17882
|
+
if (oldNumRef != null && numIdMap.has(oldNumRef)) {
|
|
17883
|
+
setElementVal2(numIdNode, numIdMap.get(oldNumRef));
|
|
17884
|
+
}
|
|
17885
|
+
}
|
|
17886
|
+
}
|
|
17887
|
+
return {
|
|
17888
|
+
numberingXml: serializeOoxml(numberingDoc),
|
|
17889
|
+
replacementNodes: clonedNodes
|
|
17890
|
+
};
|
|
17891
|
+
}
|
|
17892
|
+
function mergeNumberingXmlBySchemaOrder(existingNumberingXml, incomingNumberingXml) {
|
|
17893
|
+
const existingText = String(existingNumberingXml || "");
|
|
17894
|
+
const incomingText = String(incomingNumberingXml || "");
|
|
17895
|
+
if (!incomingText.trim()) return existingText;
|
|
17896
|
+
if (!existingText.trim()) return incomingText;
|
|
17897
|
+
try {
|
|
17898
|
+
const existingDoc = parseOoxml(existingText);
|
|
17899
|
+
const incomingDoc = parseOoxml(incomingText);
|
|
17900
|
+
if (hasXmlParseError(existingDoc) || hasXmlParseError(incomingDoc)) {
|
|
17901
|
+
return existingText;
|
|
17902
|
+
}
|
|
17903
|
+
const existingRoot = existingDoc.documentElement;
|
|
17904
|
+
const incomingRoot = incomingDoc.documentElement;
|
|
17905
|
+
if (!existingRoot || !incomingRoot) return existingText;
|
|
17906
|
+
const existingAbstractIds = new Set(
|
|
17907
|
+
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "abstractNum")).map((node) => parseIntegerAttribute(node, ["w:abstractNumId", "abstractNumId"])).filter((id) => id != null)
|
|
17908
|
+
);
|
|
17909
|
+
const existingNumIds = new Set(
|
|
17910
|
+
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "num")).map((node) => parseIntegerAttribute(node, ["w:numId", "numId"])).filter((id) => id != null)
|
|
17911
|
+
);
|
|
17912
|
+
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
17913
|
+
if (!isDirectWordChild(incomingNode, "abstractNum")) continue;
|
|
17914
|
+
const incomingId = parseIntegerAttribute(incomingNode, ["w:abstractNumId", "abstractNumId"]);
|
|
17915
|
+
if (incomingId == null || existingAbstractIds.has(incomingId)) continue;
|
|
17916
|
+
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "abstract");
|
|
17917
|
+
existingAbstractIds.add(incomingId);
|
|
17918
|
+
}
|
|
17919
|
+
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
17920
|
+
if (!isDirectWordChild(incomingNode, "num")) continue;
|
|
17921
|
+
const incomingId = parseIntegerAttribute(incomingNode, ["w:numId", "numId"]);
|
|
17922
|
+
if (incomingId == null || existingNumIds.has(incomingId)) continue;
|
|
17923
|
+
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "num");
|
|
17924
|
+
existingNumIds.add(incomingId);
|
|
17925
|
+
}
|
|
17926
|
+
return serializeOoxml(existingDoc);
|
|
17927
|
+
} catch {
|
|
17928
|
+
return existingText;
|
|
17929
|
+
}
|
|
17930
|
+
}
|
|
17931
|
+
|
|
17932
|
+
// core/redline-validation.js
|
|
17933
|
+
var REVISION_ID_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "rPrChange", "pPrChange"]);
|
|
17934
|
+
var REVISION_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
|
|
17935
|
+
function localNameOf3(node) {
|
|
17936
|
+
return String(node?.localName || node?.nodeName || "").replace(/^.*:/, "");
|
|
17937
|
+
}
|
|
17938
|
+
function elementsByLocalName(root, name) {
|
|
17939
|
+
return Array.from(root.getElementsByTagName("*")).filter((el) => localNameOf3(el) === name);
|
|
17940
|
+
}
|
|
17941
|
+
function wordAttribute3(node, name) {
|
|
17942
|
+
return node.getAttribute(`w:${name}`) || node.getAttribute(name) || "";
|
|
17943
|
+
}
|
|
17944
|
+
function xmlSpaceAttribute(node) {
|
|
17945
|
+
return node.getAttribute("xml:space") || node.getAttribute("space") || node.getAttributeNS?.("http://www.w3.org/XML/1998/namespace", "space") || "";
|
|
17946
|
+
}
|
|
17947
|
+
function isParagraphMarkRevision(node) {
|
|
17948
|
+
return localNameOf3(node.parentNode) === "rPr";
|
|
17949
|
+
}
|
|
17950
|
+
function parseOoxmlForValidation(oxml) {
|
|
17951
|
+
const attempt = (xml) => {
|
|
17952
|
+
const doc = parseXml(xml);
|
|
17953
|
+
const parseError = doc.getElementsByTagName("parsererror")[0];
|
|
17954
|
+
if (parseError) {
|
|
17955
|
+
throw new Error(parseError.textContent || "XML parse error");
|
|
17956
|
+
}
|
|
17957
|
+
return doc;
|
|
17958
|
+
};
|
|
17959
|
+
try {
|
|
17960
|
+
return { doc: attempt(oxml) };
|
|
17961
|
+
} catch {
|
|
17962
|
+
try {
|
|
17963
|
+
return { doc: attempt(`<w:root xmlns:w="${NS_W}">${oxml}</w:root>`) };
|
|
17964
|
+
} catch (error2) {
|
|
17965
|
+
return { error: error2?.message || "XML parse error" };
|
|
17966
|
+
}
|
|
17967
|
+
}
|
|
17968
|
+
}
|
|
17969
|
+
function validateRedlineOoxml(oxml) {
|
|
17970
|
+
const issues = [];
|
|
17971
|
+
const addIssue = (code, severity, message) => issues.push({ code, severity, message });
|
|
17972
|
+
const isDomNode = oxml && typeof oxml === "object" && (oxml.nodeType === 9 || oxml.nodeType === 1);
|
|
17973
|
+
if (!isDomNode && (typeof oxml !== "string" || oxml.trim() === "")) {
|
|
17974
|
+
addIssue("PARSE_ERROR", "error", "Input is not non-empty OOXML or a parsed XML DOM.");
|
|
17975
|
+
return { valid: false, issues };
|
|
17976
|
+
}
|
|
17977
|
+
const { doc, error: error2 } = isDomNode ? { doc: oxml.nodeType === 9 ? oxml : oxml.ownerDocument } : parseOoxmlForValidation(oxml);
|
|
17978
|
+
if (!doc) {
|
|
17979
|
+
addIssue("PARSE_ERROR", "error", `OOXML does not parse as XML: ${error2}`);
|
|
17980
|
+
return { valid: false, issues };
|
|
17981
|
+
}
|
|
17982
|
+
const insElements = elementsByLocalName(doc, "ins");
|
|
17983
|
+
const delElements = elementsByLocalName(doc, "del");
|
|
17984
|
+
const revisions = insElements.concat(delElements);
|
|
17985
|
+
for (const paragraph of elementsByLocalName(doc, "p")) {
|
|
17986
|
+
const nested = Array.from(paragraph.getElementsByTagName("*")).find((el) => el !== paragraph && localNameOf3(el) === "p");
|
|
17987
|
+
if (nested) {
|
|
17988
|
+
addIssue("NESTED_PARAGRAPH", "error", `<${paragraph.nodeName}> contains nested <${nested.nodeName}>.`);
|
|
17989
|
+
}
|
|
17990
|
+
}
|
|
17991
|
+
for (const body of elementsByLocalName(doc, "body")) {
|
|
17992
|
+
const children = Array.from(body.childNodes || []).filter((child) => child.nodeType === 1);
|
|
17993
|
+
const sectPrIndexes = children.map((child, index) => localNameOf3(child) === "sectPr" ? index : -1).filter((index) => index >= 0);
|
|
17994
|
+
if (sectPrIndexes.length > 1) {
|
|
17995
|
+
addIssue("MULTIPLE_BODY_SECTPR", "error", "<w:body> contains multiple direct <w:sectPr> elements.");
|
|
17996
|
+
} else if (sectPrIndexes.length === 1 && sectPrIndexes[0] !== children.length - 1) {
|
|
17997
|
+
addIssue("SECTPR_NOT_LAST", "error", "<w:sectPr> is not the last element child of <w:body>.");
|
|
17998
|
+
}
|
|
17999
|
+
}
|
|
18000
|
+
for (const revision of revisions) {
|
|
18001
|
+
const nested = Array.from(revision.getElementsByTagName("*")).filter((el) => el !== revision && ["ins", "del"].includes(localNameOf3(el)));
|
|
18002
|
+
const invalidNested = nested.find((candidate) => {
|
|
18003
|
+
if (localNameOf3(revision) !== "ins" || localNameOf3(candidate) !== "del") return true;
|
|
18004
|
+
return candidate.parentNode !== revision;
|
|
18005
|
+
});
|
|
18006
|
+
if (invalidNested) {
|
|
18007
|
+
addIssue(
|
|
18008
|
+
"NESTED_REVISION",
|
|
18009
|
+
"error",
|
|
18010
|
+
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") contains invalid nested <${invalidNested.nodeName}>.`
|
|
18011
|
+
);
|
|
18012
|
+
}
|
|
18013
|
+
}
|
|
18014
|
+
for (const del of delElements) {
|
|
18015
|
+
const plainTextNodes = elementsByLocalName(del, "t");
|
|
18016
|
+
if (plainTextNodes.length > 0) {
|
|
18017
|
+
addIssue(
|
|
18018
|
+
"DEL_CONTAINS_T",
|
|
18019
|
+
"error",
|
|
18020
|
+
`<w:del> (w:id="${wordAttribute3(del, "id")}") contains <w:t>; deleted text must use <w:delText>.`
|
|
18021
|
+
);
|
|
18022
|
+
}
|
|
18023
|
+
}
|
|
18024
|
+
for (const revision of revisions) {
|
|
18025
|
+
const missing = [];
|
|
18026
|
+
if (!wordAttribute3(revision, "id")) missing.push("w:id");
|
|
18027
|
+
if (!wordAttribute3(revision, "author")) missing.push("w:author");
|
|
18028
|
+
if (!REVISION_DATE_PATTERN.test(wordAttribute3(revision, "date"))) missing.push("w:date");
|
|
18029
|
+
if (missing.length > 0) {
|
|
18030
|
+
addIssue(
|
|
18031
|
+
"MISSING_REVISION_METADATA",
|
|
18032
|
+
"error",
|
|
18033
|
+
`<${revision.nodeName}> is missing or has malformed ${missing.join(", ")}.`
|
|
18034
|
+
);
|
|
18035
|
+
}
|
|
18036
|
+
}
|
|
18037
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
18038
|
+
for (const node of Array.from(doc.getElementsByTagName("*"))) {
|
|
18039
|
+
if (!REVISION_ID_ELEMENTS.has(localNameOf3(node))) continue;
|
|
18040
|
+
const id = wordAttribute3(node, "id");
|
|
18041
|
+
if (!id) continue;
|
|
18042
|
+
if (seenIds.has(id)) {
|
|
18043
|
+
addIssue("DUPLICATE_REVISION_ID", "error", `Revision id ${id} appears more than once.`);
|
|
18044
|
+
}
|
|
18045
|
+
seenIds.add(id);
|
|
18046
|
+
}
|
|
18047
|
+
const textNodes = elementsByLocalName(doc, "t").concat(elementsByLocalName(doc, "delText"));
|
|
18048
|
+
for (const node of textNodes) {
|
|
18049
|
+
const text2 = node.textContent || "";
|
|
18050
|
+
if (/^\s|\s$/.test(text2) && xmlSpaceAttribute(node) !== "preserve") {
|
|
18051
|
+
addIssue(
|
|
18052
|
+
"MISSING_SPACE_PRESERVE",
|
|
18053
|
+
"error",
|
|
18054
|
+
`<${node.nodeName}> has boundary whitespace without xml:space="preserve".`
|
|
18055
|
+
);
|
|
18056
|
+
}
|
|
18057
|
+
if (text2 === "") {
|
|
18058
|
+
addIssue("EMPTY_TEXT_ELEMENT", "warning", `<${node.nodeName}> is empty.`);
|
|
18059
|
+
}
|
|
18060
|
+
}
|
|
18061
|
+
for (const revision of revisions) {
|
|
18062
|
+
if (isParagraphMarkRevision(revision)) continue;
|
|
18063
|
+
const hasElementChild = Array.from(revision.childNodes || []).some((child) => child.nodeType === 1);
|
|
18064
|
+
if (!hasElementChild) {
|
|
18065
|
+
addIssue(
|
|
18066
|
+
"EMPTY_REVISION_WRAPPER",
|
|
18067
|
+
"warning",
|
|
18068
|
+
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") wraps no content.`
|
|
18069
|
+
);
|
|
18070
|
+
}
|
|
18071
|
+
}
|
|
18072
|
+
for (const resurrection of findForeignDeletedParagraphResurrections(doc)) {
|
|
18073
|
+
const ownerAuthor = resurrection.ownerAuthor || "unattributed";
|
|
18074
|
+
addIssue(
|
|
18075
|
+
"FOREIGN_PARAGRAPH_MARK_DELETION",
|
|
18076
|
+
"warning",
|
|
18077
|
+
`Paragraph deleted by ${ownerAuthor} also contains non-empty insertion content from another author; Accept/Reject lifecycle may not preserve the apparent restoration.`
|
|
18078
|
+
);
|
|
18079
|
+
}
|
|
18080
|
+
return { valid: !issues.some((issue) => issue.severity === "error"), issues };
|
|
18081
|
+
}
|
|
18082
|
+
|
|
18083
|
+
// core/table-targeting.js
|
|
18084
|
+
function getDirectWordChildren(element, localName2) {
|
|
18085
|
+
if (!element) return [];
|
|
18086
|
+
return Array.from(element.childNodes || []).filter(
|
|
18087
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === WORD_MAIN_NS && node.localName === localName2
|
|
18088
|
+
);
|
|
18089
|
+
}
|
|
18090
|
+
function escapeMarkdownCell(text2) {
|
|
18091
|
+
return String(text2 || "").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
18092
|
+
}
|
|
18093
|
+
function extractTableMatrix(tableElement) {
|
|
18094
|
+
const rowElements = getDirectWordChildren(tableElement, "tr");
|
|
18095
|
+
const matrix = rowElements.map((row) => {
|
|
18096
|
+
const cellElements = getDirectWordChildren(row, "tc");
|
|
18097
|
+
return cellElements.map((cell) => {
|
|
18098
|
+
const paragraphs = getDirectWordChildren(cell, "p");
|
|
18099
|
+
if (paragraphs.length === 0) return normalizeWhitespaceForTargeting(getParagraphText(cell));
|
|
18100
|
+
const lines = paragraphs.map((p) => normalizeWhitespaceForTargeting(getParagraphText(p))).filter(Boolean);
|
|
18101
|
+
return lines.join("\n");
|
|
18102
|
+
});
|
|
18103
|
+
});
|
|
18104
|
+
const columnCount = matrix.reduce((max2, row) => Math.max(max2, row.length), 0);
|
|
18105
|
+
matrix.forEach((row) => {
|
|
18106
|
+
while (row.length < columnCount) row.push("");
|
|
18107
|
+
});
|
|
18108
|
+
return { matrix, rowElements, columnCount };
|
|
18109
|
+
}
|
|
18110
|
+
function tableMatrixToMarkdown(matrix, columnCount) {
|
|
18111
|
+
if (!Array.isArray(matrix) || matrix.length === 0 || columnCount <= 0) return null;
|
|
18112
|
+
const normalized = matrix.map((row) => {
|
|
18113
|
+
const copy = Array.isArray(row) ? row.slice(0, columnCount) : [];
|
|
18114
|
+
while (copy.length < columnCount) copy.push("");
|
|
18115
|
+
return copy;
|
|
18116
|
+
});
|
|
18117
|
+
const header = normalized[0];
|
|
18118
|
+
const separator = new Array(columnCount).fill("---");
|
|
18119
|
+
const bodyRows = normalized.slice(1);
|
|
18120
|
+
const toLine = (row) => `| ${row.map((cell) => escapeMarkdownCell(cell)).join(" | ")} |`;
|
|
18121
|
+
return [toLine(header), toLine(separator), ...bodyRows.map(toLine)].join("\n");
|
|
18122
|
+
}
|
|
18123
|
+
function isSymmetricLabelRow(rowValues) {
|
|
18124
|
+
if (!Array.isArray(rowValues) || rowValues.length < 2) return false;
|
|
18125
|
+
const normalized = rowValues.map((value) => normalizeWhitespaceForTargeting(value)).filter(Boolean);
|
|
18126
|
+
if (normalized.length < 2) return false;
|
|
18127
|
+
return normalized.every((value) => value === normalized[0]);
|
|
18128
|
+
}
|
|
18129
|
+
function isLikelyStructuredTableSourceParagraph(text2) {
|
|
18130
|
+
const normalized = String(text2 || "").trim();
|
|
18131
|
+
if (!normalized) return false;
|
|
18132
|
+
if (/^and$/i.test(normalized)) return true;
|
|
18133
|
+
if (/^\[.*\]$/.test(normalized)) return true;
|
|
18134
|
+
if (/^\(.*\)$/.test(normalized)) return true;
|
|
18135
|
+
if (/:\s*$/.test(normalized)) return true;
|
|
18136
|
+
if (normalized.length <= 90 && !/[.!?]$/.test(normalized) && /[:\[\]()]/.test(normalized)) return true;
|
|
18137
|
+
if (/^[\[(]/.test(normalized)) return true;
|
|
18138
|
+
return false;
|
|
18139
|
+
}
|
|
18140
|
+
function inferTableReplacementParagraphBlock(startParagraph, options = {}) {
|
|
18141
|
+
const maxScan = Number.isInteger(options?.maxScan) && options.maxScan > 0 ? options.maxScan : 10;
|
|
18142
|
+
const paragraphTextGetter = typeof options?.getParagraphText === "function" ? options.getParagraphText : getParagraphText;
|
|
18143
|
+
if (!startParagraph || !startParagraph.parentNode) return null;
|
|
18144
|
+
const block = [startParagraph];
|
|
18145
|
+
let cursor = startParagraph.nextSibling;
|
|
18146
|
+
let scanned = 0;
|
|
18147
|
+
while (cursor && scanned < maxScan) {
|
|
18148
|
+
scanned += 1;
|
|
18149
|
+
const nextCursor = cursor.nextSibling;
|
|
18150
|
+
if (cursor.nodeType !== 1 || cursor.namespaceURI !== WORD_MAIN_NS || cursor.localName !== "p") {
|
|
18151
|
+
cursor = nextCursor;
|
|
18152
|
+
continue;
|
|
18153
|
+
}
|
|
18154
|
+
const text2 = String(paragraphTextGetter(cursor) || "").trim();
|
|
18155
|
+
if (!text2) {
|
|
18156
|
+
if (block.length > 1) break;
|
|
18157
|
+
cursor = nextCursor;
|
|
18158
|
+
continue;
|
|
17971
18159
|
}
|
|
18160
|
+
if (!isLikelyStructuredTableSourceParagraph(text2)) break;
|
|
18161
|
+
block.push(cursor);
|
|
18162
|
+
cursor = nextCursor;
|
|
17972
18163
|
}
|
|
17973
|
-
return null;
|
|
17974
|
-
}
|
|
17975
|
-
function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startAt) {
|
|
17976
|
-
const safeNumId = String(numId);
|
|
17977
|
-
const safeAbstractNumId = String(abstractNumId);
|
|
17978
|
-
const safeStartAt = Number.isInteger(startAt) && startAt > 0 ? startAt : 1;
|
|
17979
|
-
const levelsXml = Array.from({ length: 9 }, (_, level) => {
|
|
17980
|
-
const lvlText = Array.from({ length: level + 1 }, (_2, i2) => `%${i2 + 1}`).join(".") + ".";
|
|
17981
|
-
const left = 720 * (level + 1);
|
|
17982
|
-
return `
|
|
17983
|
-
<w:lvl w:ilvl="${level}">
|
|
17984
|
-
<w:start w:val="1"/>
|
|
17985
|
-
<w:numFmt w:val="decimal"/>
|
|
17986
|
-
<w:lvlText w:val="${lvlText}"/>
|
|
17987
|
-
<w:lvlJc w:val="left"/>
|
|
17988
|
-
<w:pPr><w:ind w:left="${left}" w:hanging="360"/></w:pPr>
|
|
17989
|
-
</w:lvl>`;
|
|
17990
|
-
}).join("");
|
|
17991
|
-
return `
|
|
17992
|
-
<w:numbering xmlns:w="${WORD_MAIN_NS}">
|
|
17993
|
-
<w:abstractNum w:abstractNumId="${safeAbstractNumId}">
|
|
17994
|
-
<w:multiLevelType w:val="multilevel"/>
|
|
17995
|
-
${levelsXml}
|
|
17996
|
-
</w:abstractNum>
|
|
17997
|
-
<w:num w:numId="${safeNumId}">
|
|
17998
|
-
<w:abstractNumId w:val="${safeAbstractNumId}"/>
|
|
17999
|
-
<w:lvlOverride w:ilvl="0">
|
|
18000
|
-
<w:startOverride w:val="${safeStartAt}"/>
|
|
18001
|
-
</w:lvlOverride>
|
|
18002
|
-
</w:num>
|
|
18003
|
-
</w:numbering>`.trim();
|
|
18164
|
+
return block.length > 1 ? block : null;
|
|
18004
18165
|
}
|
|
18005
|
-
function
|
|
18006
|
-
const
|
|
18007
|
-
|
|
18008
|
-
|
|
18009
|
-
|
|
18010
|
-
|
|
18011
|
-
|
|
18166
|
+
function synthesizeTableMarkdownFromMultilineCellEdit(targetParagraph, modifiedText, options = {}) {
|
|
18167
|
+
const onInfo = typeof options.onInfo === "function" ? options.onInfo : () => {
|
|
18168
|
+
};
|
|
18169
|
+
const onWarn = typeof options.onWarn === "function" ? options.onWarn : () => {
|
|
18170
|
+
};
|
|
18171
|
+
const rawModified = String(modifiedText || "");
|
|
18172
|
+
if (!rawModified.includes("\n")) return null;
|
|
18173
|
+
if (isMarkdownTableText(rawModified)) return null;
|
|
18174
|
+
const lines = rawModified.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean);
|
|
18175
|
+
if (lines.length < 2) return null;
|
|
18176
|
+
const tableElement = options.tableElement || findContainingWordElement(targetParagraph, "tbl");
|
|
18177
|
+
const rowElement = findContainingWordElement(targetParagraph, "tr");
|
|
18178
|
+
const cellElement = findContainingWordElement(targetParagraph, "tc");
|
|
18179
|
+
if (!tableElement || !rowElement || !cellElement) return null;
|
|
18180
|
+
const currentParagraphText = normalizeWhitespaceForTargeting(
|
|
18181
|
+
options.currentParagraphText || getParagraphText(targetParagraph)
|
|
18182
|
+
);
|
|
18183
|
+
const firstLine = normalizeWhitespaceForTargeting(lines[0]);
|
|
18184
|
+
if (currentParagraphText && firstLine && firstLine !== currentParagraphText) {
|
|
18185
|
+
onWarn("[Table] Multiline cell text did not anchor to original cell text; skipping table-row synthesis heuristic.");
|
|
18186
|
+
return null;
|
|
18012
18187
|
}
|
|
18013
|
-
const
|
|
18014
|
-
|
|
18015
|
-
const
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
|
|
18188
|
+
const { matrix, rowElements, columnCount } = extractTableMatrix(tableElement);
|
|
18189
|
+
if (matrix.length === 0 || columnCount === 0) return null;
|
|
18190
|
+
const rowIndex = rowElements.indexOf(rowElement);
|
|
18191
|
+
const cellElements = getDirectWordChildren(rowElement, "tc");
|
|
18192
|
+
const colIndex = cellElements.indexOf(cellElement);
|
|
18193
|
+
if (rowIndex < 0 || colIndex < 0 || colIndex >= columnCount) return null;
|
|
18194
|
+
matrix[rowIndex][colIndex] = lines[0];
|
|
18195
|
+
const mirrorAcrossColumns = isSymmetricLabelRow(matrix[rowIndex]);
|
|
18196
|
+
if (mirrorAcrossColumns) {
|
|
18197
|
+
onInfo("[Table] Symmetric row detected; mirroring inserted row values across columns.");
|
|
18023
18198
|
}
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
const
|
|
18027
|
-
if (
|
|
18028
|
-
|
|
18029
|
-
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
setElementVal2(abstractNumIdNode, abstractNumMap.get(oldAbsRef));
|
|
18199
|
+
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
18200
|
+
const insertIndex = rowIndex + i2;
|
|
18201
|
+
const extraValue = lines[i2];
|
|
18202
|
+
if (insertIndex < matrix.length && !normalizeWhitespaceForTargeting(matrix[insertIndex][colIndex])) {
|
|
18203
|
+
if (mirrorAcrossColumns) {
|
|
18204
|
+
for (let col = 0; col < columnCount; col++) {
|
|
18205
|
+
if (!normalizeWhitespaceForTargeting(matrix[insertIndex][col])) {
|
|
18206
|
+
matrix[insertIndex][col] = extraValue;
|
|
18207
|
+
}
|
|
18208
|
+
}
|
|
18209
|
+
} else {
|
|
18210
|
+
matrix[insertIndex][colIndex] = extraValue;
|
|
18037
18211
|
}
|
|
18038
|
-
}
|
|
18039
|
-
|
|
18040
|
-
|
|
18041
|
-
|
|
18042
|
-
|
|
18043
|
-
|
|
18044
|
-
const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
|
|
18045
|
-
if (oldNumRef != null && numIdMap.has(oldNumRef)) {
|
|
18046
|
-
setElementVal2(numIdNode, numIdMap.get(oldNumRef));
|
|
18212
|
+
} else {
|
|
18213
|
+
const newRow = new Array(columnCount).fill("");
|
|
18214
|
+
if (mirrorAcrossColumns) {
|
|
18215
|
+
for (let col = 0; col < columnCount; col++) newRow[col] = extraValue;
|
|
18216
|
+
} else {
|
|
18217
|
+
newRow[colIndex] = extraValue;
|
|
18047
18218
|
}
|
|
18219
|
+
matrix.splice(Math.min(insertIndex, matrix.length), 0, newRow);
|
|
18048
18220
|
}
|
|
18049
18221
|
}
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
}
|
|
18055
|
-
function mergeNumberingXmlBySchemaOrder(existingNumberingXml, incomingNumberingXml) {
|
|
18056
|
-
const existingText = String(existingNumberingXml || "");
|
|
18057
|
-
const incomingText = String(incomingNumberingXml || "");
|
|
18058
|
-
if (!incomingText.trim()) return existingText;
|
|
18059
|
-
if (!existingText.trim()) return incomingText;
|
|
18060
|
-
try {
|
|
18061
|
-
const existingDoc = parseOoxml(existingText);
|
|
18062
|
-
const incomingDoc = parseOoxml(incomingText);
|
|
18063
|
-
if (hasXmlParseError(existingDoc) || hasXmlParseError(incomingDoc)) {
|
|
18064
|
-
return existingText;
|
|
18065
|
-
}
|
|
18066
|
-
const existingRoot = existingDoc.documentElement;
|
|
18067
|
-
const incomingRoot = incomingDoc.documentElement;
|
|
18068
|
-
if (!existingRoot || !incomingRoot) return existingText;
|
|
18069
|
-
const existingAbstractIds = new Set(
|
|
18070
|
-
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "abstractNum")).map((node) => parseIntegerAttribute(node, ["w:abstractNumId", "abstractNumId"])).filter((id) => id != null)
|
|
18071
|
-
);
|
|
18072
|
-
const existingNumIds = new Set(
|
|
18073
|
-
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "num")).map((node) => parseIntegerAttribute(node, ["w:numId", "numId"])).filter((id) => id != null)
|
|
18074
|
-
);
|
|
18075
|
-
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
18076
|
-
if (!isDirectWordChild(incomingNode, "abstractNum")) continue;
|
|
18077
|
-
const incomingId = parseIntegerAttribute(incomingNode, ["w:abstractNumId", "abstractNumId"]);
|
|
18078
|
-
if (incomingId == null || existingAbstractIds.has(incomingId)) continue;
|
|
18079
|
-
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "abstract");
|
|
18080
|
-
existingAbstractIds.add(incomingId);
|
|
18081
|
-
}
|
|
18082
|
-
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
18083
|
-
if (!isDirectWordChild(incomingNode, "num")) continue;
|
|
18084
|
-
const incomingId = parseIntegerAttribute(incomingNode, ["w:numId", "numId"]);
|
|
18085
|
-
if (incomingId == null || existingNumIds.has(incomingId)) continue;
|
|
18086
|
-
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "num");
|
|
18087
|
-
existingNumIds.add(incomingId);
|
|
18088
|
-
}
|
|
18089
|
-
return serializeOoxml(existingDoc);
|
|
18090
|
-
} catch {
|
|
18091
|
-
return existingText;
|
|
18092
|
-
}
|
|
18222
|
+
const markdown = tableMatrixToMarkdown(matrix, columnCount);
|
|
18223
|
+
if (!markdown) return null;
|
|
18224
|
+
onInfo("[Table] Synthesized full markdown table from multiline cell edit for table-scope reconciliation.");
|
|
18225
|
+
return markdown;
|
|
18093
18226
|
}
|
|
18094
18227
|
|
|
18095
18228
|
// pipeline/ingestion-export.js
|
|
@@ -18859,6 +18992,8 @@ function areRevisionTokensEqual(a, b) {
|
|
|
18859
18992
|
var attr2 = (node, name) => node?.getAttribute?.(`w:${name}`) || node?.getAttribute?.(name) || "";
|
|
18860
18993
|
var descendants = (node, name) => Array.from(node?.getElementsByTagNameNS?.(NS_W, name) || []);
|
|
18861
18994
|
var first = (node, name) => descendants(node, name)[0] || null;
|
|
18995
|
+
var directChild2 = (node, name) => Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.localName === name && (!child.namespaceURI || child.namespaceURI === NS_W)) || null;
|
|
18996
|
+
var currentPPr = (paragraph) => directChild2(paragraph, "pPr");
|
|
18862
18997
|
function hasAncestor(node, localName2) {
|
|
18863
18998
|
let cursor = node?.parentNode;
|
|
18864
18999
|
while (cursor) {
|
|
@@ -18874,11 +19009,11 @@ function parseXml2(xml, partName, required = false) {
|
|
|
18874
19009
|
return { doc: parsed.doc, warnings: parsed.warnings || [] };
|
|
18875
19010
|
}
|
|
18876
19011
|
function paragraphListProperties(paragraph) {
|
|
18877
|
-
const numPr =
|
|
19012
|
+
const numPr = directChild2(currentPPr(paragraph), "numPr");
|
|
18878
19013
|
if (!numPr) return null;
|
|
18879
|
-
const numId = attr2(
|
|
19014
|
+
const numId = attr2(directChild2(numPr, "numId"), "val");
|
|
18880
19015
|
if (!numId || numId === "0") return null;
|
|
18881
|
-
return { numId, level: Number.parseInt(attr2(
|
|
19016
|
+
return { numId, level: Number.parseInt(attr2(directChild2(numPr, "ilvl"), "val") || "0", 10) || 0 };
|
|
18882
19017
|
}
|
|
18883
19018
|
function parseNumbering(numberingDoc) {
|
|
18884
19019
|
const abstracts = /* @__PURE__ */ new Map();
|
|
@@ -18963,11 +19098,11 @@ function createNumberingResolver(numberingDoc) {
|
|
|
18963
19098
|
};
|
|
18964
19099
|
}
|
|
18965
19100
|
function headingLevel(paragraph) {
|
|
18966
|
-
const pPr =
|
|
18967
|
-
const style = attr2(
|
|
19101
|
+
const pPr = currentPPr(paragraph);
|
|
19102
|
+
const style = attr2(directChild2(pPr, "pStyle"), "val");
|
|
18968
19103
|
const match = style.match(/^heading\s*([1-9])$/i);
|
|
18969
19104
|
if (match) return Math.min(Number(match[1]), 6);
|
|
18970
|
-
const outline = Number.parseInt(attr2(
|
|
19105
|
+
const outline = Number.parseInt(attr2(directChild2(pPr, "outlineLvl"), "val"), 10);
|
|
18971
19106
|
return Number.isInteger(outline) ? Math.min(outline + 1, 6) : null;
|
|
18972
19107
|
}
|
|
18973
19108
|
function structuralContext(paragraph, text2) {
|
|
@@ -19146,7 +19281,7 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
19146
19281
|
const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
|
|
19147
19282
|
const authors = revisionAuthors(paragraph);
|
|
19148
19283
|
const list = resolveNumbering(paragraphListProperties(paragraph));
|
|
19149
|
-
const styleId = attr2(
|
|
19284
|
+
const styleId = attr2(directChild2(currentPPr(paragraph), "pStyle"), "val") || null;
|
|
19150
19285
|
const structure = structuralContext(paragraph, text2);
|
|
19151
19286
|
const index = zeroIndex + 1;
|
|
19152
19287
|
const provision = list?.label && list.format !== "bullet" ? list.label : null;
|
|
@@ -21737,7 +21872,7 @@ function validateDocumentOperation(operation) {
|
|
|
21737
21872
|
}
|
|
21738
21873
|
};
|
|
21739
21874
|
}
|
|
21740
|
-
if (target.revisionView !== "accepted" || target.captureRef || target.occurrence != null || normalized.captureKey || normalized.targetEndDescriptor || normalized.targetEndRef != null
|
|
21875
|
+
if (target.revisionView !== "accepted" || target.captureRef || target.occurrence != null || normalized.captureKey || normalized.targetEndDescriptor || normalized.targetEndRef != null) {
|
|
21741
21876
|
return {
|
|
21742
21877
|
valid: false,
|
|
21743
21878
|
error: {
|
|
@@ -21759,6 +21894,15 @@ function validateDocumentOperation(operation) {
|
|
|
21759
21894
|
}
|
|
21760
21895
|
};
|
|
21761
21896
|
}
|
|
21897
|
+
if (replacementValidation.replacements.some((replacement) => /\r|\n/.test(replacement.find))) {
|
|
21898
|
+
return {
|
|
21899
|
+
valid: false,
|
|
21900
|
+
error: {
|
|
21901
|
+
code: "INVALID_OPERATION",
|
|
21902
|
+
message: 'Localized replacements v1 cannot span a line break; use one replacement per line or a full "modified" paragraph.'
|
|
21903
|
+
}
|
|
21904
|
+
};
|
|
21905
|
+
}
|
|
21762
21906
|
normalized.replacements = replacementValidation.replacements;
|
|
21763
21907
|
}
|
|
21764
21908
|
}
|
|
@@ -21953,6 +22097,12 @@ function validateDocumentOperation(operation) {
|
|
|
21953
22097
|
error: { code: "INVALID_OPERATION", message: 'Format operations require a non-empty "textToFormat" field.' }
|
|
21954
22098
|
};
|
|
21955
22099
|
}
|
|
22100
|
+
if (normalized.textOccurrence != null && (!Number.isInteger(normalized.textOccurrence) || normalized.textOccurrence < 1)) {
|
|
22101
|
+
return {
|
|
22102
|
+
valid: false,
|
|
22103
|
+
error: { code: "INVALID_OPERATION", message: "textOccurrence must be a positive integer when provided." }
|
|
22104
|
+
};
|
|
22105
|
+
}
|
|
21956
22106
|
if (!isRecord(normalized.properties)) {
|
|
21957
22107
|
return {
|
|
21958
22108
|
valid: false,
|
|
@@ -22409,7 +22559,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
22409
22559
|
};
|
|
22410
22560
|
const compiled = { ...sourceOperation };
|
|
22411
22561
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
22412
|
-
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
22562
|
+
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));
|
|
22413
22563
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
22414
22564
|
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
22415
22565
|
try {
|
|
@@ -22747,6 +22897,17 @@ function deriveSingleParagraphPlainAdjacencyInsertion(currentParagraphText, modi
|
|
|
22747
22897
|
}
|
|
22748
22898
|
return null;
|
|
22749
22899
|
}
|
|
22900
|
+
function deriveParagraphThenTableAppend(modifiedText) {
|
|
22901
|
+
const lines = String(modifiedText || "").split(/\r?\n/g).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
22902
|
+
if (lines.length < 3) return null;
|
|
22903
|
+
const tableStart = lines.findIndex((line) => line.trim().startsWith("|"));
|
|
22904
|
+
if (tableStart !== 1) return null;
|
|
22905
|
+
const tableLines = lines.slice(1);
|
|
22906
|
+
if (!tableLines.every((line) => line.trim().startsWith("|"))) return null;
|
|
22907
|
+
const separator = tableLines[1].replace(/\s+/g, "");
|
|
22908
|
+
if (!/^\|:?-{3,}:?(\|:?-{3,}:?)+\|?$/.test(separator)) return null;
|
|
22909
|
+
return { paragraphMarkdown: lines[0].trim(), tableText: tableLines.join("\n") };
|
|
22910
|
+
}
|
|
22750
22911
|
|
|
22751
22912
|
// services/capture-engine.js
|
|
22752
22913
|
function createCaptureError(code, message) {
|
|
@@ -24532,6 +24693,81 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24532
24693
|
numberingXml: null
|
|
24533
24694
|
};
|
|
24534
24695
|
}
|
|
24696
|
+
const tableAppendCandidate = !useTableScope && !hasExplicitRangeScope && !targetListInfo && !containingTable && !targetEndRef ? deriveParagraphThenTableAppend(effectiveModifiedText) : null;
|
|
24697
|
+
const tableAppendParagraph = tableAppendCandidate ? preprocessMarkdown(tableAppendCandidate.paragraphMarkdown) : null;
|
|
24698
|
+
if (tableAppendCandidate && normalizeWhitespaceForTargeting(tableAppendParagraph.cleanText) === normalizeWhitespaceForTargeting(currentParagraphText)) {
|
|
24699
|
+
const tableData = parseTable(tableAppendCandidate.tableText);
|
|
24700
|
+
if (tableData.headers.length > 0 || tableData.rows.length > 0) {
|
|
24701
|
+
onInfo("[Table] Applying retained-paragraph table append heuristic.");
|
|
24702
|
+
const parent2 = targetParagraph.parentNode;
|
|
24703
|
+
if (!parent2) throw new Error("Target paragraph has no parent for table append");
|
|
24704
|
+
const followingNode = targetParagraph.nextSibling;
|
|
24705
|
+
if (tableAppendParagraph.formatHints.length > 0) {
|
|
24706
|
+
const formatted = await applyRedlineToOxml2(
|
|
24707
|
+
serializer.serializeToString(targetParagraph),
|
|
24708
|
+
currentParagraphText || targetText,
|
|
24709
|
+
tableAppendCandidate.paragraphMarkdown,
|
|
24710
|
+
{
|
|
24711
|
+
author,
|
|
24712
|
+
generateRedlines,
|
|
24713
|
+
existingRevisions: options.existingRevisions || "merge-same-author",
|
|
24714
|
+
structuredContent: false,
|
|
24715
|
+
_revisionIdAllocator: revisionIdAllocator
|
|
24716
|
+
}
|
|
24717
|
+
);
|
|
24718
|
+
if (formatted?.status === "error") {
|
|
24719
|
+
return {
|
|
24720
|
+
documentXml,
|
|
24721
|
+
hasChanges: false,
|
|
24722
|
+
numberingXml: null,
|
|
24723
|
+
status: "error",
|
|
24724
|
+
error: formatted.error
|
|
24725
|
+
};
|
|
24726
|
+
}
|
|
24727
|
+
if (formatted?.hasChanges) {
|
|
24728
|
+
if (typeof formatted.oxml !== "string") {
|
|
24729
|
+
throw new Error("Reconciliation engine did not return OOXML for a changed paragraph format");
|
|
24730
|
+
}
|
|
24731
|
+
for (const node of extractReplacementNodes(formatted.oxml).replacementNodes) {
|
|
24732
|
+
const imported = xmlDoc.importNode(node, true);
|
|
24733
|
+
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
24734
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(imported, targetParagraph));
|
|
24735
|
+
}
|
|
24736
|
+
options?._mutationRemovedNodes?.push(targetParagraph);
|
|
24737
|
+
parent2.removeChild(targetParagraph);
|
|
24738
|
+
}
|
|
24739
|
+
}
|
|
24740
|
+
const tableOoxml = generateTableOoxml(tableData, {
|
|
24741
|
+
generateRedlines,
|
|
24742
|
+
author,
|
|
24743
|
+
revisionIdAllocator,
|
|
24744
|
+
trackAsBlock: true
|
|
24745
|
+
});
|
|
24746
|
+
for (const node of extractReplacementNodes(tableOoxml).replacementNodes) {
|
|
24747
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(xmlDoc.importNode(node, true), followingNode));
|
|
24748
|
+
}
|
|
24749
|
+
const endsContainer = !followingNode || followingNode.nodeType === 1 && followingNode.namespaceURI === NS_W9 && followingNode.localName === "sectPr";
|
|
24750
|
+
if (endsContainer) {
|
|
24751
|
+
const trailing = createWordElement(xmlDoc, "w:p");
|
|
24752
|
+
const trailingPPr = clonePropertiesWithoutRevisionHistory(getDirectWordChild4(targetParagraph, "pPr"));
|
|
24753
|
+
if (trailingPPr) {
|
|
24754
|
+
for (const child of Array.from(trailingPPr.childNodes)) {
|
|
24755
|
+
if (child.nodeType === 1 && ["sectPr", "numPr"].includes(child.localName)) trailingPPr.removeChild(child);
|
|
24756
|
+
}
|
|
24757
|
+
trailing.appendChild(trailingPPr);
|
|
24758
|
+
}
|
|
24759
|
+
if (generateRedlines) markParagraphMarkInserted(xmlDoc, trailing, author);
|
|
24760
|
+
ensureParagraphIdsOnImportedNode(trailing, operationSession);
|
|
24761
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(trailing, followingNode));
|
|
24762
|
+
}
|
|
24763
|
+
normalizeBodySectionOrder(xmlDoc);
|
|
24764
|
+
return {
|
|
24765
|
+
documentXml: completedDocumentXml(xmlDoc, serializer, documentXml, operationSession),
|
|
24766
|
+
hasChanges: true,
|
|
24767
|
+
numberingXml: null
|
|
24768
|
+
};
|
|
24769
|
+
}
|
|
24770
|
+
}
|
|
24535
24771
|
const plainAdjacencyInsertionCandidate = !bypassSingleParagraphHeuristics && !useTableScope && !hasExplicitRangeScope && !targetListInfo ? deriveSingleParagraphPlainAdjacencyInsertion(currentParagraphText, effectiveModifiedText) : null;
|
|
24536
24772
|
if (plainAdjacencyInsertionCandidate) {
|
|
24537
24773
|
onInfo(
|
|
@@ -24547,7 +24783,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24547
24783
|
paragraphText,
|
|
24548
24784
|
generateRedlines ? createRevisionMetadata(author, xmlDoc) : null,
|
|
24549
24785
|
author,
|
|
24550
|
-
|
|
24786
|
+
// A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
|
|
24787
|
+
// Reject All removes the text but leaves an empty paragraph behind.
|
|
24788
|
+
{ generateRedlines, trackParagraphMark: true }
|
|
24551
24789
|
);
|
|
24552
24790
|
const imported = xmlDoc.importNode(plainParagraph, true);
|
|
24553
24791
|
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
@@ -24655,6 +24893,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24655
24893
|
explicitStructuredContent: options.explicitStructuredContent === true,
|
|
24656
24894
|
pairReplacements: options.pairReplacements === true,
|
|
24657
24895
|
insertionAffinity: options.insertionAffinity || null,
|
|
24896
|
+
literalTextEdit: options.literalTextEdit === true,
|
|
24658
24897
|
_revisionIdAllocator: revisionIdAllocator,
|
|
24659
24898
|
_isolatedTableCell: useTableScope
|
|
24660
24899
|
});
|
|
@@ -24678,6 +24917,14 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24678
24917
|
const extracted = extractReplacementNodes(result.oxml);
|
|
24679
24918
|
let replacementNodes = removeListPackagingSentinel(extracted.replacementNodes, result.warnings);
|
|
24680
24919
|
let numberingXml = extracted.numberingXml;
|
|
24920
|
+
if (numberingXml && runtimeContext?.numberingIdState && !useTableScope) {
|
|
24921
|
+
const sourceParagraphs = useListScope ? listScopeEdit.paragraphs : explicitRangeParagraphs || [targetParagraph];
|
|
24922
|
+
const continued = reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, runtimeContext.numberingIdState);
|
|
24923
|
+
if (continued) {
|
|
24924
|
+
replacementNodes = continued.replacementNodes;
|
|
24925
|
+
numberingXml = null;
|
|
24926
|
+
}
|
|
24927
|
+
}
|
|
24681
24928
|
if (numberingXml && runtimeContext?.numberingIdState) {
|
|
24682
24929
|
const normalizedNumbering = remapNumberingPayloadForDocument(numberingXml, replacementNodes, runtimeContext.numberingIdState);
|
|
24683
24930
|
replacementNodes = normalizedNumbering.replacementNodes;
|
|
@@ -24934,7 +25181,8 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24934
25181
|
spanRanges.push({ ...entry, start, end });
|
|
24935
25182
|
}
|
|
24936
25183
|
const targetDescriptor = options.targetDescriptor;
|
|
24937
|
-
const
|
|
25184
|
+
const explicitTextOccurrence = Number.isInteger(options.textOccurrence) && options.textOccurrence > 0 ? options.textOccurrence : null;
|
|
25185
|
+
const occurrence = explicitTextOccurrence ?? targetDescriptor?.occurrence ?? null;
|
|
24938
25186
|
let matchStart = -1;
|
|
24939
25187
|
if (occurrence != null && occurrence > 0) {
|
|
24940
25188
|
let count = 0;
|
|
@@ -24952,6 +25200,17 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24952
25200
|
} else {
|
|
24953
25201
|
matchStart = fullParaText.indexOf(textToFormat);
|
|
24954
25202
|
}
|
|
25203
|
+
if (matchStart === -1 && explicitTextOccurrence != null && fullParaText.includes(textToFormat)) {
|
|
25204
|
+
return {
|
|
25205
|
+
documentXml,
|
|
25206
|
+
hasChanges: false,
|
|
25207
|
+
status: "error",
|
|
25208
|
+
error: {
|
|
25209
|
+
code: "PATCH_SOURCE_NOT_FOUND",
|
|
25210
|
+
message: `Occurrence ${explicitTextOccurrence} of text to format was not found in target paragraph: "${textToFormat}".`
|
|
25211
|
+
}
|
|
25212
|
+
};
|
|
25213
|
+
}
|
|
24955
25214
|
if (matchStart === -1) {
|
|
24956
25215
|
return {
|
|
24957
25216
|
documentXml,
|
|
@@ -25234,9 +25493,11 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
25234
25493
|
...operation.existingRevisions ? { existingRevisions: operation.existingRevisions } : {},
|
|
25235
25494
|
structuredContent: typeof operation.structuredContent === "boolean" ? operation.structuredContent : options.structuredContent !== false,
|
|
25236
25495
|
explicitStructuredContent: operation.structuredContent === true,
|
|
25496
|
+
...operation._localizedReplacementCompilation && operation.structuredContent !== true ? { literalTextEdit: true } : {},
|
|
25237
25497
|
pairReplacements: typeof operation.pairReplacements === "boolean" ? operation.pairReplacements : options.pairReplacements !== false,
|
|
25238
25498
|
...operation.insertionAffinity ? { insertionAffinity: operation.insertionAffinity } : {},
|
|
25239
25499
|
...operation.formattingRevisionPolicy ? { formattingRevisionPolicy: operation.formattingRevisionPolicy } : {},
|
|
25500
|
+
...operation.operationKind === "format" && Number.isInteger(operation.textOccurrence) ? { textOccurrence: operation.textOccurrence } : {},
|
|
25240
25501
|
targetDescriptor: operation.targetDescriptor,
|
|
25241
25502
|
targetEndDescriptor: operation.targetEndDescriptor,
|
|
25242
25503
|
_compiledSourceId: operation._compiledSourceId || null,
|
|
@@ -25860,6 +26121,9 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25860
26121
|
const scheduled = dependencyPlan.scheduled;
|
|
25861
26122
|
const atomic = options.atomic === true;
|
|
25862
26123
|
const continueOnError = options.continueOnError !== false;
|
|
26124
|
+
if (runtimeContext && typeof runtimeContext === "object" && !runtimeContext.numberingIdState && typeof runtimeContext.numberingXml === "string" && runtimeContext.numberingXml.trim()) {
|
|
26125
|
+
runtimeContext.numberingIdState = createDynamicNumberingIdState(runtimeContext.numberingXml);
|
|
26126
|
+
}
|
|
25863
26127
|
const context = cloneBatchRuntimeContext(runtimeContext);
|
|
25864
26128
|
if (!(context.targetRefSnapshot instanceof Map)) {
|
|
25865
26129
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
@@ -28285,8 +28549,32 @@ function openDocx(input) {
|
|
|
28285
28549
|
}
|
|
28286
28550
|
|
|
28287
28551
|
// index.js
|
|
28552
|
+
function remapGeneratedNumberingForSource(result, options) {
|
|
28553
|
+
const sourceNumbering = typeof options?.numberingXml === "string" && options.numberingXml.trim() ? options.numberingXml : null;
|
|
28554
|
+
const state = options?.numberingIdState && typeof options.numberingIdState === "object" ? options.numberingIdState : sourceNumbering ? createDynamicNumberingIdState(sourceNumbering) : null;
|
|
28555
|
+
if (!state || !result?.hasChanges || typeof result.oxml !== "string" || !result.oxml.includes("/word/numbering.xml")) {
|
|
28556
|
+
return result;
|
|
28557
|
+
}
|
|
28558
|
+
const pkgDoc = parseOoxml(result.oxml);
|
|
28559
|
+
const parts = Array.from(pkgDoc.getElementsByTagNameNS("*", "part"));
|
|
28560
|
+
const payloadOf = (name) => {
|
|
28561
|
+
const part = parts.find((candidate) => (candidate.getAttribute("pkg:name") || candidate.getAttribute("name")) === name);
|
|
28562
|
+
const xmlData = part?.getElementsByTagNameNS("*", "xmlData")[0];
|
|
28563
|
+
return xmlData ? Array.from(xmlData.childNodes).find((node) => node.nodeType === 1) || null : null;
|
|
28564
|
+
};
|
|
28565
|
+
const numberingNode = payloadOf("/word/numbering.xml");
|
|
28566
|
+
const body = payloadOf("/word/document.xml")?.getElementsByTagNameNS("*", "body")[0];
|
|
28567
|
+
if (!numberingNode || !body) return result;
|
|
28568
|
+
const bodyNodes = Array.from(body.childNodes).filter((node) => node.nodeType === 1 && node.localName !== "sectPr");
|
|
28569
|
+
const remapped = remapNumberingPayloadForDocument(serializeOoxml(numberingNode), bodyNodes, state);
|
|
28570
|
+
const newNumbering = pkgDoc.importNode(parseOoxml(remapped.numberingXml).documentElement, true);
|
|
28571
|
+
numberingNode.parentNode.replaceChild(newNumbering, numberingNode);
|
|
28572
|
+
bodyNodes.forEach((node, index) => body.replaceChild(remapped.replacementNodes[index], node));
|
|
28573
|
+
return { ...result, oxml: serializeOoxml(pkgDoc) };
|
|
28574
|
+
}
|
|
28288
28575
|
async function applyRedlineToOxml3(oxml, originalText, modifiedText, options = {}) {
|
|
28289
|
-
const
|
|
28576
|
+
const rawResult = await applyRedlineToOxml(oxml, originalText, modifiedText, options);
|
|
28577
|
+
const result = remapGeneratedNumberingForSource(rawResult, options);
|
|
28290
28578
|
if (result?.useNativeApi && typeof result?.oxml !== "string") {
|
|
28291
28579
|
const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
|
|
28292
28580
|
return withOoxmlSourceType({
|