@ansonlai/docx-redline-js 0.8.2 → 0.8.3
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 +586 -553
- package/README.md +901 -890
- package/core/list-targeting.js +12 -10
- package/dist/docx-redline-js.esm.js +154 -28
- package/dist/docx-redline-js.esm.js.map +3 -3
- package/dist/docx-redline-js.esm.min.js +86 -86
- package/dist/docx-redline-js.esm.min.js.map +3 -3
- package/dist/docx-redline.bundle.cjs +154 -28
- package/dist/docx-redline.bundle.cjs.map +3 -3
- package/dist/docx-redline.bundle.js +154 -28
- package/dist/docx-redline.bundle.js.map +3 -3
- package/orchestration/list-structural-fallback.js +530 -518
- package/package.json +1 -1
- package/pipeline/list-generation.js +203 -168
- package/services/document-inspection.js +10 -7
- package/services/document-operation-mutations.js +1597 -1586
- package/services/numbering-helpers.js +108 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js standalone bundle v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone bundle v0.8.3 — 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;
|
|
@@ -10452,7 +10452,37 @@ async function executeListGeneration(options) {
|
|
|
10452
10452
|
}];
|
|
10453
10453
|
}
|
|
10454
10454
|
}
|
|
10455
|
-
|
|
10455
|
+
const sourceParagraphCount = (originalRunModel || []).filter((run) => run.kind === RunKind.PARAGRAPH_START).length;
|
|
10456
|
+
if (generateRedlines && sourceParagraphCount > 0 && (sourceParagraphCount > 1 || deletionRuns.length === 0)) {
|
|
10457
|
+
let segment = null;
|
|
10458
|
+
const flushSegment = () => {
|
|
10459
|
+
if (!segment) return;
|
|
10460
|
+
results.push(serializeToOoxml(segment, null, [], {
|
|
10461
|
+
author,
|
|
10462
|
+
generateRedlines,
|
|
10463
|
+
revisionIdAllocator
|
|
10464
|
+
}));
|
|
10465
|
+
segment = null;
|
|
10466
|
+
};
|
|
10467
|
+
for (const run of originalRunModel) {
|
|
10468
|
+
if (run.kind === RunKind.PARAGRAPH_START) {
|
|
10469
|
+
flushSegment();
|
|
10470
|
+
segment = [{
|
|
10471
|
+
...run,
|
|
10472
|
+
pPrElement: null,
|
|
10473
|
+
pPrXml: addParagraphMarkRevision(
|
|
10474
|
+
run.pPrElement || run.pPrXml || null,
|
|
10475
|
+
"del",
|
|
10476
|
+
author,
|
|
10477
|
+
revisionIdAllocator
|
|
10478
|
+
)
|
|
10479
|
+
}];
|
|
10480
|
+
} else if (segment && (run.kind === "text" || run.kind === "run")) {
|
|
10481
|
+
segment.push({ ...run, kind: "deletion", author });
|
|
10482
|
+
}
|
|
10483
|
+
}
|
|
10484
|
+
flushSegment();
|
|
10485
|
+
} else if (generateRedlines && deletionRuns.length > 0) {
|
|
10456
10486
|
const deletedPPr = addParagraphMarkRevision(sourcePPr, "del", author, revisionIdAllocator);
|
|
10457
10487
|
const deletedParagraph = serializeToOoxml(deletionRuns, deletedPPr, [], {
|
|
10458
10488
|
author,
|
|
@@ -16650,12 +16680,12 @@ function validateParagraphBoundaryMutation(targetParagraph, modifiedText, option
|
|
|
16650
16680
|
}
|
|
16651
16681
|
|
|
16652
16682
|
// core/list-targeting.js
|
|
16653
|
-
function
|
|
16654
|
-
if (!node
|
|
16655
|
-
const
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
return
|
|
16683
|
+
function getDirectChildByLocalName(node, localName2) {
|
|
16684
|
+
if (!node) return null;
|
|
16685
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
16686
|
+
if (child?.nodeType === 1 && child.localName === localName2) return child;
|
|
16687
|
+
}
|
|
16688
|
+
return null;
|
|
16659
16689
|
}
|
|
16660
16690
|
function readValAttribute(element) {
|
|
16661
16691
|
if (!element) return null;
|
|
@@ -16772,15 +16802,15 @@ function buildListEntriesForInsertion(parsedItems, normalizedTargetText, anchorL
|
|
|
16772
16802
|
}
|
|
16773
16803
|
function getParagraphListInfo(paragraph) {
|
|
16774
16804
|
if (!paragraph) return null;
|
|
16775
|
-
const pPr =
|
|
16805
|
+
const pPr = getDirectChildByLocalName(paragraph, "pPr");
|
|
16776
16806
|
if (!pPr) return null;
|
|
16777
|
-
const numPr =
|
|
16807
|
+
const numPr = getDirectChildByLocalName(pPr, "numPr");
|
|
16778
16808
|
if (!numPr) return null;
|
|
16779
|
-
const numIdEl =
|
|
16809
|
+
const numIdEl = getDirectChildByLocalName(numPr, "numId");
|
|
16780
16810
|
if (!numIdEl) return null;
|
|
16781
16811
|
const numId = readValAttribute(numIdEl);
|
|
16782
16812
|
if (!numId || numId === "0") return null;
|
|
16783
|
-
const ilvlEl =
|
|
16813
|
+
const ilvlEl = getDirectChildByLocalName(numPr, "ilvl");
|
|
16784
16814
|
const ilvlRaw = readValAttribute(ilvlEl);
|
|
16785
16815
|
const ilvl = Number.parseInt(ilvlRaw || "0", 10);
|
|
16786
16816
|
return {
|
|
@@ -17190,8 +17220,13 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
17190
17220
|
}
|
|
17191
17221
|
return updated;
|
|
17192
17222
|
}
|
|
17223
|
+
function parseOxmlFragment(oxml) {
|
|
17224
|
+
const text2 = String(oxml || "");
|
|
17225
|
+
const declared = /xmlns:w\s*=/.test(text2);
|
|
17226
|
+
return parseOoxmlSafe(declared ? text2 : `<w:root xmlns:w="${NS_W}">${text2}</w:root>`, "application/xml").doc;
|
|
17227
|
+
}
|
|
17193
17228
|
function getFirstParagraphFromOxml(oxml) {
|
|
17194
|
-
const doc =
|
|
17229
|
+
const doc = parseOxmlFragment(oxml);
|
|
17195
17230
|
if (!doc) return null;
|
|
17196
17231
|
const parseError = getXmlParseError(doc);
|
|
17197
17232
|
if (parseError) return null;
|
|
@@ -17218,17 +17253,17 @@ function setElementVal(element, value) {
|
|
|
17218
17253
|
element.setAttribute("w:val", String(value));
|
|
17219
17254
|
}
|
|
17220
17255
|
function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
17221
|
-
const doc =
|
|
17256
|
+
const doc = parseOxmlFragment(oxml);
|
|
17222
17257
|
if (!doc) return null;
|
|
17223
17258
|
const parseError = getXmlParseError(doc);
|
|
17224
17259
|
if (parseError) return null;
|
|
17225
17260
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
17226
|
-
const
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17261
|
+
for (const paragraph of paragraphs) {
|
|
17262
|
+
const numIdNodes = Array.from(paragraph.getElementsByTagNameNS("*", "numId"));
|
|
17263
|
+
for (const numIdNode of numIdNodes) {
|
|
17264
|
+
const numId = getElementId(numIdNode, ["w:val", "val"]);
|
|
17265
|
+
if (numId != null) return String(numId);
|
|
17266
|
+
}
|
|
17232
17267
|
}
|
|
17233
17268
|
return null;
|
|
17234
17269
|
}
|
|
@@ -17724,6 +17759,8 @@ function createDynamicNumberingIdState(numberingXml, options = {}) {
|
|
|
17724
17759
|
const baseNumId = Math.max(minId, maxUsedNumId + 1);
|
|
17725
17760
|
const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
|
|
17726
17761
|
return {
|
|
17762
|
+
// Kept so list edits can recognise which existing definition a source list uses and continue it.
|
|
17763
|
+
sourceNumberingXml: String(numberingXml || ""),
|
|
17727
17764
|
nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
|
|
17728
17765
|
nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
|
|
17729
17766
|
usedNumIds,
|
|
@@ -17864,6 +17901,82 @@ function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startA
|
|
|
17864
17901
|
</w:num>
|
|
17865
17902
|
</w:numbering>`.trim();
|
|
17866
17903
|
}
|
|
17904
|
+
function isTrackedDeletedParagraph(node) {
|
|
17905
|
+
if (!node || node.nodeType !== 1 || node.localName !== "p") return false;
|
|
17906
|
+
const pPr = Array.from(node.childNodes || []).find((child) => isDirectWordChild(child, "pPr"));
|
|
17907
|
+
const rPr = pPr && Array.from(pPr.childNodes || []).find((child) => isDirectWordChild(child, "rPr"));
|
|
17908
|
+
return !!rPr && Array.from(rPr.childNodes || []).some((child) => isDirectWordChild(child, "del"));
|
|
17909
|
+
}
|
|
17910
|
+
function paragraphNumIdNodes(node) {
|
|
17911
|
+
return Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []).filter((numIdNode) => numIdNode.parentNode?.localName === "numPr");
|
|
17912
|
+
}
|
|
17913
|
+
function levelFormats(abstractNum) {
|
|
17914
|
+
const formats = /* @__PURE__ */ new Map();
|
|
17915
|
+
for (const lvl of Array.from(abstractNum?.getElementsByTagNameNS?.("*", "lvl") || [])) {
|
|
17916
|
+
const ilvl = getElementId2(lvl, ["w:ilvl", "ilvl"]);
|
|
17917
|
+
const fmt = Array.from(lvl.getElementsByTagNameNS("*", "numFmt"))[0]?.getAttribute("w:val") || null;
|
|
17918
|
+
if (ilvl != null && fmt) formats.set(ilvl, fmt);
|
|
17919
|
+
}
|
|
17920
|
+
return formats;
|
|
17921
|
+
}
|
|
17922
|
+
function normalizeListFormat(format) {
|
|
17923
|
+
return format === "bullet" || format === "circle" || format === "square" ? "bullet" : format;
|
|
17924
|
+
}
|
|
17925
|
+
function abstractNumForNumId(numberingDoc, numId) {
|
|
17926
|
+
const num = Array.from(numberingDoc.getElementsByTagNameNS("*", "num")).find((candidate) => getElementId2(candidate, ["w:numId", "numId"]) === numId);
|
|
17927
|
+
const abstractId = num && getElementId2(Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0], ["w:val", "val"]);
|
|
17928
|
+
if (abstractId == null) return null;
|
|
17929
|
+
return Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum")).find((candidate) => getElementId2(candidate, ["w:abstractNumId", "abstractNumId"]) === abstractId) || null;
|
|
17930
|
+
}
|
|
17931
|
+
function reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, numberingIdState) {
|
|
17932
|
+
const sourceXml = numberingIdState?.sourceNumberingXml;
|
|
17933
|
+
if (!numberingXml || !String(sourceXml || "").trim() || !Array.isArray(replacementNodes)) return null;
|
|
17934
|
+
const sourceNumIds = /* @__PURE__ */ new Set();
|
|
17935
|
+
for (const paragraph of sourceParagraphs || []) {
|
|
17936
|
+
for (const numIdNode of paragraphNumIdNodes(paragraph)) {
|
|
17937
|
+
const id = getElementId2(numIdNode, ["w:val", "val"]);
|
|
17938
|
+
if (id != null && id !== 0) sourceNumIds.add(id);
|
|
17939
|
+
}
|
|
17940
|
+
}
|
|
17941
|
+
if (sourceNumIds.size !== 1) return null;
|
|
17942
|
+
const [sourceNumId] = sourceNumIds;
|
|
17943
|
+
const sourceDoc = parseOoxml(sourceXml);
|
|
17944
|
+
const payloadDoc = parseOoxml(numberingXml);
|
|
17945
|
+
if (hasXmlParseError(sourceDoc) || hasXmlParseError(payloadDoc)) return null;
|
|
17946
|
+
const sourceAbstract = abstractNumForNumId(sourceDoc, sourceNumId);
|
|
17947
|
+
if (!sourceAbstract) return null;
|
|
17948
|
+
const sourceFormats = levelFormats(sourceAbstract);
|
|
17949
|
+
const newParagraphNumIds = [];
|
|
17950
|
+
for (const node of replacementNodes) {
|
|
17951
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17952
|
+
for (const numIdNode of paragraphNumIdNodes(node)) {
|
|
17953
|
+
const ilvlNode = Array.from(numIdNode.parentNode.childNodes || []).find((child) => child.localName === "ilvl");
|
|
17954
|
+
newParagraphNumIds.push({
|
|
17955
|
+
numId: getElementId2(numIdNode, ["w:val", "val"]),
|
|
17956
|
+
ilvl: ilvlNode ? getElementId2(ilvlNode, ["w:val", "val"]) ?? 0 : 0
|
|
17957
|
+
});
|
|
17958
|
+
}
|
|
17959
|
+
}
|
|
17960
|
+
if (newParagraphNumIds.length === 0) return null;
|
|
17961
|
+
const payloadFormats = /* @__PURE__ */ new Map();
|
|
17962
|
+
for (const { numId, ilvl } of newParagraphNumIds) {
|
|
17963
|
+
if (numId == null) return null;
|
|
17964
|
+
if (!payloadFormats.has(numId)) {
|
|
17965
|
+
const abstract = abstractNumForNumId(payloadDoc, numId);
|
|
17966
|
+
if (!abstract) return null;
|
|
17967
|
+
payloadFormats.set(numId, levelFormats(abstract));
|
|
17968
|
+
}
|
|
17969
|
+
const generated = payloadFormats.get(numId).get(ilvl);
|
|
17970
|
+
const existing = sourceFormats.get(ilvl);
|
|
17971
|
+
if (!generated || !existing || normalizeListFormat(generated) !== normalizeListFormat(existing)) return null;
|
|
17972
|
+
}
|
|
17973
|
+
const clonedNodes = replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node);
|
|
17974
|
+
for (const node of clonedNodes) {
|
|
17975
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17976
|
+
for (const numIdNode of paragraphNumIdNodes(node)) setElementVal2(numIdNode, sourceNumId);
|
|
17977
|
+
}
|
|
17978
|
+
return { numId: sourceNumId, replacementNodes: clonedNodes };
|
|
17979
|
+
}
|
|
17867
17980
|
function remapNumberingPayloadForDocument(numberingXml, replacementNodes, numberingIdState) {
|
|
17868
17981
|
const numberingDoc = parseOoxml(numberingXml);
|
|
17869
17982
|
if (hasXmlParseError(numberingDoc)) {
|
|
@@ -17901,6 +18014,7 @@ function remapNumberingPayloadForDocument(numberingXml, replacementNodes, number
|
|
|
17901
18014
|
}
|
|
17902
18015
|
const clonedNodes = Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : [];
|
|
17903
18016
|
for (const node of clonedNodes) {
|
|
18017
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
17904
18018
|
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
17905
18019
|
for (const numIdNode of numIdNodes) {
|
|
17906
18020
|
const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
|
|
@@ -18721,6 +18835,8 @@ function areRevisionTokensEqual(a, b) {
|
|
|
18721
18835
|
var attr2 = (node, name) => node?.getAttribute?.(`w:${name}`) || node?.getAttribute?.(name) || "";
|
|
18722
18836
|
var descendants = (node, name) => Array.from(node?.getElementsByTagNameNS?.(NS_W, name) || []);
|
|
18723
18837
|
var first = (node, name) => descendants(node, name)[0] || null;
|
|
18838
|
+
var directChild2 = (node, name) => Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.localName === name && (!child.namespaceURI || child.namespaceURI === NS_W)) || null;
|
|
18839
|
+
var currentPPr = (paragraph) => directChild2(paragraph, "pPr");
|
|
18724
18840
|
function hasAncestor(node, localName2) {
|
|
18725
18841
|
let cursor = node?.parentNode;
|
|
18726
18842
|
while (cursor) {
|
|
@@ -18736,11 +18852,11 @@ function parseXml2(xml, partName, required = false) {
|
|
|
18736
18852
|
return { doc: parsed.doc, warnings: parsed.warnings || [] };
|
|
18737
18853
|
}
|
|
18738
18854
|
function paragraphListProperties(paragraph) {
|
|
18739
|
-
const numPr =
|
|
18855
|
+
const numPr = directChild2(currentPPr(paragraph), "numPr");
|
|
18740
18856
|
if (!numPr) return null;
|
|
18741
|
-
const numId = attr2(
|
|
18857
|
+
const numId = attr2(directChild2(numPr, "numId"), "val");
|
|
18742
18858
|
if (!numId || numId === "0") return null;
|
|
18743
|
-
return { numId, level: Number.parseInt(attr2(
|
|
18859
|
+
return { numId, level: Number.parseInt(attr2(directChild2(numPr, "ilvl"), "val") || "0", 10) || 0 };
|
|
18744
18860
|
}
|
|
18745
18861
|
function parseNumbering(numberingDoc) {
|
|
18746
18862
|
const abstracts = /* @__PURE__ */ new Map();
|
|
@@ -18825,11 +18941,11 @@ function createNumberingResolver(numberingDoc) {
|
|
|
18825
18941
|
};
|
|
18826
18942
|
}
|
|
18827
18943
|
function headingLevel(paragraph) {
|
|
18828
|
-
const pPr =
|
|
18829
|
-
const style = attr2(
|
|
18944
|
+
const pPr = currentPPr(paragraph);
|
|
18945
|
+
const style = attr2(directChild2(pPr, "pStyle"), "val");
|
|
18830
18946
|
const match = style.match(/^heading\s*([1-9])$/i);
|
|
18831
18947
|
if (match) return Math.min(Number(match[1]), 6);
|
|
18832
|
-
const outline = Number.parseInt(attr2(
|
|
18948
|
+
const outline = Number.parseInt(attr2(directChild2(pPr, "outlineLvl"), "val"), 10);
|
|
18833
18949
|
return Number.isInteger(outline) ? Math.min(outline + 1, 6) : null;
|
|
18834
18950
|
}
|
|
18835
18951
|
function structuralContext(paragraph, text2) {
|
|
@@ -19008,7 +19124,7 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
19008
19124
|
const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
|
|
19009
19125
|
const authors = revisionAuthors(paragraph);
|
|
19010
19126
|
const list = resolveNumbering(paragraphListProperties(paragraph));
|
|
19011
|
-
const styleId = attr2(
|
|
19127
|
+
const styleId = attr2(directChild2(currentPPr(paragraph), "pStyle"), "val") || null;
|
|
19012
19128
|
const structure = structuralContext(paragraph, text2);
|
|
19013
19129
|
const index = zeroIndex + 1;
|
|
19014
19130
|
const provision = list?.label && list.format !== "bullet" ? list.label : null;
|
|
@@ -24409,7 +24525,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24409
24525
|
paragraphText,
|
|
24410
24526
|
generateRedlines ? createRevisionMetadata(author, xmlDoc) : null,
|
|
24411
24527
|
author,
|
|
24412
|
-
|
|
24528
|
+
// A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
|
|
24529
|
+
// Reject All removes the text but leaves an empty paragraph behind.
|
|
24530
|
+
{ generateRedlines, trackParagraphMark: true }
|
|
24413
24531
|
);
|
|
24414
24532
|
const imported = xmlDoc.importNode(plainParagraph, true);
|
|
24415
24533
|
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
@@ -24540,6 +24658,14 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
24540
24658
|
const extracted = extractReplacementNodes(result.oxml);
|
|
24541
24659
|
let replacementNodes = removeListPackagingSentinel(extracted.replacementNodes, result.warnings);
|
|
24542
24660
|
let numberingXml = extracted.numberingXml;
|
|
24661
|
+
if (numberingXml && runtimeContext?.numberingIdState && !useTableScope) {
|
|
24662
|
+
const sourceParagraphs = useListScope ? listScopeEdit.paragraphs : explicitRangeParagraphs || [targetParagraph];
|
|
24663
|
+
const continued = reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, runtimeContext.numberingIdState);
|
|
24664
|
+
if (continued) {
|
|
24665
|
+
replacementNodes = continued.replacementNodes;
|
|
24666
|
+
numberingXml = null;
|
|
24667
|
+
}
|
|
24668
|
+
}
|
|
24543
24669
|
if (numberingXml && runtimeContext?.numberingIdState) {
|
|
24544
24670
|
const normalizedNumbering = remapNumberingPayloadForDocument(numberingXml, replacementNodes, runtimeContext.numberingIdState);
|
|
24545
24671
|
replacementNodes = normalizedNumbering.replacementNodes;
|