@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 v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js 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;
|
|
@@ -3254,7 +3254,14 @@ function buildRunXmlWithHints(item, formatHints, options = {}) {
|
|
|
3254
3254
|
function buildSimpleRun(text2, rPrXml) {
|
|
3255
3255
|
if (!text2) return "";
|
|
3256
3256
|
const rPr = rPrXml || "";
|
|
3257
|
-
return `<w:r>${rPr}
|
|
3257
|
+
return `<w:r>${rPr}${buildRunContent(text2, "w:t")}</w:r>`;
|
|
3258
|
+
}
|
|
3259
|
+
function buildRunContent(text2, textTag) {
|
|
3260
|
+
return String(text2).split(/(\r\n|[\n\r\v\t])/).map((part) => {
|
|
3261
|
+
if (part === " ") return "<w:tab/>";
|
|
3262
|
+
if (part === "\n" || part === "\r" || part === "\r\n" || part === "\v") return "<w:br/>";
|
|
3263
|
+
return part ? `<${textTag} xml:space="preserve">${escapeXml(part)}</${textTag}>` : "";
|
|
3264
|
+
}).join("");
|
|
3258
3265
|
}
|
|
3259
3266
|
function buildDeletionXml(item, options = {}) {
|
|
3260
3267
|
const metadata = createRevisionMetadata(
|
|
@@ -3267,7 +3274,7 @@ function buildDeletionXml(item, options = {}) {
|
|
|
3267
3274
|
if (font) {
|
|
3268
3275
|
rPr = applyFont(rPr, font);
|
|
3269
3276
|
}
|
|
3270
|
-
return `<w:del w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}"><w:r>${rPr}
|
|
3277
|
+
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>`;
|
|
3271
3278
|
}
|
|
3272
3279
|
function buildInsertionXml(item, formatHints, options = {}) {
|
|
3273
3280
|
const metadata = createRevisionMetadata(
|
|
@@ -3608,7 +3615,7 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
3608
3615
|
revisionIdAllocator = null,
|
|
3609
3616
|
trackAsBlock = false
|
|
3610
3617
|
} = options;
|
|
3611
|
-
const
|
|
3618
|
+
const trackRows = generateRedlines && trackAsBlock;
|
|
3612
3619
|
const numCols = tableData.headers?.length || (tableData.rows?.[0]?.length || 1);
|
|
3613
3620
|
const tblPr = `
|
|
3614
3621
|
<w:tblPr>
|
|
@@ -3636,14 +3643,16 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
3636
3643
|
const cellText = row[c] || "";
|
|
3637
3644
|
const { cleanText, formatHints } = preprocessMarkdown(cellText);
|
|
3638
3645
|
const runModel = [{
|
|
3639
|
-
kind: generateRedlines
|
|
3646
|
+
kind: generateRedlines ? RunKind.INSERTION : RunKind.TEXT,
|
|
3640
3647
|
text: cleanText,
|
|
3641
3648
|
rPrXml: isHeaderRow ? "<w:rPr><w:b/></w:rPr>" : "",
|
|
3642
3649
|
author,
|
|
3643
3650
|
startOffset: 0,
|
|
3644
3651
|
endOffset: cleanText.length
|
|
3645
3652
|
}];
|
|
3646
|
-
const
|
|
3653
|
+
const cellMarkMeta = trackRows ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
3654
|
+
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;
|
|
3655
|
+
const runsOoxml = serializeToOoxml(runModel, cellPPr, formatHints, {
|
|
3647
3656
|
author,
|
|
3648
3657
|
generateRedlines,
|
|
3649
3658
|
revisionIdAllocator
|
|
@@ -3652,13 +3661,11 @@ function generateTableOoxml(tableData, options = {}) {
|
|
|
3652
3661
|
cellsXml += `<w:tc>${tcPr}${runsOoxml}</w:tc>`;
|
|
3653
3662
|
}
|
|
3654
3663
|
const trPr = isHeaderRow ? "<w:trPr><w:tblHeader/><w:cantSplit/></w:trPr>" : "<w:trPr><w:cantSplit/></w:trPr>";
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
if (tableInsertMeta) {
|
|
3659
|
-
tableXml = `<w:ins w:id="${tableInsertMeta.id}" w:author="${escapeXml(tableInsertMeta.author)}" w:date="${tableInsertMeta.date}">${tableXml}</w:ins>`;
|
|
3664
|
+
const rowMeta = trackRows ? createRevisionMetadata(author, revisionIdAllocator) : null;
|
|
3665
|
+
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;
|
|
3666
|
+
rowsXml += `<w:tr>${trackedTrPr}${cellsXml}</w:tr>`;
|
|
3660
3667
|
}
|
|
3661
|
-
return
|
|
3668
|
+
return `<w:tbl>${tblPr}${tblGrid}${rowsXml}</w:tbl>`;
|
|
3662
3669
|
}
|
|
3663
3670
|
function diffTablesWithVirtualGrid(oldGrid, newTableData) {
|
|
3664
3671
|
const operations = [];
|
|
@@ -3973,7 +3980,37 @@ async function executeListGeneration(options) {
|
|
|
3973
3980
|
}];
|
|
3974
3981
|
}
|
|
3975
3982
|
}
|
|
3976
|
-
|
|
3983
|
+
const sourceParagraphCount = (originalRunModel || []).filter((run) => run.kind === RunKind.PARAGRAPH_START).length;
|
|
3984
|
+
if (generateRedlines && sourceParagraphCount > 0 && (sourceParagraphCount > 1 || deletionRuns.length === 0)) {
|
|
3985
|
+
let segment = null;
|
|
3986
|
+
const flushSegment = () => {
|
|
3987
|
+
if (!segment) return;
|
|
3988
|
+
results.push(serializeToOoxml(segment, null, [], {
|
|
3989
|
+
author,
|
|
3990
|
+
generateRedlines,
|
|
3991
|
+
revisionIdAllocator
|
|
3992
|
+
}));
|
|
3993
|
+
segment = null;
|
|
3994
|
+
};
|
|
3995
|
+
for (const run of originalRunModel) {
|
|
3996
|
+
if (run.kind === RunKind.PARAGRAPH_START) {
|
|
3997
|
+
flushSegment();
|
|
3998
|
+
segment = [{
|
|
3999
|
+
...run,
|
|
4000
|
+
pPrElement: null,
|
|
4001
|
+
pPrXml: addParagraphMarkRevision(
|
|
4002
|
+
run.pPrElement || run.pPrXml || null,
|
|
4003
|
+
"del",
|
|
4004
|
+
author,
|
|
4005
|
+
revisionIdAllocator
|
|
4006
|
+
)
|
|
4007
|
+
}];
|
|
4008
|
+
} else if (segment && (run.kind === "text" || run.kind === "run")) {
|
|
4009
|
+
segment.push({ ...run, kind: "deletion", author });
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
flushSegment();
|
|
4013
|
+
} else if (generateRedlines && deletionRuns.length > 0) {
|
|
3977
4014
|
const deletedPPr = addParagraphMarkRevision(sourcePPr, "del", author, revisionIdAllocator);
|
|
3978
4015
|
const deletedParagraph = serializeToOoxml(deletionRuns, deletedPPr, [], {
|
|
3979
4016
|
author,
|
|
@@ -8552,6 +8589,11 @@ function rejectTrackedChangesInOoxml(oxml, options = {}) {
|
|
|
8552
8589
|
}
|
|
8553
8590
|
if (removeNode(insNode)) rejectedCount += 1;
|
|
8554
8591
|
}
|
|
8592
|
+
for (const tableNode of getWordElementsByLocalName(xmlDoc, "tbl")) {
|
|
8593
|
+
if (!tableNode.parentNode) continue;
|
|
8594
|
+
const hasRow = Array.from(tableNode.childNodes || []).some((child) => isWordElement5(child, "tr"));
|
|
8595
|
+
if (!hasRow && removeNode(tableNode)) rejectedCount += 1;
|
|
8596
|
+
}
|
|
8555
8597
|
for (const delNode of getWordElementsByLocalName(xmlDoc, "del")) {
|
|
8556
8598
|
if (!delNode.parentNode || !authorMatchesNode(delNode, filter)) continue;
|
|
8557
8599
|
if (isParagraphMarkRevisionMarker(delNode)) {
|
|
@@ -9181,7 +9223,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
9181
9223
|
operationWarnings.push("Input was sanitized; pass sanitizeInput: false to disable.");
|
|
9182
9224
|
}
|
|
9183
9225
|
let structuredAnalysis = null;
|
|
9184
|
-
if (options.structuredContent !== false) {
|
|
9226
|
+
if (options.structuredContent !== false && options.literalTextEdit !== true) {
|
|
9185
9227
|
structuredAnalysis = analyzeStructuredContent(sanitizedText);
|
|
9186
9228
|
if (!structuredAnalysis.valid) {
|
|
9187
9229
|
if (options.explicitStructuredContent === true) {
|
|
@@ -9200,13 +9242,16 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
9200
9242
|
}
|
|
9201
9243
|
}
|
|
9202
9244
|
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
9203
|
-
const hasTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
9204
|
-
const hasFormatHints = formatHints.length > 0;
|
|
9205
9245
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
9246
|
+
const hasLiteralTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
9247
|
+
const hasListStructureChange = !hasLiteralTextChanges && options.explicitStructuredContent === true && isListTargetLoose(cleanModifiedText) && !sourceMatchesListShape(paragraphs, cleanModifiedText);
|
|
9248
|
+
const hasTextChanges = hasLiteralTextChanges || hasListStructureChange;
|
|
9249
|
+
const hasFormatHints = formatHints.length > 0;
|
|
9206
9250
|
const hasExistingFormatting = existingFormatHints.length > 0;
|
|
9207
|
-
const
|
|
9251
|
+
const { textSpans: targetSpans } = buildTextSpansFromParagraphs(paragraphs);
|
|
9252
|
+
const visibleText = targetSpans.map((span) => textSpanVisibleText(span)).join("");
|
|
9208
9253
|
const targetFound = originalText.includes("\n") || originalText.includes("\r") ? originalText.split(/\r?\n/).map(normalizeTargetText).filter(Boolean).every((line) => paragraphs.some((paragraph) => {
|
|
9209
|
-
const paragraphText =
|
|
9254
|
+
const paragraphText = targetSpans.filter((span) => span.paragraph === paragraph).map(textSpanVisibleText).join("");
|
|
9210
9255
|
return normalizeTargetText(paragraphText).includes(line);
|
|
9211
9256
|
})) : visibleText.includes(originalText.trim()) || visibleText.replace(/[\t\n\u2011]/g, "").includes(originalText.trim().replace(/[\t\n\u2011]/g, "")) || normalizeTargetText(visibleText).includes(normalizeTargetText(originalText));
|
|
9212
9257
|
if (hasTextChanges && typeof originalText === "string" && originalText.trim() && !targetFound) {
|
|
@@ -9320,7 +9365,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
9320
9365
|
const tables = getElementsByTagNSOrTag(xmlDoc, NS_W, "tbl");
|
|
9321
9366
|
const hasTables = tables.length > 0;
|
|
9322
9367
|
const isMarkdownTable = /^\|.+\|/.test(cleanModifiedText.trim()) && cleanModifiedText.includes("\n");
|
|
9323
|
-
const isTargetList = isListTargetLoose(cleanModifiedText);
|
|
9368
|
+
const isTargetList = options.literalTextEdit !== true && isListTargetLoose(cleanModifiedText);
|
|
9324
9369
|
const isStructuredContent = options.structuredContent !== false && structuredAnalysis?.requiresStructuredContent === true;
|
|
9325
9370
|
const tableCellContext = initialTableCellContext;
|
|
9326
9371
|
const usesSurgicalTextMode = hasTables || existingRevisionsPolicy === "slice-cross-author";
|
|
@@ -9435,6 +9480,10 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
9435
9480
|
throw caught;
|
|
9436
9481
|
}
|
|
9437
9482
|
}
|
|
9483
|
+
function sourceMatchesListShape(paragraphs, listText) {
|
|
9484
|
+
const itemCount = String(listText).split(/\r?\n/).filter((line) => line.trim()).length;
|
|
9485
|
+
return paragraphs.length === itemCount && paragraphs.every((paragraph) => getElementsByTagNSOrTag(paragraph, NS_W, "numPr").length > 0);
|
|
9486
|
+
}
|
|
9438
9487
|
function normalizeTargetText(text2) {
|
|
9439
9488
|
return String(text2 || "").replace(/[\t\n\u2011]/g, " ").replace(/\s+/g, " ").trim();
|
|
9440
9489
|
}
|
|
@@ -10171,12 +10220,12 @@ function validateParagraphBoundaryMutation(targetParagraph, modifiedText, option
|
|
|
10171
10220
|
}
|
|
10172
10221
|
|
|
10173
10222
|
// core/list-targeting.js
|
|
10174
|
-
function
|
|
10175
|
-
if (!node
|
|
10176
|
-
const
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
return
|
|
10223
|
+
function getDirectChildByLocalName(node, localName2) {
|
|
10224
|
+
if (!node) return null;
|
|
10225
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
10226
|
+
if (child?.nodeType === 1 && child.localName === localName2) return child;
|
|
10227
|
+
}
|
|
10228
|
+
return null;
|
|
10180
10229
|
}
|
|
10181
10230
|
function readValAttribute(element) {
|
|
10182
10231
|
if (!element) return null;
|
|
@@ -10293,15 +10342,15 @@ function buildListEntriesForInsertion(parsedItems, normalizedTargetText, anchorL
|
|
|
10293
10342
|
}
|
|
10294
10343
|
function getParagraphListInfo(paragraph) {
|
|
10295
10344
|
if (!paragraph) return null;
|
|
10296
|
-
const pPr =
|
|
10345
|
+
const pPr = getDirectChildByLocalName(paragraph, "pPr");
|
|
10297
10346
|
if (!pPr) return null;
|
|
10298
|
-
const numPr =
|
|
10347
|
+
const numPr = getDirectChildByLocalName(pPr, "numPr");
|
|
10299
10348
|
if (!numPr) return null;
|
|
10300
|
-
const numIdEl =
|
|
10349
|
+
const numIdEl = getDirectChildByLocalName(numPr, "numId");
|
|
10301
10350
|
if (!numIdEl) return null;
|
|
10302
10351
|
const numId = readValAttribute(numIdEl);
|
|
10303
10352
|
if (!numId || numId === "0") return null;
|
|
10304
|
-
const ilvlEl =
|
|
10353
|
+
const ilvlEl = getDirectChildByLocalName(numPr, "ilvl");
|
|
10305
10354
|
const ilvlRaw = readValAttribute(ilvlEl);
|
|
10306
10355
|
const ilvl = Number.parseInt(ilvlRaw || "0", 10);
|
|
10307
10356
|
return {
|
|
@@ -10711,8 +10760,13 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
10711
10760
|
}
|
|
10712
10761
|
return updated;
|
|
10713
10762
|
}
|
|
10763
|
+
function parseOxmlFragment(oxml) {
|
|
10764
|
+
const text2 = String(oxml || "");
|
|
10765
|
+
const declared = /xmlns:w\s*=/.test(text2);
|
|
10766
|
+
return parseOoxmlSafe(declared ? text2 : `<w:root xmlns:w="${NS_W}">${text2}</w:root>`, "application/xml").doc;
|
|
10767
|
+
}
|
|
10714
10768
|
function getFirstParagraphFromOxml(oxml) {
|
|
10715
|
-
const doc =
|
|
10769
|
+
const doc = parseOxmlFragment(oxml);
|
|
10716
10770
|
if (!doc) return null;
|
|
10717
10771
|
const parseError = getXmlParseError(doc);
|
|
10718
10772
|
if (parseError) return null;
|
|
@@ -10739,17 +10793,17 @@ function setElementVal(element, value) {
|
|
|
10739
10793
|
element.setAttribute("w:val", String(value));
|
|
10740
10794
|
}
|
|
10741
10795
|
function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
10742
|
-
const doc =
|
|
10796
|
+
const doc = parseOxmlFragment(oxml);
|
|
10743
10797
|
if (!doc) return null;
|
|
10744
10798
|
const parseError = getXmlParseError(doc);
|
|
10745
10799
|
if (parseError) return null;
|
|
10746
10800
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
10747
|
-
const
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10801
|
+
for (const paragraph of paragraphs) {
|
|
10802
|
+
const numIdNodes = Array.from(paragraph.getElementsByTagNameNS("*", "numId"));
|
|
10803
|
+
for (const numIdNode of numIdNodes) {
|
|
10804
|
+
const numId = getElementId(numIdNode, ["w:val", "val"]);
|
|
10805
|
+
if (numId != null) return String(numId);
|
|
10806
|
+
}
|
|
10753
10807
|
}
|
|
10754
10808
|
return null;
|
|
10755
10809
|
}
|
|
@@ -10899,400 +10953,106 @@ async function executeSingleLineListStructuralFallback(plan, options = {}) {
|
|
|
10899
10953
|
};
|
|
10900
10954
|
}
|
|
10901
10955
|
|
|
10902
|
-
//
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
}
|
|
10911
|
-
function wordAttribute3(node, name) {
|
|
10912
|
-
return node.getAttribute(`w:${name}`) || node.getAttribute(name) || "";
|
|
10913
|
-
}
|
|
10914
|
-
function xmlSpaceAttribute(node) {
|
|
10915
|
-
return node.getAttribute("xml:space") || node.getAttribute("space") || node.getAttributeNS?.("http://www.w3.org/XML/1998/namespace", "space") || "";
|
|
10916
|
-
}
|
|
10917
|
-
function isParagraphMarkRevision(node) {
|
|
10918
|
-
return localNameOf3(node.parentNode) === "rPr";
|
|
10919
|
-
}
|
|
10920
|
-
function parseOoxmlForValidation(oxml) {
|
|
10921
|
-
const attempt = (xml) => {
|
|
10922
|
-
const doc = parseXml(xml);
|
|
10923
|
-
const parseError = doc.getElementsByTagName("parsererror")[0];
|
|
10924
|
-
if (parseError) {
|
|
10925
|
-
throw new Error(parseError.textContent || "XML parse error");
|
|
10926
|
-
}
|
|
10927
|
-
return doc;
|
|
10928
|
-
};
|
|
10929
|
-
try {
|
|
10930
|
-
return { doc: attempt(oxml) };
|
|
10931
|
-
} catch {
|
|
10932
|
-
try {
|
|
10933
|
-
return { doc: attempt(`<w:root xmlns:w="${NS_W}">${oxml}</w:root>`) };
|
|
10934
|
-
} catch (error2) {
|
|
10935
|
-
return { error: error2?.message || "XML parse error" };
|
|
10936
|
-
}
|
|
10956
|
+
// services/numbering-helpers.js
|
|
10957
|
+
function parseIntegerAttribute(element, names) {
|
|
10958
|
+
if (!element || !Array.isArray(names)) return null;
|
|
10959
|
+
for (const name of names) {
|
|
10960
|
+
const raw = element.getAttribute(name);
|
|
10961
|
+
if (raw == null || raw === "") continue;
|
|
10962
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
10963
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
10937
10964
|
}
|
|
10965
|
+
return null;
|
|
10938
10966
|
}
|
|
10939
|
-
function
|
|
10940
|
-
|
|
10941
|
-
const
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
addIssue("PARSE_ERROR", "error", "Input is not non-empty OOXML or a parsed XML DOM.");
|
|
10945
|
-
return { valid: false, issues };
|
|
10946
|
-
}
|
|
10947
|
-
const { doc, error: error2 } = isDomNode ? { doc: oxml.nodeType === 9 ? oxml : oxml.ownerDocument } : parseOoxmlForValidation(oxml);
|
|
10948
|
-
if (!doc) {
|
|
10949
|
-
addIssue("PARSE_ERROR", "error", `OOXML does not parse as XML: ${error2}`);
|
|
10950
|
-
return { valid: false, issues };
|
|
10951
|
-
}
|
|
10952
|
-
const insElements = elementsByLocalName(doc, "ins");
|
|
10953
|
-
const delElements = elementsByLocalName(doc, "del");
|
|
10954
|
-
const revisions = insElements.concat(delElements);
|
|
10955
|
-
for (const paragraph of elementsByLocalName(doc, "p")) {
|
|
10956
|
-
const nested = Array.from(paragraph.getElementsByTagName("*")).find((el) => el !== paragraph && localNameOf3(el) === "p");
|
|
10957
|
-
if (nested) {
|
|
10958
|
-
addIssue("NESTED_PARAGRAPH", "error", `<${paragraph.nodeName}> contains nested <${nested.nodeName}>.`);
|
|
10959
|
-
}
|
|
10960
|
-
}
|
|
10961
|
-
for (const body of elementsByLocalName(doc, "body")) {
|
|
10962
|
-
const children = Array.from(body.childNodes || []).filter((child) => child.nodeType === 1);
|
|
10963
|
-
const sectPrIndexes = children.map((child, index) => localNameOf3(child) === "sectPr" ? index : -1).filter((index) => index >= 0);
|
|
10964
|
-
if (sectPrIndexes.length > 1) {
|
|
10965
|
-
addIssue("MULTIPLE_BODY_SECTPR", "error", "<w:body> contains multiple direct <w:sectPr> elements.");
|
|
10966
|
-
} else if (sectPrIndexes.length === 1 && sectPrIndexes[0] !== children.length - 1) {
|
|
10967
|
-
addIssue("SECTPR_NOT_LAST", "error", "<w:sectPr> is not the last element child of <w:body>.");
|
|
10968
|
-
}
|
|
10967
|
+
function nextAvailableId(startId, occupiedIds, maxPreferred = null) {
|
|
10968
|
+
let candidate = Number.isInteger(startId) && startId > 0 ? startId : 1;
|
|
10969
|
+
const occupied = occupiedIds instanceof Set ? occupiedIds : /* @__PURE__ */ new Set();
|
|
10970
|
+
while (occupied.has(candidate)) {
|
|
10971
|
+
candidate += 1;
|
|
10969
10972
|
}
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
if (localNameOf3(revision) !== "ins" || localNameOf3(candidate) !== "del") return true;
|
|
10974
|
-
return candidate.parentNode !== revision;
|
|
10975
|
-
});
|
|
10976
|
-
if (invalidNested) {
|
|
10977
|
-
addIssue(
|
|
10978
|
-
"NESTED_REVISION",
|
|
10979
|
-
"error",
|
|
10980
|
-
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") contains invalid nested <${invalidNested.nodeName}>.`
|
|
10981
|
-
);
|
|
10973
|
+
if (Number.isInteger(maxPreferred) && maxPreferred > 0 && candidate > maxPreferred) {
|
|
10974
|
+
for (let probe = 1; probe <= maxPreferred; probe += 1) {
|
|
10975
|
+
if (!occupied.has(probe)) return probe;
|
|
10982
10976
|
}
|
|
10983
10977
|
}
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10978
|
+
return candidate;
|
|
10979
|
+
}
|
|
10980
|
+
function createDynamicNumberingIdState(numberingXml, options = {}) {
|
|
10981
|
+
const minId = Number.isInteger(options?.minId) && options.minId > 0 ? options.minId : 1;
|
|
10982
|
+
const maxPreferred = Number.isInteger(options?.maxPreferred) && options.maxPreferred >= minId ? options.maxPreferred : 32767;
|
|
10983
|
+
const usedNumIds = /* @__PURE__ */ new Set();
|
|
10984
|
+
const usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
10985
|
+
if (String(numberingXml || "").trim()) {
|
|
10986
|
+
try {
|
|
10987
|
+
const numberingDoc = parseOoxml(numberingXml);
|
|
10988
|
+
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
10989
|
+
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
10990
|
+
for (const abstractNum of abstractNums) {
|
|
10991
|
+
const id = parseIntegerAttribute(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
10992
|
+
if (id != null) usedAbstractNumIds.add(id);
|
|
10993
|
+
}
|
|
10994
|
+
for (const num of nums) {
|
|
10995
|
+
const id = parseIntegerAttribute(num, ["w:numId", "numId"]);
|
|
10996
|
+
if (id != null) usedNumIds.add(id);
|
|
10997
|
+
}
|
|
10998
|
+
} catch {
|
|
10992
10999
|
}
|
|
10993
11000
|
}
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11001
|
+
const maxUsedNumId = usedNumIds.size > 0 ? Math.max(...usedNumIds) : 0;
|
|
11002
|
+
const maxUsedAbstractNumId = usedAbstractNumIds.size > 0 ? Math.max(...usedAbstractNumIds) : 0;
|
|
11003
|
+
const baseNumId = Math.max(minId, maxUsedNumId + 1);
|
|
11004
|
+
const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
|
|
11005
|
+
return {
|
|
11006
|
+
// Kept so list edits can recognise which existing definition a source list uses and continue it.
|
|
11007
|
+
sourceNumberingXml: String(numberingXml || ""),
|
|
11008
|
+
nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
|
|
11009
|
+
nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
|
|
11010
|
+
usedNumIds,
|
|
11011
|
+
usedAbstractNumIds,
|
|
11012
|
+
minId,
|
|
11013
|
+
maxPreferred
|
|
11014
|
+
};
|
|
11015
|
+
}
|
|
11016
|
+
function normalizeNumberingIdState(state) {
|
|
11017
|
+
if (!state || typeof state !== "object") return null;
|
|
11018
|
+
if (!(state.usedNumIds instanceof Set)) state.usedNumIds = /* @__PURE__ */ new Set();
|
|
11019
|
+
if (!(state.usedAbstractNumIds instanceof Set)) state.usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
11020
|
+
if (!Number.isInteger(state.minId) || state.minId < 1) {
|
|
11021
|
+
state.minId = 1;
|
|
11006
11022
|
}
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
if (!REVISION_ID_ELEMENTS.has(localNameOf3(node))) continue;
|
|
11010
|
-
const id = wordAttribute3(node, "id");
|
|
11011
|
-
if (!id) continue;
|
|
11012
|
-
if (seenIds.has(id)) {
|
|
11013
|
-
addIssue("DUPLICATE_REVISION_ID", "error", `Revision id ${id} appears more than once.`);
|
|
11014
|
-
}
|
|
11015
|
-
seenIds.add(id);
|
|
11023
|
+
if (!Number.isInteger(state.maxPreferred) || state.maxPreferred < state.minId) {
|
|
11024
|
+
state.maxPreferred = 32767;
|
|
11016
11025
|
}
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
const text2 = node.textContent || "";
|
|
11020
|
-
if (/^\s|\s$/.test(text2) && xmlSpaceAttribute(node) !== "preserve") {
|
|
11021
|
-
addIssue(
|
|
11022
|
-
"MISSING_SPACE_PRESERVE",
|
|
11023
|
-
"error",
|
|
11024
|
-
`<${node.nodeName}> has boundary whitespace without xml:space="preserve".`
|
|
11025
|
-
);
|
|
11026
|
-
}
|
|
11027
|
-
if (text2 === "") {
|
|
11028
|
-
addIssue("EMPTY_TEXT_ELEMENT", "warning", `<${node.nodeName}> is empty.`);
|
|
11029
|
-
}
|
|
11026
|
+
if (!Number.isInteger(state.nextNumId) || state.nextNumId < state.minId) {
|
|
11027
|
+
state.nextNumId = state.minId;
|
|
11030
11028
|
}
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
const hasElementChild = Array.from(revision.childNodes || []).some((child) => child.nodeType === 1);
|
|
11034
|
-
if (!hasElementChild) {
|
|
11035
|
-
addIssue(
|
|
11036
|
-
"EMPTY_REVISION_WRAPPER",
|
|
11037
|
-
"warning",
|
|
11038
|
-
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") wraps no content.`
|
|
11039
|
-
);
|
|
11040
|
-
}
|
|
11029
|
+
if (!Number.isInteger(state.nextAbstractNumId) || state.nextAbstractNumId < state.minId) {
|
|
11030
|
+
state.nextAbstractNumId = state.minId;
|
|
11041
11031
|
}
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11032
|
+
state.nextNumId = nextAvailableId(state.nextNumId, state.usedNumIds, state.maxPreferred);
|
|
11033
|
+
state.nextAbstractNumId = nextAvailableId(state.nextAbstractNumId, state.usedAbstractNumIds, state.maxPreferred);
|
|
11034
|
+
return state;
|
|
11035
|
+
}
|
|
11036
|
+
function reserveNextNumberingId(state, kind = "num") {
|
|
11037
|
+
const normalized = normalizeNumberingIdState(state);
|
|
11038
|
+
if (!normalized) return null;
|
|
11039
|
+
const useAbstract = kind === "abstract";
|
|
11040
|
+
const id = useAbstract ? normalized.nextAbstractNumId : normalized.nextNumId;
|
|
11041
|
+
if (!Number.isInteger(id) || id < 1) return null;
|
|
11042
|
+
if (useAbstract) {
|
|
11043
|
+
normalized.usedAbstractNumIds.add(id);
|
|
11044
|
+
normalized.nextAbstractNumId = nextAvailableId(id + 1, normalized.usedAbstractNumIds, normalized.maxPreferred);
|
|
11045
|
+
} else {
|
|
11046
|
+
normalized.usedNumIds.add(id);
|
|
11047
|
+
normalized.nextNumId = nextAvailableId(id + 1, normalized.usedNumIds, normalized.maxPreferred);
|
|
11049
11048
|
}
|
|
11050
|
-
return
|
|
11049
|
+
return id;
|
|
11051
11050
|
}
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
if (
|
|
11056
|
-
return
|
|
11057
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI === WORD_MAIN_NS && node.localName === localName2
|
|
11058
|
-
);
|
|
11059
|
-
}
|
|
11060
|
-
function escapeMarkdownCell(text2) {
|
|
11061
|
-
return String(text2 || "").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
11062
|
-
}
|
|
11063
|
-
function extractTableMatrix(tableElement) {
|
|
11064
|
-
const rowElements = getDirectWordChildren(tableElement, "tr");
|
|
11065
|
-
const matrix = rowElements.map((row) => {
|
|
11066
|
-
const cellElements = getDirectWordChildren(row, "tc");
|
|
11067
|
-
return cellElements.map((cell) => {
|
|
11068
|
-
const paragraphs = getDirectWordChildren(cell, "p");
|
|
11069
|
-
if (paragraphs.length === 0) return normalizeWhitespaceForTargeting(getParagraphText(cell));
|
|
11070
|
-
const lines = paragraphs.map((p) => normalizeWhitespaceForTargeting(getParagraphText(p))).filter(Boolean);
|
|
11071
|
-
return lines.join("\n");
|
|
11072
|
-
});
|
|
11073
|
-
});
|
|
11074
|
-
const columnCount = matrix.reduce((max2, row) => Math.max(max2, row.length), 0);
|
|
11075
|
-
matrix.forEach((row) => {
|
|
11076
|
-
while (row.length < columnCount) row.push("");
|
|
11077
|
-
});
|
|
11078
|
-
return { matrix, rowElements, columnCount };
|
|
11079
|
-
}
|
|
11080
|
-
function tableMatrixToMarkdown(matrix, columnCount) {
|
|
11081
|
-
if (!Array.isArray(matrix) || matrix.length === 0 || columnCount <= 0) return null;
|
|
11082
|
-
const normalized = matrix.map((row) => {
|
|
11083
|
-
const copy = Array.isArray(row) ? row.slice(0, columnCount) : [];
|
|
11084
|
-
while (copy.length < columnCount) copy.push("");
|
|
11085
|
-
return copy;
|
|
11086
|
-
});
|
|
11087
|
-
const header = normalized[0];
|
|
11088
|
-
const separator = new Array(columnCount).fill("---");
|
|
11089
|
-
const bodyRows = normalized.slice(1);
|
|
11090
|
-
const toLine = (row) => `| ${row.map((cell) => escapeMarkdownCell(cell)).join(" | ")} |`;
|
|
11091
|
-
return [toLine(header), toLine(separator), ...bodyRows.map(toLine)].join("\n");
|
|
11092
|
-
}
|
|
11093
|
-
function isSymmetricLabelRow(rowValues) {
|
|
11094
|
-
if (!Array.isArray(rowValues) || rowValues.length < 2) return false;
|
|
11095
|
-
const normalized = rowValues.map((value) => normalizeWhitespaceForTargeting(value)).filter(Boolean);
|
|
11096
|
-
if (normalized.length < 2) return false;
|
|
11097
|
-
return normalized.every((value) => value === normalized[0]);
|
|
11098
|
-
}
|
|
11099
|
-
function isLikelyStructuredTableSourceParagraph(text2) {
|
|
11100
|
-
const normalized = String(text2 || "").trim();
|
|
11101
|
-
if (!normalized) return false;
|
|
11102
|
-
if (/^and$/i.test(normalized)) return true;
|
|
11103
|
-
if (/^\[.*\]$/.test(normalized)) return true;
|
|
11104
|
-
if (/^\(.*\)$/.test(normalized)) return true;
|
|
11105
|
-
if (/:\s*$/.test(normalized)) return true;
|
|
11106
|
-
if (normalized.length <= 90 && !/[.!?]$/.test(normalized) && /[:\[\]()]/.test(normalized)) return true;
|
|
11107
|
-
if (/^[\[(]/.test(normalized)) return true;
|
|
11108
|
-
return false;
|
|
11109
|
-
}
|
|
11110
|
-
function inferTableReplacementParagraphBlock(startParagraph, options = {}) {
|
|
11111
|
-
const maxScan = Number.isInteger(options?.maxScan) && options.maxScan > 0 ? options.maxScan : 10;
|
|
11112
|
-
const paragraphTextGetter = typeof options?.getParagraphText === "function" ? options.getParagraphText : getParagraphText;
|
|
11113
|
-
if (!startParagraph || !startParagraph.parentNode) return null;
|
|
11114
|
-
const block = [startParagraph];
|
|
11115
|
-
let cursor = startParagraph.nextSibling;
|
|
11116
|
-
let scanned = 0;
|
|
11117
|
-
while (cursor && scanned < maxScan) {
|
|
11118
|
-
scanned += 1;
|
|
11119
|
-
const nextCursor = cursor.nextSibling;
|
|
11120
|
-
if (cursor.nodeType !== 1 || cursor.namespaceURI !== WORD_MAIN_NS || cursor.localName !== "p") {
|
|
11121
|
-
cursor = nextCursor;
|
|
11122
|
-
continue;
|
|
11123
|
-
}
|
|
11124
|
-
const text2 = String(paragraphTextGetter(cursor) || "").trim();
|
|
11125
|
-
if (!text2) {
|
|
11126
|
-
if (block.length > 1) break;
|
|
11127
|
-
cursor = nextCursor;
|
|
11128
|
-
continue;
|
|
11129
|
-
}
|
|
11130
|
-
if (!isLikelyStructuredTableSourceParagraph(text2)) break;
|
|
11131
|
-
block.push(cursor);
|
|
11132
|
-
cursor = nextCursor;
|
|
11133
|
-
}
|
|
11134
|
-
return block.length > 1 ? block : null;
|
|
11135
|
-
}
|
|
11136
|
-
function synthesizeTableMarkdownFromMultilineCellEdit(targetParagraph, modifiedText, options = {}) {
|
|
11137
|
-
const onInfo = typeof options.onInfo === "function" ? options.onInfo : () => {
|
|
11138
|
-
};
|
|
11139
|
-
const onWarn = typeof options.onWarn === "function" ? options.onWarn : () => {
|
|
11140
|
-
};
|
|
11141
|
-
const rawModified = String(modifiedText || "");
|
|
11142
|
-
if (!rawModified.includes("\n")) return null;
|
|
11143
|
-
if (isMarkdownTableText(rawModified)) return null;
|
|
11144
|
-
const lines = rawModified.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean);
|
|
11145
|
-
if (lines.length < 2) return null;
|
|
11146
|
-
const tableElement = options.tableElement || findContainingWordElement(targetParagraph, "tbl");
|
|
11147
|
-
const rowElement = findContainingWordElement(targetParagraph, "tr");
|
|
11148
|
-
const cellElement = findContainingWordElement(targetParagraph, "tc");
|
|
11149
|
-
if (!tableElement || !rowElement || !cellElement) return null;
|
|
11150
|
-
const currentParagraphText = normalizeWhitespaceForTargeting(
|
|
11151
|
-
options.currentParagraphText || getParagraphText(targetParagraph)
|
|
11152
|
-
);
|
|
11153
|
-
const firstLine = normalizeWhitespaceForTargeting(lines[0]);
|
|
11154
|
-
if (currentParagraphText && firstLine && firstLine !== currentParagraphText) {
|
|
11155
|
-
onWarn("[Table] Multiline cell text did not anchor to original cell text; skipping table-row synthesis heuristic.");
|
|
11156
|
-
return null;
|
|
11157
|
-
}
|
|
11158
|
-
const { matrix, rowElements, columnCount } = extractTableMatrix(tableElement);
|
|
11159
|
-
if (matrix.length === 0 || columnCount === 0) return null;
|
|
11160
|
-
const rowIndex = rowElements.indexOf(rowElement);
|
|
11161
|
-
const cellElements = getDirectWordChildren(rowElement, "tc");
|
|
11162
|
-
const colIndex = cellElements.indexOf(cellElement);
|
|
11163
|
-
if (rowIndex < 0 || colIndex < 0 || colIndex >= columnCount) return null;
|
|
11164
|
-
matrix[rowIndex][colIndex] = lines[0];
|
|
11165
|
-
const mirrorAcrossColumns = isSymmetricLabelRow(matrix[rowIndex]);
|
|
11166
|
-
if (mirrorAcrossColumns) {
|
|
11167
|
-
onInfo("[Table] Symmetric row detected; mirroring inserted row values across columns.");
|
|
11168
|
-
}
|
|
11169
|
-
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
11170
|
-
const insertIndex = rowIndex + i2;
|
|
11171
|
-
const extraValue = lines[i2];
|
|
11172
|
-
if (insertIndex < matrix.length && !normalizeWhitespaceForTargeting(matrix[insertIndex][colIndex])) {
|
|
11173
|
-
if (mirrorAcrossColumns) {
|
|
11174
|
-
for (let col = 0; col < columnCount; col++) {
|
|
11175
|
-
if (!normalizeWhitespaceForTargeting(matrix[insertIndex][col])) {
|
|
11176
|
-
matrix[insertIndex][col] = extraValue;
|
|
11177
|
-
}
|
|
11178
|
-
}
|
|
11179
|
-
} else {
|
|
11180
|
-
matrix[insertIndex][colIndex] = extraValue;
|
|
11181
|
-
}
|
|
11182
|
-
} else {
|
|
11183
|
-
const newRow = new Array(columnCount).fill("");
|
|
11184
|
-
if (mirrorAcrossColumns) {
|
|
11185
|
-
for (let col = 0; col < columnCount; col++) newRow[col] = extraValue;
|
|
11186
|
-
} else {
|
|
11187
|
-
newRow[colIndex] = extraValue;
|
|
11188
|
-
}
|
|
11189
|
-
matrix.splice(Math.min(insertIndex, matrix.length), 0, newRow);
|
|
11190
|
-
}
|
|
11191
|
-
}
|
|
11192
|
-
const markdown = tableMatrixToMarkdown(matrix, columnCount);
|
|
11193
|
-
if (!markdown) return null;
|
|
11194
|
-
onInfo("[Table] Synthesized full markdown table from multiline cell edit for table-scope reconciliation.");
|
|
11195
|
-
return markdown;
|
|
11196
|
-
}
|
|
11197
|
-
|
|
11198
|
-
// services/numbering-helpers.js
|
|
11199
|
-
function parseIntegerAttribute(element, names) {
|
|
11200
|
-
if (!element || !Array.isArray(names)) return null;
|
|
11201
|
-
for (const name of names) {
|
|
11202
|
-
const raw = element.getAttribute(name);
|
|
11203
|
-
if (raw == null || raw === "") continue;
|
|
11204
|
-
const parsed = Number.parseInt(String(raw), 10);
|
|
11205
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
11206
|
-
}
|
|
11207
|
-
return null;
|
|
11208
|
-
}
|
|
11209
|
-
function nextAvailableId(startId, occupiedIds, maxPreferred = null) {
|
|
11210
|
-
let candidate = Number.isInteger(startId) && startId > 0 ? startId : 1;
|
|
11211
|
-
const occupied = occupiedIds instanceof Set ? occupiedIds : /* @__PURE__ */ new Set();
|
|
11212
|
-
while (occupied.has(candidate)) {
|
|
11213
|
-
candidate += 1;
|
|
11214
|
-
}
|
|
11215
|
-
if (Number.isInteger(maxPreferred) && maxPreferred > 0 && candidate > maxPreferred) {
|
|
11216
|
-
for (let probe = 1; probe <= maxPreferred; probe += 1) {
|
|
11217
|
-
if (!occupied.has(probe)) return probe;
|
|
11218
|
-
}
|
|
11219
|
-
}
|
|
11220
|
-
return candidate;
|
|
11221
|
-
}
|
|
11222
|
-
function createDynamicNumberingIdState(numberingXml, options = {}) {
|
|
11223
|
-
const minId = Number.isInteger(options?.minId) && options.minId > 0 ? options.minId : 1;
|
|
11224
|
-
const maxPreferred = Number.isInteger(options?.maxPreferred) && options.maxPreferred >= minId ? options.maxPreferred : 32767;
|
|
11225
|
-
const usedNumIds = /* @__PURE__ */ new Set();
|
|
11226
|
-
const usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
11227
|
-
if (String(numberingXml || "").trim()) {
|
|
11228
|
-
try {
|
|
11229
|
-
const numberingDoc = parseOoxml(numberingXml);
|
|
11230
|
-
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
11231
|
-
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
11232
|
-
for (const abstractNum of abstractNums) {
|
|
11233
|
-
const id = parseIntegerAttribute(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
11234
|
-
if (id != null) usedAbstractNumIds.add(id);
|
|
11235
|
-
}
|
|
11236
|
-
for (const num of nums) {
|
|
11237
|
-
const id = parseIntegerAttribute(num, ["w:numId", "numId"]);
|
|
11238
|
-
if (id != null) usedNumIds.add(id);
|
|
11239
|
-
}
|
|
11240
|
-
} catch {
|
|
11241
|
-
}
|
|
11242
|
-
}
|
|
11243
|
-
const maxUsedNumId = usedNumIds.size > 0 ? Math.max(...usedNumIds) : 0;
|
|
11244
|
-
const maxUsedAbstractNumId = usedAbstractNumIds.size > 0 ? Math.max(...usedAbstractNumIds) : 0;
|
|
11245
|
-
const baseNumId = Math.max(minId, maxUsedNumId + 1);
|
|
11246
|
-
const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
|
|
11247
|
-
return {
|
|
11248
|
-
nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
|
|
11249
|
-
nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
|
|
11250
|
-
usedNumIds,
|
|
11251
|
-
usedAbstractNumIds,
|
|
11252
|
-
minId,
|
|
11253
|
-
maxPreferred
|
|
11254
|
-
};
|
|
11255
|
-
}
|
|
11256
|
-
function normalizeNumberingIdState(state) {
|
|
11257
|
-
if (!state || typeof state !== "object") return null;
|
|
11258
|
-
if (!(state.usedNumIds instanceof Set)) state.usedNumIds = /* @__PURE__ */ new Set();
|
|
11259
|
-
if (!(state.usedAbstractNumIds instanceof Set)) state.usedAbstractNumIds = /* @__PURE__ */ new Set();
|
|
11260
|
-
if (!Number.isInteger(state.minId) || state.minId < 1) {
|
|
11261
|
-
state.minId = 1;
|
|
11262
|
-
}
|
|
11263
|
-
if (!Number.isInteger(state.maxPreferred) || state.maxPreferred < state.minId) {
|
|
11264
|
-
state.maxPreferred = 32767;
|
|
11265
|
-
}
|
|
11266
|
-
if (!Number.isInteger(state.nextNumId) || state.nextNumId < state.minId) {
|
|
11267
|
-
state.nextNumId = state.minId;
|
|
11268
|
-
}
|
|
11269
|
-
if (!Number.isInteger(state.nextAbstractNumId) || state.nextAbstractNumId < state.minId) {
|
|
11270
|
-
state.nextAbstractNumId = state.minId;
|
|
11271
|
-
}
|
|
11272
|
-
state.nextNumId = nextAvailableId(state.nextNumId, state.usedNumIds, state.maxPreferred);
|
|
11273
|
-
state.nextAbstractNumId = nextAvailableId(state.nextAbstractNumId, state.usedAbstractNumIds, state.maxPreferred);
|
|
11274
|
-
return state;
|
|
11275
|
-
}
|
|
11276
|
-
function reserveNextNumberingId(state, kind = "num") {
|
|
11277
|
-
const normalized = normalizeNumberingIdState(state);
|
|
11278
|
-
if (!normalized) return null;
|
|
11279
|
-
const useAbstract = kind === "abstract";
|
|
11280
|
-
const id = useAbstract ? normalized.nextAbstractNumId : normalized.nextNumId;
|
|
11281
|
-
if (!Number.isInteger(id) || id < 1) return null;
|
|
11282
|
-
if (useAbstract) {
|
|
11283
|
-
normalized.usedAbstractNumIds.add(id);
|
|
11284
|
-
normalized.nextAbstractNumId = nextAvailableId(id + 1, normalized.usedAbstractNumIds, normalized.maxPreferred);
|
|
11285
|
-
} else {
|
|
11286
|
-
normalized.usedNumIds.add(id);
|
|
11287
|
-
normalized.nextNumId = nextAvailableId(id + 1, normalized.usedNumIds, normalized.maxPreferred);
|
|
11288
|
-
}
|
|
11289
|
-
return id;
|
|
11290
|
-
}
|
|
11291
|
-
function reserveNextNumberingIdPair(state) {
|
|
11292
|
-
const numId = reserveNextNumberingId(state, "num");
|
|
11293
|
-
const abstractNumId = reserveNextNumberingId(state, "abstract");
|
|
11294
|
-
if (numId == null || abstractNumId == null) return null;
|
|
11295
|
-
return { numId, abstractNumId };
|
|
11051
|
+
function reserveNextNumberingIdPair(state) {
|
|
11052
|
+
const numId = reserveNextNumberingId(state, "num");
|
|
11053
|
+
const abstractNumId = reserveNextNumberingId(state, "abstract");
|
|
11054
|
+
if (numId == null || abstractNumId == null) return null;
|
|
11055
|
+
return { numId, abstractNumId };
|
|
11296
11056
|
}
|
|
11297
11057
|
function hasXmlParseError(doc) {
|
|
11298
11058
|
if (!doc || !doc.documentElement) return true;
|
|
@@ -11345,134 +11105,507 @@ function overwriteParagraphNumIds(paragraphNodes, targetNumId) {
|
|
|
11345
11105
|
}
|
|
11346
11106
|
}
|
|
11347
11107
|
}
|
|
11348
|
-
function extractFirstParagraphNumId(paragraphNodes) {
|
|
11349
|
-
for (const node of paragraphNodes || []) {
|
|
11350
|
-
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
11351
|
-
for (const numIdNode of numIdNodes) {
|
|
11352
|
-
const numId = getElementId2(numIdNode, ["w:val", "val"]);
|
|
11353
|
-
if (numId != null) return String(numId);
|
|
11108
|
+
function extractFirstParagraphNumId(paragraphNodes) {
|
|
11109
|
+
for (const node of paragraphNodes || []) {
|
|
11110
|
+
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
11111
|
+
for (const numIdNode of numIdNodes) {
|
|
11112
|
+
const numId = getElementId2(numIdNode, ["w:val", "val"]);
|
|
11113
|
+
if (numId != null) return String(numId);
|
|
11114
|
+
}
|
|
11115
|
+
}
|
|
11116
|
+
return null;
|
|
11117
|
+
}
|
|
11118
|
+
function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startAt) {
|
|
11119
|
+
const safeNumId = String(numId);
|
|
11120
|
+
const safeAbstractNumId = String(abstractNumId);
|
|
11121
|
+
const safeStartAt = Number.isInteger(startAt) && startAt > 0 ? startAt : 1;
|
|
11122
|
+
const levelsXml = Array.from({ length: 9 }, (_, level) => {
|
|
11123
|
+
const lvlText = Array.from({ length: level + 1 }, (_2, i2) => `%${i2 + 1}`).join(".") + ".";
|
|
11124
|
+
const left = 720 * (level + 1);
|
|
11125
|
+
return `
|
|
11126
|
+
<w:lvl w:ilvl="${level}">
|
|
11127
|
+
<w:start w:val="1"/>
|
|
11128
|
+
<w:numFmt w:val="decimal"/>
|
|
11129
|
+
<w:lvlText w:val="${lvlText}"/>
|
|
11130
|
+
<w:lvlJc w:val="left"/>
|
|
11131
|
+
<w:pPr><w:ind w:left="${left}" w:hanging="360"/></w:pPr>
|
|
11132
|
+
</w:lvl>`;
|
|
11133
|
+
}).join("");
|
|
11134
|
+
return `
|
|
11135
|
+
<w:numbering xmlns:w="${WORD_MAIN_NS}">
|
|
11136
|
+
<w:abstractNum w:abstractNumId="${safeAbstractNumId}">
|
|
11137
|
+
<w:multiLevelType w:val="multilevel"/>
|
|
11138
|
+
${levelsXml}
|
|
11139
|
+
</w:abstractNum>
|
|
11140
|
+
<w:num w:numId="${safeNumId}">
|
|
11141
|
+
<w:abstractNumId w:val="${safeAbstractNumId}"/>
|
|
11142
|
+
<w:lvlOverride w:ilvl="0">
|
|
11143
|
+
<w:startOverride w:val="${safeStartAt}"/>
|
|
11144
|
+
</w:lvlOverride>
|
|
11145
|
+
</w:num>
|
|
11146
|
+
</w:numbering>`.trim();
|
|
11147
|
+
}
|
|
11148
|
+
function isTrackedDeletedParagraph(node) {
|
|
11149
|
+
if (!node || node.nodeType !== 1 || node.localName !== "p") return false;
|
|
11150
|
+
const pPr = Array.from(node.childNodes || []).find((child) => isDirectWordChild(child, "pPr"));
|
|
11151
|
+
const rPr = pPr && Array.from(pPr.childNodes || []).find((child) => isDirectWordChild(child, "rPr"));
|
|
11152
|
+
return !!rPr && Array.from(rPr.childNodes || []).some((child) => isDirectWordChild(child, "del"));
|
|
11153
|
+
}
|
|
11154
|
+
function paragraphNumIdNodes(node) {
|
|
11155
|
+
return Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []).filter((numIdNode) => numIdNode.parentNode?.localName === "numPr");
|
|
11156
|
+
}
|
|
11157
|
+
function levelFormats(abstractNum) {
|
|
11158
|
+
const formats = /* @__PURE__ */ new Map();
|
|
11159
|
+
for (const lvl of Array.from(abstractNum?.getElementsByTagNameNS?.("*", "lvl") || [])) {
|
|
11160
|
+
const ilvl = getElementId2(lvl, ["w:ilvl", "ilvl"]);
|
|
11161
|
+
const fmt = Array.from(lvl.getElementsByTagNameNS("*", "numFmt"))[0]?.getAttribute("w:val") || null;
|
|
11162
|
+
if (ilvl != null && fmt) formats.set(ilvl, fmt);
|
|
11163
|
+
}
|
|
11164
|
+
return formats;
|
|
11165
|
+
}
|
|
11166
|
+
function normalizeListFormat(format) {
|
|
11167
|
+
return format === "bullet" || format === "circle" || format === "square" ? "bullet" : format;
|
|
11168
|
+
}
|
|
11169
|
+
function abstractNumForNumId(numberingDoc, numId) {
|
|
11170
|
+
const num = Array.from(numberingDoc.getElementsByTagNameNS("*", "num")).find((candidate) => getElementId2(candidate, ["w:numId", "numId"]) === numId);
|
|
11171
|
+
const abstractId = num && getElementId2(Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0], ["w:val", "val"]);
|
|
11172
|
+
if (abstractId == null) return null;
|
|
11173
|
+
return Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum")).find((candidate) => getElementId2(candidate, ["w:abstractNumId", "abstractNumId"]) === abstractId) || null;
|
|
11174
|
+
}
|
|
11175
|
+
function reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, numberingIdState) {
|
|
11176
|
+
const sourceXml = numberingIdState?.sourceNumberingXml;
|
|
11177
|
+
if (!numberingXml || !String(sourceXml || "").trim() || !Array.isArray(replacementNodes)) return null;
|
|
11178
|
+
const sourceNumIds = /* @__PURE__ */ new Set();
|
|
11179
|
+
for (const paragraph of sourceParagraphs || []) {
|
|
11180
|
+
for (const numIdNode of paragraphNumIdNodes(paragraph)) {
|
|
11181
|
+
const id = getElementId2(numIdNode, ["w:val", "val"]);
|
|
11182
|
+
if (id != null && id !== 0) sourceNumIds.add(id);
|
|
11183
|
+
}
|
|
11184
|
+
}
|
|
11185
|
+
if (sourceNumIds.size !== 1) return null;
|
|
11186
|
+
const [sourceNumId] = sourceNumIds;
|
|
11187
|
+
const sourceDoc = parseOoxml(sourceXml);
|
|
11188
|
+
const payloadDoc = parseOoxml(numberingXml);
|
|
11189
|
+
if (hasXmlParseError(sourceDoc) || hasXmlParseError(payloadDoc)) return null;
|
|
11190
|
+
const sourceAbstract = abstractNumForNumId(sourceDoc, sourceNumId);
|
|
11191
|
+
if (!sourceAbstract) return null;
|
|
11192
|
+
const sourceFormats = levelFormats(sourceAbstract);
|
|
11193
|
+
const newParagraphNumIds = [];
|
|
11194
|
+
for (const node of replacementNodes) {
|
|
11195
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
11196
|
+
for (const numIdNode of paragraphNumIdNodes(node)) {
|
|
11197
|
+
const ilvlNode = Array.from(numIdNode.parentNode.childNodes || []).find((child) => child.localName === "ilvl");
|
|
11198
|
+
newParagraphNumIds.push({
|
|
11199
|
+
numId: getElementId2(numIdNode, ["w:val", "val"]),
|
|
11200
|
+
ilvl: ilvlNode ? getElementId2(ilvlNode, ["w:val", "val"]) ?? 0 : 0
|
|
11201
|
+
});
|
|
11202
|
+
}
|
|
11203
|
+
}
|
|
11204
|
+
if (newParagraphNumIds.length === 0) return null;
|
|
11205
|
+
const payloadFormats = /* @__PURE__ */ new Map();
|
|
11206
|
+
for (const { numId, ilvl } of newParagraphNumIds) {
|
|
11207
|
+
if (numId == null) return null;
|
|
11208
|
+
if (!payloadFormats.has(numId)) {
|
|
11209
|
+
const abstract = abstractNumForNumId(payloadDoc, numId);
|
|
11210
|
+
if (!abstract) return null;
|
|
11211
|
+
payloadFormats.set(numId, levelFormats(abstract));
|
|
11212
|
+
}
|
|
11213
|
+
const generated = payloadFormats.get(numId).get(ilvl);
|
|
11214
|
+
const existing = sourceFormats.get(ilvl);
|
|
11215
|
+
if (!generated || !existing || normalizeListFormat(generated) !== normalizeListFormat(existing)) return null;
|
|
11216
|
+
}
|
|
11217
|
+
const clonedNodes = replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node);
|
|
11218
|
+
for (const node of clonedNodes) {
|
|
11219
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
11220
|
+
for (const numIdNode of paragraphNumIdNodes(node)) setElementVal2(numIdNode, sourceNumId);
|
|
11221
|
+
}
|
|
11222
|
+
return { numId: sourceNumId, replacementNodes: clonedNodes };
|
|
11223
|
+
}
|
|
11224
|
+
function remapNumberingPayloadForDocument(numberingXml, replacementNodes, numberingIdState) {
|
|
11225
|
+
const numberingDoc = parseOoxml(numberingXml);
|
|
11226
|
+
if (hasXmlParseError(numberingDoc)) {
|
|
11227
|
+
return {
|
|
11228
|
+
numberingXml: String(numberingXml || ""),
|
|
11229
|
+
replacementNodes: Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : []
|
|
11230
|
+
};
|
|
11231
|
+
}
|
|
11232
|
+
const abstractNumMap = /* @__PURE__ */ new Map();
|
|
11233
|
+
const numIdMap = /* @__PURE__ */ new Map();
|
|
11234
|
+
const abstractNums = Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum"));
|
|
11235
|
+
for (const abstractNum of abstractNums) {
|
|
11236
|
+
const oldId = getElementId2(abstractNum, ["w:abstractNumId", "abstractNumId"]);
|
|
11237
|
+
if (oldId == null) continue;
|
|
11238
|
+
const newId = reserveNextNumberingId(numberingIdState, "abstract");
|
|
11239
|
+
if (newId == null) continue;
|
|
11240
|
+
abstractNumMap.set(oldId, newId);
|
|
11241
|
+
setElementId(abstractNum, "w:abstractNumId", newId);
|
|
11242
|
+
}
|
|
11243
|
+
const nums = Array.from(numberingDoc.getElementsByTagNameNS("*", "num"));
|
|
11244
|
+
for (const num of nums) {
|
|
11245
|
+
const oldNumId = getElementId2(num, ["w:numId", "numId"]);
|
|
11246
|
+
if (oldNumId == null) continue;
|
|
11247
|
+
const newNumId = reserveNextNumberingId(numberingIdState, "num");
|
|
11248
|
+
if (newNumId == null) continue;
|
|
11249
|
+
numIdMap.set(oldNumId, newNumId);
|
|
11250
|
+
setElementId(num, "w:numId", newNumId);
|
|
11251
|
+
const abstractNumIdNode = Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0] || null;
|
|
11252
|
+
if (abstractNumIdNode) {
|
|
11253
|
+
const oldAbsRef = getElementId2(abstractNumIdNode, ["w:val", "val"]);
|
|
11254
|
+
if (oldAbsRef != null && abstractNumMap.has(oldAbsRef)) {
|
|
11255
|
+
setElementVal2(abstractNumIdNode, abstractNumMap.get(oldAbsRef));
|
|
11256
|
+
}
|
|
11257
|
+
}
|
|
11258
|
+
}
|
|
11259
|
+
const clonedNodes = Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : [];
|
|
11260
|
+
for (const node of clonedNodes) {
|
|
11261
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
11262
|
+
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
11263
|
+
for (const numIdNode of numIdNodes) {
|
|
11264
|
+
const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
|
|
11265
|
+
if (oldNumRef != null && numIdMap.has(oldNumRef)) {
|
|
11266
|
+
setElementVal2(numIdNode, numIdMap.get(oldNumRef));
|
|
11267
|
+
}
|
|
11268
|
+
}
|
|
11269
|
+
}
|
|
11270
|
+
return {
|
|
11271
|
+
numberingXml: serializeOoxml(numberingDoc),
|
|
11272
|
+
replacementNodes: clonedNodes
|
|
11273
|
+
};
|
|
11274
|
+
}
|
|
11275
|
+
function mergeNumberingXmlBySchemaOrder(existingNumberingXml, incomingNumberingXml) {
|
|
11276
|
+
const existingText = String(existingNumberingXml || "");
|
|
11277
|
+
const incomingText = String(incomingNumberingXml || "");
|
|
11278
|
+
if (!incomingText.trim()) return existingText;
|
|
11279
|
+
if (!existingText.trim()) return incomingText;
|
|
11280
|
+
try {
|
|
11281
|
+
const existingDoc = parseOoxml(existingText);
|
|
11282
|
+
const incomingDoc = parseOoxml(incomingText);
|
|
11283
|
+
if (hasXmlParseError(existingDoc) || hasXmlParseError(incomingDoc)) {
|
|
11284
|
+
return existingText;
|
|
11285
|
+
}
|
|
11286
|
+
const existingRoot = existingDoc.documentElement;
|
|
11287
|
+
const incomingRoot = incomingDoc.documentElement;
|
|
11288
|
+
if (!existingRoot || !incomingRoot) return existingText;
|
|
11289
|
+
const existingAbstractIds = new Set(
|
|
11290
|
+
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "abstractNum")).map((node) => parseIntegerAttribute(node, ["w:abstractNumId", "abstractNumId"])).filter((id) => id != null)
|
|
11291
|
+
);
|
|
11292
|
+
const existingNumIds = new Set(
|
|
11293
|
+
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "num")).map((node) => parseIntegerAttribute(node, ["w:numId", "numId"])).filter((id) => id != null)
|
|
11294
|
+
);
|
|
11295
|
+
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
11296
|
+
if (!isDirectWordChild(incomingNode, "abstractNum")) continue;
|
|
11297
|
+
const incomingId = parseIntegerAttribute(incomingNode, ["w:abstractNumId", "abstractNumId"]);
|
|
11298
|
+
if (incomingId == null || existingAbstractIds.has(incomingId)) continue;
|
|
11299
|
+
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "abstract");
|
|
11300
|
+
existingAbstractIds.add(incomingId);
|
|
11301
|
+
}
|
|
11302
|
+
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
11303
|
+
if (!isDirectWordChild(incomingNode, "num")) continue;
|
|
11304
|
+
const incomingId = parseIntegerAttribute(incomingNode, ["w:numId", "numId"]);
|
|
11305
|
+
if (incomingId == null || existingNumIds.has(incomingId)) continue;
|
|
11306
|
+
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "num");
|
|
11307
|
+
existingNumIds.add(incomingId);
|
|
11308
|
+
}
|
|
11309
|
+
return serializeOoxml(existingDoc);
|
|
11310
|
+
} catch {
|
|
11311
|
+
return existingText;
|
|
11312
|
+
}
|
|
11313
|
+
}
|
|
11314
|
+
|
|
11315
|
+
// core/redline-validation.js
|
|
11316
|
+
var REVISION_ID_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "rPrChange", "pPrChange"]);
|
|
11317
|
+
var REVISION_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T/;
|
|
11318
|
+
function localNameOf3(node) {
|
|
11319
|
+
return String(node?.localName || node?.nodeName || "").replace(/^.*:/, "");
|
|
11320
|
+
}
|
|
11321
|
+
function elementsByLocalName(root, name) {
|
|
11322
|
+
return Array.from(root.getElementsByTagName("*")).filter((el) => localNameOf3(el) === name);
|
|
11323
|
+
}
|
|
11324
|
+
function wordAttribute3(node, name) {
|
|
11325
|
+
return node.getAttribute(`w:${name}`) || node.getAttribute(name) || "";
|
|
11326
|
+
}
|
|
11327
|
+
function xmlSpaceAttribute(node) {
|
|
11328
|
+
return node.getAttribute("xml:space") || node.getAttribute("space") || node.getAttributeNS?.("http://www.w3.org/XML/1998/namespace", "space") || "";
|
|
11329
|
+
}
|
|
11330
|
+
function isParagraphMarkRevision(node) {
|
|
11331
|
+
return localNameOf3(node.parentNode) === "rPr";
|
|
11332
|
+
}
|
|
11333
|
+
function parseOoxmlForValidation(oxml) {
|
|
11334
|
+
const attempt = (xml) => {
|
|
11335
|
+
const doc = parseXml(xml);
|
|
11336
|
+
const parseError = doc.getElementsByTagName("parsererror")[0];
|
|
11337
|
+
if (parseError) {
|
|
11338
|
+
throw new Error(parseError.textContent || "XML parse error");
|
|
11339
|
+
}
|
|
11340
|
+
return doc;
|
|
11341
|
+
};
|
|
11342
|
+
try {
|
|
11343
|
+
return { doc: attempt(oxml) };
|
|
11344
|
+
} catch {
|
|
11345
|
+
try {
|
|
11346
|
+
return { doc: attempt(`<w:root xmlns:w="${NS_W}">${oxml}</w:root>`) };
|
|
11347
|
+
} catch (error2) {
|
|
11348
|
+
return { error: error2?.message || "XML parse error" };
|
|
11349
|
+
}
|
|
11350
|
+
}
|
|
11351
|
+
}
|
|
11352
|
+
function validateRedlineOoxml(oxml) {
|
|
11353
|
+
const issues = [];
|
|
11354
|
+
const addIssue = (code, severity, message) => issues.push({ code, severity, message });
|
|
11355
|
+
const isDomNode = oxml && typeof oxml === "object" && (oxml.nodeType === 9 || oxml.nodeType === 1);
|
|
11356
|
+
if (!isDomNode && (typeof oxml !== "string" || oxml.trim() === "")) {
|
|
11357
|
+
addIssue("PARSE_ERROR", "error", "Input is not non-empty OOXML or a parsed XML DOM.");
|
|
11358
|
+
return { valid: false, issues };
|
|
11359
|
+
}
|
|
11360
|
+
const { doc, error: error2 } = isDomNode ? { doc: oxml.nodeType === 9 ? oxml : oxml.ownerDocument } : parseOoxmlForValidation(oxml);
|
|
11361
|
+
if (!doc) {
|
|
11362
|
+
addIssue("PARSE_ERROR", "error", `OOXML does not parse as XML: ${error2}`);
|
|
11363
|
+
return { valid: false, issues };
|
|
11364
|
+
}
|
|
11365
|
+
const insElements = elementsByLocalName(doc, "ins");
|
|
11366
|
+
const delElements = elementsByLocalName(doc, "del");
|
|
11367
|
+
const revisions = insElements.concat(delElements);
|
|
11368
|
+
for (const paragraph of elementsByLocalName(doc, "p")) {
|
|
11369
|
+
const nested = Array.from(paragraph.getElementsByTagName("*")).find((el) => el !== paragraph && localNameOf3(el) === "p");
|
|
11370
|
+
if (nested) {
|
|
11371
|
+
addIssue("NESTED_PARAGRAPH", "error", `<${paragraph.nodeName}> contains nested <${nested.nodeName}>.`);
|
|
11372
|
+
}
|
|
11373
|
+
}
|
|
11374
|
+
for (const body of elementsByLocalName(doc, "body")) {
|
|
11375
|
+
const children = Array.from(body.childNodes || []).filter((child) => child.nodeType === 1);
|
|
11376
|
+
const sectPrIndexes = children.map((child, index) => localNameOf3(child) === "sectPr" ? index : -1).filter((index) => index >= 0);
|
|
11377
|
+
if (sectPrIndexes.length > 1) {
|
|
11378
|
+
addIssue("MULTIPLE_BODY_SECTPR", "error", "<w:body> contains multiple direct <w:sectPr> elements.");
|
|
11379
|
+
} else if (sectPrIndexes.length === 1 && sectPrIndexes[0] !== children.length - 1) {
|
|
11380
|
+
addIssue("SECTPR_NOT_LAST", "error", "<w:sectPr> is not the last element child of <w:body>.");
|
|
11381
|
+
}
|
|
11382
|
+
}
|
|
11383
|
+
for (const revision of revisions) {
|
|
11384
|
+
const nested = Array.from(revision.getElementsByTagName("*")).filter((el) => el !== revision && ["ins", "del"].includes(localNameOf3(el)));
|
|
11385
|
+
const invalidNested = nested.find((candidate) => {
|
|
11386
|
+
if (localNameOf3(revision) !== "ins" || localNameOf3(candidate) !== "del") return true;
|
|
11387
|
+
return candidate.parentNode !== revision;
|
|
11388
|
+
});
|
|
11389
|
+
if (invalidNested) {
|
|
11390
|
+
addIssue(
|
|
11391
|
+
"NESTED_REVISION",
|
|
11392
|
+
"error",
|
|
11393
|
+
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") contains invalid nested <${invalidNested.nodeName}>.`
|
|
11394
|
+
);
|
|
11395
|
+
}
|
|
11396
|
+
}
|
|
11397
|
+
for (const del of delElements) {
|
|
11398
|
+
const plainTextNodes = elementsByLocalName(del, "t");
|
|
11399
|
+
if (plainTextNodes.length > 0) {
|
|
11400
|
+
addIssue(
|
|
11401
|
+
"DEL_CONTAINS_T",
|
|
11402
|
+
"error",
|
|
11403
|
+
`<w:del> (w:id="${wordAttribute3(del, "id")}") contains <w:t>; deleted text must use <w:delText>.`
|
|
11404
|
+
);
|
|
11405
|
+
}
|
|
11406
|
+
}
|
|
11407
|
+
for (const revision of revisions) {
|
|
11408
|
+
const missing = [];
|
|
11409
|
+
if (!wordAttribute3(revision, "id")) missing.push("w:id");
|
|
11410
|
+
if (!wordAttribute3(revision, "author")) missing.push("w:author");
|
|
11411
|
+
if (!REVISION_DATE_PATTERN.test(wordAttribute3(revision, "date"))) missing.push("w:date");
|
|
11412
|
+
if (missing.length > 0) {
|
|
11413
|
+
addIssue(
|
|
11414
|
+
"MISSING_REVISION_METADATA",
|
|
11415
|
+
"error",
|
|
11416
|
+
`<${revision.nodeName}> is missing or has malformed ${missing.join(", ")}.`
|
|
11417
|
+
);
|
|
11418
|
+
}
|
|
11419
|
+
}
|
|
11420
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
11421
|
+
for (const node of Array.from(doc.getElementsByTagName("*"))) {
|
|
11422
|
+
if (!REVISION_ID_ELEMENTS.has(localNameOf3(node))) continue;
|
|
11423
|
+
const id = wordAttribute3(node, "id");
|
|
11424
|
+
if (!id) continue;
|
|
11425
|
+
if (seenIds.has(id)) {
|
|
11426
|
+
addIssue("DUPLICATE_REVISION_ID", "error", `Revision id ${id} appears more than once.`);
|
|
11427
|
+
}
|
|
11428
|
+
seenIds.add(id);
|
|
11429
|
+
}
|
|
11430
|
+
const textNodes = elementsByLocalName(doc, "t").concat(elementsByLocalName(doc, "delText"));
|
|
11431
|
+
for (const node of textNodes) {
|
|
11432
|
+
const text2 = node.textContent || "";
|
|
11433
|
+
if (/^\s|\s$/.test(text2) && xmlSpaceAttribute(node) !== "preserve") {
|
|
11434
|
+
addIssue(
|
|
11435
|
+
"MISSING_SPACE_PRESERVE",
|
|
11436
|
+
"error",
|
|
11437
|
+
`<${node.nodeName}> has boundary whitespace without xml:space="preserve".`
|
|
11438
|
+
);
|
|
11439
|
+
}
|
|
11440
|
+
if (text2 === "") {
|
|
11441
|
+
addIssue("EMPTY_TEXT_ELEMENT", "warning", `<${node.nodeName}> is empty.`);
|
|
11442
|
+
}
|
|
11443
|
+
}
|
|
11444
|
+
for (const revision of revisions) {
|
|
11445
|
+
if (isParagraphMarkRevision(revision)) continue;
|
|
11446
|
+
const hasElementChild = Array.from(revision.childNodes || []).some((child) => child.nodeType === 1);
|
|
11447
|
+
if (!hasElementChild) {
|
|
11448
|
+
addIssue(
|
|
11449
|
+
"EMPTY_REVISION_WRAPPER",
|
|
11450
|
+
"warning",
|
|
11451
|
+
`<${revision.nodeName}> (w:id="${wordAttribute3(revision, "id")}") wraps no content.`
|
|
11452
|
+
);
|
|
11453
|
+
}
|
|
11454
|
+
}
|
|
11455
|
+
for (const resurrection of findForeignDeletedParagraphResurrections(doc)) {
|
|
11456
|
+
const ownerAuthor = resurrection.ownerAuthor || "unattributed";
|
|
11457
|
+
addIssue(
|
|
11458
|
+
"FOREIGN_PARAGRAPH_MARK_DELETION",
|
|
11459
|
+
"warning",
|
|
11460
|
+
`Paragraph deleted by ${ownerAuthor} also contains non-empty insertion content from another author; Accept/Reject lifecycle may not preserve the apparent restoration.`
|
|
11461
|
+
);
|
|
11462
|
+
}
|
|
11463
|
+
return { valid: !issues.some((issue) => issue.severity === "error"), issues };
|
|
11464
|
+
}
|
|
11465
|
+
|
|
11466
|
+
// core/table-targeting.js
|
|
11467
|
+
function getDirectWordChildren(element, localName2) {
|
|
11468
|
+
if (!element) return [];
|
|
11469
|
+
return Array.from(element.childNodes || []).filter(
|
|
11470
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === WORD_MAIN_NS && node.localName === localName2
|
|
11471
|
+
);
|
|
11472
|
+
}
|
|
11473
|
+
function escapeMarkdownCell(text2) {
|
|
11474
|
+
return String(text2 || "").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
11475
|
+
}
|
|
11476
|
+
function extractTableMatrix(tableElement) {
|
|
11477
|
+
const rowElements = getDirectWordChildren(tableElement, "tr");
|
|
11478
|
+
const matrix = rowElements.map((row) => {
|
|
11479
|
+
const cellElements = getDirectWordChildren(row, "tc");
|
|
11480
|
+
return cellElements.map((cell) => {
|
|
11481
|
+
const paragraphs = getDirectWordChildren(cell, "p");
|
|
11482
|
+
if (paragraphs.length === 0) return normalizeWhitespaceForTargeting(getParagraphText(cell));
|
|
11483
|
+
const lines = paragraphs.map((p) => normalizeWhitespaceForTargeting(getParagraphText(p))).filter(Boolean);
|
|
11484
|
+
return lines.join("\n");
|
|
11485
|
+
});
|
|
11486
|
+
});
|
|
11487
|
+
const columnCount = matrix.reduce((max2, row) => Math.max(max2, row.length), 0);
|
|
11488
|
+
matrix.forEach((row) => {
|
|
11489
|
+
while (row.length < columnCount) row.push("");
|
|
11490
|
+
});
|
|
11491
|
+
return { matrix, rowElements, columnCount };
|
|
11492
|
+
}
|
|
11493
|
+
function tableMatrixToMarkdown(matrix, columnCount) {
|
|
11494
|
+
if (!Array.isArray(matrix) || matrix.length === 0 || columnCount <= 0) return null;
|
|
11495
|
+
const normalized = matrix.map((row) => {
|
|
11496
|
+
const copy = Array.isArray(row) ? row.slice(0, columnCount) : [];
|
|
11497
|
+
while (copy.length < columnCount) copy.push("");
|
|
11498
|
+
return copy;
|
|
11499
|
+
});
|
|
11500
|
+
const header = normalized[0];
|
|
11501
|
+
const separator = new Array(columnCount).fill("---");
|
|
11502
|
+
const bodyRows = normalized.slice(1);
|
|
11503
|
+
const toLine = (row) => `| ${row.map((cell) => escapeMarkdownCell(cell)).join(" | ")} |`;
|
|
11504
|
+
return [toLine(header), toLine(separator), ...bodyRows.map(toLine)].join("\n");
|
|
11505
|
+
}
|
|
11506
|
+
function isSymmetricLabelRow(rowValues) {
|
|
11507
|
+
if (!Array.isArray(rowValues) || rowValues.length < 2) return false;
|
|
11508
|
+
const normalized = rowValues.map((value) => normalizeWhitespaceForTargeting(value)).filter(Boolean);
|
|
11509
|
+
if (normalized.length < 2) return false;
|
|
11510
|
+
return normalized.every((value) => value === normalized[0]);
|
|
11511
|
+
}
|
|
11512
|
+
function isLikelyStructuredTableSourceParagraph(text2) {
|
|
11513
|
+
const normalized = String(text2 || "").trim();
|
|
11514
|
+
if (!normalized) return false;
|
|
11515
|
+
if (/^and$/i.test(normalized)) return true;
|
|
11516
|
+
if (/^\[.*\]$/.test(normalized)) return true;
|
|
11517
|
+
if (/^\(.*\)$/.test(normalized)) return true;
|
|
11518
|
+
if (/:\s*$/.test(normalized)) return true;
|
|
11519
|
+
if (normalized.length <= 90 && !/[.!?]$/.test(normalized) && /[:\[\]()]/.test(normalized)) return true;
|
|
11520
|
+
if (/^[\[(]/.test(normalized)) return true;
|
|
11521
|
+
return false;
|
|
11522
|
+
}
|
|
11523
|
+
function inferTableReplacementParagraphBlock(startParagraph, options = {}) {
|
|
11524
|
+
const maxScan = Number.isInteger(options?.maxScan) && options.maxScan > 0 ? options.maxScan : 10;
|
|
11525
|
+
const paragraphTextGetter = typeof options?.getParagraphText === "function" ? options.getParagraphText : getParagraphText;
|
|
11526
|
+
if (!startParagraph || !startParagraph.parentNode) return null;
|
|
11527
|
+
const block = [startParagraph];
|
|
11528
|
+
let cursor = startParagraph.nextSibling;
|
|
11529
|
+
let scanned = 0;
|
|
11530
|
+
while (cursor && scanned < maxScan) {
|
|
11531
|
+
scanned += 1;
|
|
11532
|
+
const nextCursor = cursor.nextSibling;
|
|
11533
|
+
if (cursor.nodeType !== 1 || cursor.namespaceURI !== WORD_MAIN_NS || cursor.localName !== "p") {
|
|
11534
|
+
cursor = nextCursor;
|
|
11535
|
+
continue;
|
|
11536
|
+
}
|
|
11537
|
+
const text2 = String(paragraphTextGetter(cursor) || "").trim();
|
|
11538
|
+
if (!text2) {
|
|
11539
|
+
if (block.length > 1) break;
|
|
11540
|
+
cursor = nextCursor;
|
|
11541
|
+
continue;
|
|
11354
11542
|
}
|
|
11543
|
+
if (!isLikelyStructuredTableSourceParagraph(text2)) break;
|
|
11544
|
+
block.push(cursor);
|
|
11545
|
+
cursor = nextCursor;
|
|
11355
11546
|
}
|
|
11356
|
-
return null;
|
|
11357
|
-
}
|
|
11358
|
-
function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startAt) {
|
|
11359
|
-
const safeNumId = String(numId);
|
|
11360
|
-
const safeAbstractNumId = String(abstractNumId);
|
|
11361
|
-
const safeStartAt = Number.isInteger(startAt) && startAt > 0 ? startAt : 1;
|
|
11362
|
-
const levelsXml = Array.from({ length: 9 }, (_, level) => {
|
|
11363
|
-
const lvlText = Array.from({ length: level + 1 }, (_2, i2) => `%${i2 + 1}`).join(".") + ".";
|
|
11364
|
-
const left = 720 * (level + 1);
|
|
11365
|
-
return `
|
|
11366
|
-
<w:lvl w:ilvl="${level}">
|
|
11367
|
-
<w:start w:val="1"/>
|
|
11368
|
-
<w:numFmt w:val="decimal"/>
|
|
11369
|
-
<w:lvlText w:val="${lvlText}"/>
|
|
11370
|
-
<w:lvlJc w:val="left"/>
|
|
11371
|
-
<w:pPr><w:ind w:left="${left}" w:hanging="360"/></w:pPr>
|
|
11372
|
-
</w:lvl>`;
|
|
11373
|
-
}).join("");
|
|
11374
|
-
return `
|
|
11375
|
-
<w:numbering xmlns:w="${WORD_MAIN_NS}">
|
|
11376
|
-
<w:abstractNum w:abstractNumId="${safeAbstractNumId}">
|
|
11377
|
-
<w:multiLevelType w:val="multilevel"/>
|
|
11378
|
-
${levelsXml}
|
|
11379
|
-
</w:abstractNum>
|
|
11380
|
-
<w:num w:numId="${safeNumId}">
|
|
11381
|
-
<w:abstractNumId w:val="${safeAbstractNumId}"/>
|
|
11382
|
-
<w:lvlOverride w:ilvl="0">
|
|
11383
|
-
<w:startOverride w:val="${safeStartAt}"/>
|
|
11384
|
-
</w:lvlOverride>
|
|
11385
|
-
</w:num>
|
|
11386
|
-
</w:numbering>`.trim();
|
|
11547
|
+
return block.length > 1 ? block : null;
|
|
11387
11548
|
}
|
|
11388
|
-
function
|
|
11389
|
-
const
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11549
|
+
function synthesizeTableMarkdownFromMultilineCellEdit(targetParagraph, modifiedText, options = {}) {
|
|
11550
|
+
const onInfo = typeof options.onInfo === "function" ? options.onInfo : () => {
|
|
11551
|
+
};
|
|
11552
|
+
const onWarn = typeof options.onWarn === "function" ? options.onWarn : () => {
|
|
11553
|
+
};
|
|
11554
|
+
const rawModified = String(modifiedText || "");
|
|
11555
|
+
if (!rawModified.includes("\n")) return null;
|
|
11556
|
+
if (isMarkdownTableText(rawModified)) return null;
|
|
11557
|
+
const lines = rawModified.split(/\r?\n/g).map((line) => line.trim()).filter(Boolean);
|
|
11558
|
+
if (lines.length < 2) return null;
|
|
11559
|
+
const tableElement = options.tableElement || findContainingWordElement(targetParagraph, "tbl");
|
|
11560
|
+
const rowElement = findContainingWordElement(targetParagraph, "tr");
|
|
11561
|
+
const cellElement = findContainingWordElement(targetParagraph, "tc");
|
|
11562
|
+
if (!tableElement || !rowElement || !cellElement) return null;
|
|
11563
|
+
const currentParagraphText = normalizeWhitespaceForTargeting(
|
|
11564
|
+
options.currentParagraphText || getParagraphText(targetParagraph)
|
|
11565
|
+
);
|
|
11566
|
+
const firstLine = normalizeWhitespaceForTargeting(lines[0]);
|
|
11567
|
+
if (currentParagraphText && firstLine && firstLine !== currentParagraphText) {
|
|
11568
|
+
onWarn("[Table] Multiline cell text did not anchor to original cell text; skipping table-row synthesis heuristic.");
|
|
11569
|
+
return null;
|
|
11395
11570
|
}
|
|
11396
|
-
const
|
|
11397
|
-
|
|
11398
|
-
const
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11571
|
+
const { matrix, rowElements, columnCount } = extractTableMatrix(tableElement);
|
|
11572
|
+
if (matrix.length === 0 || columnCount === 0) return null;
|
|
11573
|
+
const rowIndex = rowElements.indexOf(rowElement);
|
|
11574
|
+
const cellElements = getDirectWordChildren(rowElement, "tc");
|
|
11575
|
+
const colIndex = cellElements.indexOf(cellElement);
|
|
11576
|
+
if (rowIndex < 0 || colIndex < 0 || colIndex >= columnCount) return null;
|
|
11577
|
+
matrix[rowIndex][colIndex] = lines[0];
|
|
11578
|
+
const mirrorAcrossColumns = isSymmetricLabelRow(matrix[rowIndex]);
|
|
11579
|
+
if (mirrorAcrossColumns) {
|
|
11580
|
+
onInfo("[Table] Symmetric row detected; mirroring inserted row values across columns.");
|
|
11406
11581
|
}
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
const
|
|
11410
|
-
if (
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
setElementVal2(abstractNumIdNode, abstractNumMap.get(oldAbsRef));
|
|
11582
|
+
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
11583
|
+
const insertIndex = rowIndex + i2;
|
|
11584
|
+
const extraValue = lines[i2];
|
|
11585
|
+
if (insertIndex < matrix.length && !normalizeWhitespaceForTargeting(matrix[insertIndex][colIndex])) {
|
|
11586
|
+
if (mirrorAcrossColumns) {
|
|
11587
|
+
for (let col = 0; col < columnCount; col++) {
|
|
11588
|
+
if (!normalizeWhitespaceForTargeting(matrix[insertIndex][col])) {
|
|
11589
|
+
matrix[insertIndex][col] = extraValue;
|
|
11590
|
+
}
|
|
11591
|
+
}
|
|
11592
|
+
} else {
|
|
11593
|
+
matrix[insertIndex][colIndex] = extraValue;
|
|
11420
11594
|
}
|
|
11421
|
-
}
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
|
|
11428
|
-
if (oldNumRef != null && numIdMap.has(oldNumRef)) {
|
|
11429
|
-
setElementVal2(numIdNode, numIdMap.get(oldNumRef));
|
|
11595
|
+
} else {
|
|
11596
|
+
const newRow = new Array(columnCount).fill("");
|
|
11597
|
+
if (mirrorAcrossColumns) {
|
|
11598
|
+
for (let col = 0; col < columnCount; col++) newRow[col] = extraValue;
|
|
11599
|
+
} else {
|
|
11600
|
+
newRow[colIndex] = extraValue;
|
|
11430
11601
|
}
|
|
11602
|
+
matrix.splice(Math.min(insertIndex, matrix.length), 0, newRow);
|
|
11431
11603
|
}
|
|
11432
11604
|
}
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
}
|
|
11438
|
-
function mergeNumberingXmlBySchemaOrder(existingNumberingXml, incomingNumberingXml) {
|
|
11439
|
-
const existingText = String(existingNumberingXml || "");
|
|
11440
|
-
const incomingText = String(incomingNumberingXml || "");
|
|
11441
|
-
if (!incomingText.trim()) return existingText;
|
|
11442
|
-
if (!existingText.trim()) return incomingText;
|
|
11443
|
-
try {
|
|
11444
|
-
const existingDoc = parseOoxml(existingText);
|
|
11445
|
-
const incomingDoc = parseOoxml(incomingText);
|
|
11446
|
-
if (hasXmlParseError(existingDoc) || hasXmlParseError(incomingDoc)) {
|
|
11447
|
-
return existingText;
|
|
11448
|
-
}
|
|
11449
|
-
const existingRoot = existingDoc.documentElement;
|
|
11450
|
-
const incomingRoot = incomingDoc.documentElement;
|
|
11451
|
-
if (!existingRoot || !incomingRoot) return existingText;
|
|
11452
|
-
const existingAbstractIds = new Set(
|
|
11453
|
-
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "abstractNum")).map((node) => parseIntegerAttribute(node, ["w:abstractNumId", "abstractNumId"])).filter((id) => id != null)
|
|
11454
|
-
);
|
|
11455
|
-
const existingNumIds = new Set(
|
|
11456
|
-
Array.from(existingRoot.childNodes || []).filter((node) => isDirectWordChild(node, "num")).map((node) => parseIntegerAttribute(node, ["w:numId", "numId"])).filter((id) => id != null)
|
|
11457
|
-
);
|
|
11458
|
-
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
11459
|
-
if (!isDirectWordChild(incomingNode, "abstractNum")) continue;
|
|
11460
|
-
const incomingId = parseIntegerAttribute(incomingNode, ["w:abstractNumId", "abstractNumId"]);
|
|
11461
|
-
if (incomingId == null || existingAbstractIds.has(incomingId)) continue;
|
|
11462
|
-
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "abstract");
|
|
11463
|
-
existingAbstractIds.add(incomingId);
|
|
11464
|
-
}
|
|
11465
|
-
for (const incomingNode of Array.from(incomingRoot.childNodes || [])) {
|
|
11466
|
-
if (!isDirectWordChild(incomingNode, "num")) continue;
|
|
11467
|
-
const incomingId = parseIntegerAttribute(incomingNode, ["w:numId", "numId"]);
|
|
11468
|
-
if (incomingId == null || existingNumIds.has(incomingId)) continue;
|
|
11469
|
-
insertNumberingNodeInSchemaOrder(existingRoot, existingDoc.importNode(incomingNode, true), "num");
|
|
11470
|
-
existingNumIds.add(incomingId);
|
|
11471
|
-
}
|
|
11472
|
-
return serializeOoxml(existingDoc);
|
|
11473
|
-
} catch {
|
|
11474
|
-
return existingText;
|
|
11475
|
-
}
|
|
11605
|
+
const markdown = tableMatrixToMarkdown(matrix, columnCount);
|
|
11606
|
+
if (!markdown) return null;
|
|
11607
|
+
onInfo("[Table] Synthesized full markdown table from multiline cell edit for table-scope reconciliation.");
|
|
11608
|
+
return markdown;
|
|
11476
11609
|
}
|
|
11477
11610
|
|
|
11478
11611
|
// pipeline/ingestion-export.js
|
|
@@ -12242,6 +12375,8 @@ function areRevisionTokensEqual(a, b) {
|
|
|
12242
12375
|
var attr2 = (node, name) => node?.getAttribute?.(`w:${name}`) || node?.getAttribute?.(name) || "";
|
|
12243
12376
|
var descendants = (node, name) => Array.from(node?.getElementsByTagNameNS?.(NS_W, name) || []);
|
|
12244
12377
|
var first = (node, name) => descendants(node, name)[0] || null;
|
|
12378
|
+
var directChild2 = (node, name) => Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.localName === name && (!child.namespaceURI || child.namespaceURI === NS_W)) || null;
|
|
12379
|
+
var currentPPr = (paragraph) => directChild2(paragraph, "pPr");
|
|
12245
12380
|
function hasAncestor(node, localName2) {
|
|
12246
12381
|
let cursor = node?.parentNode;
|
|
12247
12382
|
while (cursor) {
|
|
@@ -12257,11 +12392,11 @@ function parseXml2(xml, partName, required = false) {
|
|
|
12257
12392
|
return { doc: parsed.doc, warnings: parsed.warnings || [] };
|
|
12258
12393
|
}
|
|
12259
12394
|
function paragraphListProperties(paragraph) {
|
|
12260
|
-
const numPr =
|
|
12395
|
+
const numPr = directChild2(currentPPr(paragraph), "numPr");
|
|
12261
12396
|
if (!numPr) return null;
|
|
12262
|
-
const numId = attr2(
|
|
12397
|
+
const numId = attr2(directChild2(numPr, "numId"), "val");
|
|
12263
12398
|
if (!numId || numId === "0") return null;
|
|
12264
|
-
return { numId, level: Number.parseInt(attr2(
|
|
12399
|
+
return { numId, level: Number.parseInt(attr2(directChild2(numPr, "ilvl"), "val") || "0", 10) || 0 };
|
|
12265
12400
|
}
|
|
12266
12401
|
function parseNumbering(numberingDoc) {
|
|
12267
12402
|
const abstracts = /* @__PURE__ */ new Map();
|
|
@@ -12346,11 +12481,11 @@ function createNumberingResolver(numberingDoc) {
|
|
|
12346
12481
|
};
|
|
12347
12482
|
}
|
|
12348
12483
|
function headingLevel(paragraph) {
|
|
12349
|
-
const pPr =
|
|
12350
|
-
const style = attr2(
|
|
12484
|
+
const pPr = currentPPr(paragraph);
|
|
12485
|
+
const style = attr2(directChild2(pPr, "pStyle"), "val");
|
|
12351
12486
|
const match = style.match(/^heading\s*([1-9])$/i);
|
|
12352
12487
|
if (match) return Math.min(Number(match[1]), 6);
|
|
12353
|
-
const outline = Number.parseInt(attr2(
|
|
12488
|
+
const outline = Number.parseInt(attr2(directChild2(pPr, "outlineLvl"), "val"), 10);
|
|
12354
12489
|
return Number.isInteger(outline) ? Math.min(outline + 1, 6) : null;
|
|
12355
12490
|
}
|
|
12356
12491
|
function structuralContext(paragraph, text2) {
|
|
@@ -12529,7 +12664,7 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
12529
12664
|
const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
|
|
12530
12665
|
const authors = revisionAuthors(paragraph);
|
|
12531
12666
|
const list = resolveNumbering(paragraphListProperties(paragraph));
|
|
12532
|
-
const styleId = attr2(
|
|
12667
|
+
const styleId = attr2(directChild2(currentPPr(paragraph), "pStyle"), "val") || null;
|
|
12533
12668
|
const structure = structuralContext(paragraph, text2);
|
|
12534
12669
|
const index = zeroIndex + 1;
|
|
12535
12670
|
const provision = list?.label && list.format !== "bullet" ? list.label : null;
|
|
@@ -15120,7 +15255,7 @@ function validateDocumentOperation(operation) {
|
|
|
15120
15255
|
}
|
|
15121
15256
|
};
|
|
15122
15257
|
}
|
|
15123
|
-
if (target.revisionView !== "accepted" || target.captureRef || target.occurrence != null || normalized.captureKey || normalized.targetEndDescriptor || normalized.targetEndRef != null
|
|
15258
|
+
if (target.revisionView !== "accepted" || target.captureRef || target.occurrence != null || normalized.captureKey || normalized.targetEndDescriptor || normalized.targetEndRef != null) {
|
|
15124
15259
|
return {
|
|
15125
15260
|
valid: false,
|
|
15126
15261
|
error: {
|
|
@@ -15142,6 +15277,15 @@ function validateDocumentOperation(operation) {
|
|
|
15142
15277
|
}
|
|
15143
15278
|
};
|
|
15144
15279
|
}
|
|
15280
|
+
if (replacementValidation.replacements.some((replacement) => /\r|\n/.test(replacement.find))) {
|
|
15281
|
+
return {
|
|
15282
|
+
valid: false,
|
|
15283
|
+
error: {
|
|
15284
|
+
code: "INVALID_OPERATION",
|
|
15285
|
+
message: 'Localized replacements v1 cannot span a line break; use one replacement per line or a full "modified" paragraph.'
|
|
15286
|
+
}
|
|
15287
|
+
};
|
|
15288
|
+
}
|
|
15145
15289
|
normalized.replacements = replacementValidation.replacements;
|
|
15146
15290
|
}
|
|
15147
15291
|
}
|
|
@@ -15336,6 +15480,12 @@ function validateDocumentOperation(operation) {
|
|
|
15336
15480
|
error: { code: "INVALID_OPERATION", message: 'Format operations require a non-empty "textToFormat" field.' }
|
|
15337
15481
|
};
|
|
15338
15482
|
}
|
|
15483
|
+
if (normalized.textOccurrence != null && (!Number.isInteger(normalized.textOccurrence) || normalized.textOccurrence < 1)) {
|
|
15484
|
+
return {
|
|
15485
|
+
valid: false,
|
|
15486
|
+
error: { code: "INVALID_OPERATION", message: "textOccurrence must be a positive integer when provided." }
|
|
15487
|
+
};
|
|
15488
|
+
}
|
|
15339
15489
|
if (!isRecord(normalized.properties)) {
|
|
15340
15490
|
return {
|
|
15341
15491
|
valid: false,
|
|
@@ -15792,7 +15942,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
15792
15942
|
};
|
|
15793
15943
|
const compiled = { ...sourceOperation };
|
|
15794
15944
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
15795
|
-
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
15945
|
+
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));
|
|
15796
15946
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
15797
15947
|
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
15798
15948
|
try {
|
|
@@ -16130,6 +16280,17 @@ function deriveSingleParagraphPlainAdjacencyInsertion(currentParagraphText, modi
|
|
|
16130
16280
|
}
|
|
16131
16281
|
return null;
|
|
16132
16282
|
}
|
|
16283
|
+
function deriveParagraphThenTableAppend(modifiedText) {
|
|
16284
|
+
const lines = String(modifiedText || "").split(/\r?\n/g).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
16285
|
+
if (lines.length < 3) return null;
|
|
16286
|
+
const tableStart = lines.findIndex((line) => line.trim().startsWith("|"));
|
|
16287
|
+
if (tableStart !== 1) return null;
|
|
16288
|
+
const tableLines = lines.slice(1);
|
|
16289
|
+
if (!tableLines.every((line) => line.trim().startsWith("|"))) return null;
|
|
16290
|
+
const separator = tableLines[1].replace(/\s+/g, "");
|
|
16291
|
+
if (!/^\|:?-{3,}:?(\|:?-{3,}:?)+\|?$/.test(separator)) return null;
|
|
16292
|
+
return { paragraphMarkdown: lines[0].trim(), tableText: tableLines.join("\n") };
|
|
16293
|
+
}
|
|
16133
16294
|
|
|
16134
16295
|
// services/capture-engine.js
|
|
16135
16296
|
function createCaptureError(code, message) {
|
|
@@ -17915,6 +18076,81 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
17915
18076
|
numberingXml: null
|
|
17916
18077
|
};
|
|
17917
18078
|
}
|
|
18079
|
+
const tableAppendCandidate = !useTableScope && !hasExplicitRangeScope && !targetListInfo && !containingTable && !targetEndRef ? deriveParagraphThenTableAppend(effectiveModifiedText) : null;
|
|
18080
|
+
const tableAppendParagraph = tableAppendCandidate ? preprocessMarkdown(tableAppendCandidate.paragraphMarkdown) : null;
|
|
18081
|
+
if (tableAppendCandidate && normalizeWhitespaceForTargeting(tableAppendParagraph.cleanText) === normalizeWhitespaceForTargeting(currentParagraphText)) {
|
|
18082
|
+
const tableData = parseTable(tableAppendCandidate.tableText);
|
|
18083
|
+
if (tableData.headers.length > 0 || tableData.rows.length > 0) {
|
|
18084
|
+
onInfo("[Table] Applying retained-paragraph table append heuristic.");
|
|
18085
|
+
const parent2 = targetParagraph.parentNode;
|
|
18086
|
+
if (!parent2) throw new Error("Target paragraph has no parent for table append");
|
|
18087
|
+
const followingNode = targetParagraph.nextSibling;
|
|
18088
|
+
if (tableAppendParagraph.formatHints.length > 0) {
|
|
18089
|
+
const formatted = await applyRedlineToOxml2(
|
|
18090
|
+
serializer.serializeToString(targetParagraph),
|
|
18091
|
+
currentParagraphText || targetText,
|
|
18092
|
+
tableAppendCandidate.paragraphMarkdown,
|
|
18093
|
+
{
|
|
18094
|
+
author,
|
|
18095
|
+
generateRedlines,
|
|
18096
|
+
existingRevisions: options.existingRevisions || "merge-same-author",
|
|
18097
|
+
structuredContent: false,
|
|
18098
|
+
_revisionIdAllocator: revisionIdAllocator
|
|
18099
|
+
}
|
|
18100
|
+
);
|
|
18101
|
+
if (formatted?.status === "error") {
|
|
18102
|
+
return {
|
|
18103
|
+
documentXml,
|
|
18104
|
+
hasChanges: false,
|
|
18105
|
+
numberingXml: null,
|
|
18106
|
+
status: "error",
|
|
18107
|
+
error: formatted.error
|
|
18108
|
+
};
|
|
18109
|
+
}
|
|
18110
|
+
if (formatted?.hasChanges) {
|
|
18111
|
+
if (typeof formatted.oxml !== "string") {
|
|
18112
|
+
throw new Error("Reconciliation engine did not return OOXML for a changed paragraph format");
|
|
18113
|
+
}
|
|
18114
|
+
for (const node of extractReplacementNodes(formatted.oxml).replacementNodes) {
|
|
18115
|
+
const imported = xmlDoc.importNode(node, true);
|
|
18116
|
+
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
18117
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(imported, targetParagraph));
|
|
18118
|
+
}
|
|
18119
|
+
options?._mutationRemovedNodes?.push(targetParagraph);
|
|
18120
|
+
parent2.removeChild(targetParagraph);
|
|
18121
|
+
}
|
|
18122
|
+
}
|
|
18123
|
+
const tableOoxml = generateTableOoxml(tableData, {
|
|
18124
|
+
generateRedlines,
|
|
18125
|
+
author,
|
|
18126
|
+
revisionIdAllocator,
|
|
18127
|
+
trackAsBlock: true
|
|
18128
|
+
});
|
|
18129
|
+
for (const node of extractReplacementNodes(tableOoxml).replacementNodes) {
|
|
18130
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(xmlDoc.importNode(node, true), followingNode));
|
|
18131
|
+
}
|
|
18132
|
+
const endsContainer = !followingNode || followingNode.nodeType === 1 && followingNode.namespaceURI === NS_W9 && followingNode.localName === "sectPr";
|
|
18133
|
+
if (endsContainer) {
|
|
18134
|
+
const trailing = createWordElement(xmlDoc, "w:p");
|
|
18135
|
+
const trailingPPr = clonePropertiesWithoutRevisionHistory(getDirectWordChild4(targetParagraph, "pPr"));
|
|
18136
|
+
if (trailingPPr) {
|
|
18137
|
+
for (const child of Array.from(trailingPPr.childNodes)) {
|
|
18138
|
+
if (child.nodeType === 1 && ["sectPr", "numPr"].includes(child.localName)) trailingPPr.removeChild(child);
|
|
18139
|
+
}
|
|
18140
|
+
trailing.appendChild(trailingPPr);
|
|
18141
|
+
}
|
|
18142
|
+
if (generateRedlines) markParagraphMarkInserted(xmlDoc, trailing, author);
|
|
18143
|
+
ensureParagraphIdsOnImportedNode(trailing, operationSession);
|
|
18144
|
+
options?._mutationLiveNodes?.push(parent2.insertBefore(trailing, followingNode));
|
|
18145
|
+
}
|
|
18146
|
+
normalizeBodySectionOrder(xmlDoc);
|
|
18147
|
+
return {
|
|
18148
|
+
documentXml: completedDocumentXml(xmlDoc, serializer, documentXml, operationSession),
|
|
18149
|
+
hasChanges: true,
|
|
18150
|
+
numberingXml: null
|
|
18151
|
+
};
|
|
18152
|
+
}
|
|
18153
|
+
}
|
|
17918
18154
|
const plainAdjacencyInsertionCandidate = !bypassSingleParagraphHeuristics && !useTableScope && !hasExplicitRangeScope && !targetListInfo ? deriveSingleParagraphPlainAdjacencyInsertion(currentParagraphText, effectiveModifiedText) : null;
|
|
17919
18155
|
if (plainAdjacencyInsertionCandidate) {
|
|
17920
18156
|
onInfo(
|
|
@@ -17930,7 +18166,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
17930
18166
|
paragraphText,
|
|
17931
18167
|
generateRedlines ? createRevisionMetadata(author, xmlDoc) : null,
|
|
17932
18168
|
author,
|
|
17933
|
-
|
|
18169
|
+
// A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
|
|
18170
|
+
// Reject All removes the text but leaves an empty paragraph behind.
|
|
18171
|
+
{ generateRedlines, trackParagraphMark: true }
|
|
17934
18172
|
);
|
|
17935
18173
|
const imported = xmlDoc.importNode(plainParagraph, true);
|
|
17936
18174
|
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
@@ -18038,6 +18276,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
18038
18276
|
explicitStructuredContent: options.explicitStructuredContent === true,
|
|
18039
18277
|
pairReplacements: options.pairReplacements === true,
|
|
18040
18278
|
insertionAffinity: options.insertionAffinity || null,
|
|
18279
|
+
literalTextEdit: options.literalTextEdit === true,
|
|
18041
18280
|
_revisionIdAllocator: revisionIdAllocator,
|
|
18042
18281
|
_isolatedTableCell: useTableScope
|
|
18043
18282
|
});
|
|
@@ -18061,6 +18300,14 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
18061
18300
|
const extracted = extractReplacementNodes(result.oxml);
|
|
18062
18301
|
let replacementNodes = removeListPackagingSentinel(extracted.replacementNodes, result.warnings);
|
|
18063
18302
|
let numberingXml = extracted.numberingXml;
|
|
18303
|
+
if (numberingXml && runtimeContext?.numberingIdState && !useTableScope) {
|
|
18304
|
+
const sourceParagraphs = useListScope ? listScopeEdit.paragraphs : explicitRangeParagraphs || [targetParagraph];
|
|
18305
|
+
const continued = reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, runtimeContext.numberingIdState);
|
|
18306
|
+
if (continued) {
|
|
18307
|
+
replacementNodes = continued.replacementNodes;
|
|
18308
|
+
numberingXml = null;
|
|
18309
|
+
}
|
|
18310
|
+
}
|
|
18064
18311
|
if (numberingXml && runtimeContext?.numberingIdState) {
|
|
18065
18312
|
const normalizedNumbering = remapNumberingPayloadForDocument(numberingXml, replacementNodes, runtimeContext.numberingIdState);
|
|
18066
18313
|
replacementNodes = normalizedNumbering.replacementNodes;
|
|
@@ -18317,7 +18564,8 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
18317
18564
|
spanRanges.push({ ...entry, start, end });
|
|
18318
18565
|
}
|
|
18319
18566
|
const targetDescriptor = options.targetDescriptor;
|
|
18320
|
-
const
|
|
18567
|
+
const explicitTextOccurrence = Number.isInteger(options.textOccurrence) && options.textOccurrence > 0 ? options.textOccurrence : null;
|
|
18568
|
+
const occurrence = explicitTextOccurrence ?? targetDescriptor?.occurrence ?? null;
|
|
18321
18569
|
let matchStart = -1;
|
|
18322
18570
|
if (occurrence != null && occurrence > 0) {
|
|
18323
18571
|
let count = 0;
|
|
@@ -18335,6 +18583,17 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
18335
18583
|
} else {
|
|
18336
18584
|
matchStart = fullParaText.indexOf(textToFormat);
|
|
18337
18585
|
}
|
|
18586
|
+
if (matchStart === -1 && explicitTextOccurrence != null && fullParaText.includes(textToFormat)) {
|
|
18587
|
+
return {
|
|
18588
|
+
documentXml,
|
|
18589
|
+
hasChanges: false,
|
|
18590
|
+
status: "error",
|
|
18591
|
+
error: {
|
|
18592
|
+
code: "PATCH_SOURCE_NOT_FOUND",
|
|
18593
|
+
message: `Occurrence ${explicitTextOccurrence} of text to format was not found in target paragraph: "${textToFormat}".`
|
|
18594
|
+
}
|
|
18595
|
+
};
|
|
18596
|
+
}
|
|
18338
18597
|
if (matchStart === -1) {
|
|
18339
18598
|
return {
|
|
18340
18599
|
documentXml,
|
|
@@ -18617,9 +18876,11 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
18617
18876
|
...operation.existingRevisions ? { existingRevisions: operation.existingRevisions } : {},
|
|
18618
18877
|
structuredContent: typeof operation.structuredContent === "boolean" ? operation.structuredContent : options.structuredContent !== false,
|
|
18619
18878
|
explicitStructuredContent: operation.structuredContent === true,
|
|
18879
|
+
...operation._localizedReplacementCompilation && operation.structuredContent !== true ? { literalTextEdit: true } : {},
|
|
18620
18880
|
pairReplacements: typeof operation.pairReplacements === "boolean" ? operation.pairReplacements : options.pairReplacements !== false,
|
|
18621
18881
|
...operation.insertionAffinity ? { insertionAffinity: operation.insertionAffinity } : {},
|
|
18622
18882
|
...operation.formattingRevisionPolicy ? { formattingRevisionPolicy: operation.formattingRevisionPolicy } : {},
|
|
18883
|
+
...operation.operationKind === "format" && Number.isInteger(operation.textOccurrence) ? { textOccurrence: operation.textOccurrence } : {},
|
|
18623
18884
|
targetDescriptor: operation.targetDescriptor,
|
|
18624
18885
|
targetEndDescriptor: operation.targetEndDescriptor,
|
|
18625
18886
|
_compiledSourceId: operation._compiledSourceId || null,
|
|
@@ -19243,6 +19504,9 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
19243
19504
|
const scheduled = dependencyPlan.scheduled;
|
|
19244
19505
|
const atomic = options.atomic === true;
|
|
19245
19506
|
const continueOnError = options.continueOnError !== false;
|
|
19507
|
+
if (runtimeContext && typeof runtimeContext === "object" && !runtimeContext.numberingIdState && typeof runtimeContext.numberingXml === "string" && runtimeContext.numberingXml.trim()) {
|
|
19508
|
+
runtimeContext.numberingIdState = createDynamicNumberingIdState(runtimeContext.numberingXml);
|
|
19509
|
+
}
|
|
19246
19510
|
const context = cloneBatchRuntimeContext(runtimeContext);
|
|
19247
19511
|
if (!(context.targetRefSnapshot instanceof Map)) {
|
|
19248
19512
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
@@ -21668,8 +21932,32 @@ function openDocx(input) {
|
|
|
21668
21932
|
}
|
|
21669
21933
|
|
|
21670
21934
|
// index.js
|
|
21935
|
+
function remapGeneratedNumberingForSource(result, options) {
|
|
21936
|
+
const sourceNumbering = typeof options?.numberingXml === "string" && options.numberingXml.trim() ? options.numberingXml : null;
|
|
21937
|
+
const state = options?.numberingIdState && typeof options.numberingIdState === "object" ? options.numberingIdState : sourceNumbering ? createDynamicNumberingIdState(sourceNumbering) : null;
|
|
21938
|
+
if (!state || !result?.hasChanges || typeof result.oxml !== "string" || !result.oxml.includes("/word/numbering.xml")) {
|
|
21939
|
+
return result;
|
|
21940
|
+
}
|
|
21941
|
+
const pkgDoc = parseOoxml(result.oxml);
|
|
21942
|
+
const parts = Array.from(pkgDoc.getElementsByTagNameNS("*", "part"));
|
|
21943
|
+
const payloadOf = (name) => {
|
|
21944
|
+
const part = parts.find((candidate) => (candidate.getAttribute("pkg:name") || candidate.getAttribute("name")) === name);
|
|
21945
|
+
const xmlData = part?.getElementsByTagNameNS("*", "xmlData")[0];
|
|
21946
|
+
return xmlData ? Array.from(xmlData.childNodes).find((node) => node.nodeType === 1) || null : null;
|
|
21947
|
+
};
|
|
21948
|
+
const numberingNode = payloadOf("/word/numbering.xml");
|
|
21949
|
+
const body = payloadOf("/word/document.xml")?.getElementsByTagNameNS("*", "body")[0];
|
|
21950
|
+
if (!numberingNode || !body) return result;
|
|
21951
|
+
const bodyNodes = Array.from(body.childNodes).filter((node) => node.nodeType === 1 && node.localName !== "sectPr");
|
|
21952
|
+
const remapped = remapNumberingPayloadForDocument(serializeOoxml(numberingNode), bodyNodes, state);
|
|
21953
|
+
const newNumbering = pkgDoc.importNode(parseOoxml(remapped.numberingXml).documentElement, true);
|
|
21954
|
+
numberingNode.parentNode.replaceChild(newNumbering, numberingNode);
|
|
21955
|
+
bodyNodes.forEach((node, index) => body.replaceChild(remapped.replacementNodes[index], node));
|
|
21956
|
+
return { ...result, oxml: serializeOoxml(pkgDoc) };
|
|
21957
|
+
}
|
|
21671
21958
|
async function applyRedlineToOxml3(oxml, originalText, modifiedText, options = {}) {
|
|
21672
|
-
const
|
|
21959
|
+
const rawResult = await applyRedlineToOxml(oxml, originalText, modifiedText, options);
|
|
21960
|
+
const result = remapGeneratedNumberingForSource(rawResult, options);
|
|
21673
21961
|
if (result?.useNativeApi && typeof result?.oxml !== "string") {
|
|
21674
21962
|
const existingWarnings = Array.isArray(result?.warnings) ? result.warnings : [];
|
|
21675
21963
|
return withOoxmlSourceType({
|