@ansonlai/docx-redline-js 0.8.1 → 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 -539
- package/README.md +901 -890
- package/core/list-targeting.js +12 -10
- package/dist/docx-redline-js.esm.js +280 -33
- 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 +280 -33
- package/dist/docx-redline.bundle.cjs.map +3 -3
- package/dist/docx-redline.bundle.js +280 -33
- package/dist/docx-redline.bundle.js.map +3 -3
- package/engine/reconstruction-mode.js +183 -54
- package/orchestration/list-structural-fallback.js +530 -518
- package/package.json +1 -1
- package/pipeline/diff-engine.js +274 -258
- package/pipeline/list-generation.js +203 -168
- package/scripts/run-tests.mjs +14 -2
- package/services/document-inspection.js +10 -7
- package/services/document-operation-mutations.js +1597 -1586
- package/services/numbering-helpers.js +108 -0
package/core/list-targeting.js
CHANGED
|
@@ -9,12 +9,13 @@ import {
|
|
|
9
9
|
} from './paragraph-targeting.js';
|
|
10
10
|
import { parseListItem, stripListMarker } from '../pipeline/list-markers.js';
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
/** Direct element child by local name; never crosses w:pPrChange / w:rPrChange boundaries. */
|
|
13
|
+
function getDirectChildByLocalName(node, localName) {
|
|
14
|
+
if (!node) return null;
|
|
15
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
16
|
+
if (child?.nodeType === 1 && child.localName === localName) return child;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
function readValAttribute(element) {
|
|
@@ -178,17 +179,18 @@ function buildListEntriesForInsertion(parsedItems, normalizedTargetText, anchorL
|
|
|
178
179
|
export function getParagraphListInfo(paragraph) {
|
|
179
180
|
if (!paragraph) return null;
|
|
180
181
|
|
|
181
|
-
|
|
182
|
+
// Current properties only: direct w:pPr/w:numPr, never historical w:pPrChange/w:pPr.
|
|
183
|
+
const pPr = getDirectChildByLocalName(paragraph, 'pPr');
|
|
182
184
|
if (!pPr) return null;
|
|
183
|
-
const numPr =
|
|
185
|
+
const numPr = getDirectChildByLocalName(pPr, 'numPr');
|
|
184
186
|
if (!numPr) return null;
|
|
185
|
-
const numIdEl =
|
|
187
|
+
const numIdEl = getDirectChildByLocalName(numPr, 'numId');
|
|
186
188
|
if (!numIdEl) return null;
|
|
187
189
|
|
|
188
190
|
const numId = readValAttribute(numIdEl);
|
|
189
191
|
if (!numId || numId === '0') return null;
|
|
190
192
|
|
|
191
|
-
const ilvlEl =
|
|
193
|
+
const ilvlEl = getDirectChildByLocalName(numPr, 'ilvl');
|
|
192
194
|
const ilvlRaw = readValAttribute(ilvlEl);
|
|
193
195
|
const ilvl = Number.parseInt(ilvlRaw || '0', 10);
|
|
194
196
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js 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;
|
|
@@ -2522,15 +2522,29 @@ function createDiffEngine(options = {}) {
|
|
|
2522
2522
|
engine.Diff_Timeout = timeout;
|
|
2523
2523
|
return engine;
|
|
2524
2524
|
}
|
|
2525
|
-
function tokenize(text2) {
|
|
2525
|
+
function tokenize(text2, atomicChars = null) {
|
|
2526
2526
|
const tokens = [];
|
|
2527
|
+
const pushWord = (word) => {
|
|
2528
|
+
if (!atomicChars || atomicChars.size === 0) {
|
|
2529
|
+
tokens.push(word);
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
let start = 0;
|
|
2533
|
+
for (let index = 0; index < word.length; index++) {
|
|
2534
|
+
if (!atomicChars.has(word[index])) continue;
|
|
2535
|
+
if (index > start) tokens.push(word.slice(start, index));
|
|
2536
|
+
tokens.push(word[index]);
|
|
2537
|
+
start = index + 1;
|
|
2538
|
+
}
|
|
2539
|
+
if (start < word.length) tokens.push(word.slice(start));
|
|
2540
|
+
};
|
|
2527
2541
|
const leading = text2.match(/^\s+/);
|
|
2528
2542
|
if (leading) tokens.push(leading[0]);
|
|
2529
2543
|
const regex = /(\S+)(\s*)/g;
|
|
2530
2544
|
regex.lastIndex = leading?.[0].length || 0;
|
|
2531
2545
|
let match;
|
|
2532
2546
|
while ((match = regex.exec(text2)) !== null) {
|
|
2533
|
-
if (match[1])
|
|
2547
|
+
if (match[1]) pushWord(match[1]);
|
|
2534
2548
|
if (match[2]) tokens.push(match[2]);
|
|
2535
2549
|
}
|
|
2536
2550
|
return tokens;
|
|
@@ -2558,8 +2572,8 @@ function wordsToChars(text1, text2, options = {}) {
|
|
|
2558
2572
|
}
|
|
2559
2573
|
return { chars, tokenIds };
|
|
2560
2574
|
}
|
|
2561
|
-
const tokens1 = tokenize(text1);
|
|
2562
|
-
const tokens2 = tokenize(text2);
|
|
2575
|
+
const tokens1 = tokenize(text1, options.atomicChars);
|
|
2576
|
+
const tokens2 = tokenize(text2, options.atomicChars);
|
|
2563
2577
|
const encoded1 = mapTokensToChars(tokens1);
|
|
2564
2578
|
const encoded2 = mapTokensToChars(tokens2);
|
|
2565
2579
|
return {
|
|
@@ -3959,7 +3973,37 @@ async function executeListGeneration(options) {
|
|
|
3959
3973
|
}];
|
|
3960
3974
|
}
|
|
3961
3975
|
}
|
|
3962
|
-
|
|
3976
|
+
const sourceParagraphCount = (originalRunModel || []).filter((run) => run.kind === RunKind.PARAGRAPH_START).length;
|
|
3977
|
+
if (generateRedlines && sourceParagraphCount > 0 && (sourceParagraphCount > 1 || deletionRuns.length === 0)) {
|
|
3978
|
+
let segment = null;
|
|
3979
|
+
const flushSegment = () => {
|
|
3980
|
+
if (!segment) return;
|
|
3981
|
+
results.push(serializeToOoxml(segment, null, [], {
|
|
3982
|
+
author,
|
|
3983
|
+
generateRedlines,
|
|
3984
|
+
revisionIdAllocator
|
|
3985
|
+
}));
|
|
3986
|
+
segment = null;
|
|
3987
|
+
};
|
|
3988
|
+
for (const run of originalRunModel) {
|
|
3989
|
+
if (run.kind === RunKind.PARAGRAPH_START) {
|
|
3990
|
+
flushSegment();
|
|
3991
|
+
segment = [{
|
|
3992
|
+
...run,
|
|
3993
|
+
pPrElement: null,
|
|
3994
|
+
pPrXml: addParagraphMarkRevision(
|
|
3995
|
+
run.pPrElement || run.pPrXml || null,
|
|
3996
|
+
"del",
|
|
3997
|
+
author,
|
|
3998
|
+
revisionIdAllocator
|
|
3999
|
+
)
|
|
4000
|
+
}];
|
|
4001
|
+
} else if (segment && (run.kind === "text" || run.kind === "run")) {
|
|
4002
|
+
segment.push({ ...run, kind: "deletion", author });
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
flushSegment();
|
|
4006
|
+
} else if (generateRedlines && deletionRuns.length > 0) {
|
|
3963
4007
|
const deletedPPr = addParagraphMarkRevision(sourcePPr, "del", author, revisionIdAllocator);
|
|
3964
4008
|
const deletedParagraph = serializeToOoxml(deletionRuns, deletedPPr, [], {
|
|
3965
4009
|
author,
|
|
@@ -7964,7 +8008,11 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
7964
8008
|
if (mapping.paragraphs.length === 0) {
|
|
7965
8009
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
7966
8010
|
}
|
|
7967
|
-
const
|
|
8011
|
+
const wordDiffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText, {
|
|
8012
|
+
...diffOptions,
|
|
8013
|
+
atomicChars: new Set(mapping.referenceMap.keys())
|
|
8014
|
+
});
|
|
8015
|
+
const diffs = keepUnchangedTextOutsideWrappers(anchorSharedReferences(wordDiffs, mapping.referenceMap), mapping);
|
|
7968
8016
|
return withOoxmlSourceType(applyReconstructionDiffs(
|
|
7969
8017
|
xmlDoc,
|
|
7970
8018
|
diffs,
|
|
@@ -7976,6 +8024,109 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
7976
8024
|
options
|
|
7977
8025
|
));
|
|
7978
8026
|
}
|
|
8027
|
+
function forEachChangeGroup(diffs, onEqual, onGroup) {
|
|
8028
|
+
let index = 0;
|
|
8029
|
+
while (index < diffs.length) {
|
|
8030
|
+
if (diffs[index][0] === 0) {
|
|
8031
|
+
onEqual(diffs[index]);
|
|
8032
|
+
index++;
|
|
8033
|
+
continue;
|
|
8034
|
+
}
|
|
8035
|
+
const group = [];
|
|
8036
|
+
while (index < diffs.length && diffs[index][0] !== 0) group.push(diffs[index++]);
|
|
8037
|
+
onGroup(
|
|
8038
|
+
group,
|
|
8039
|
+
group.filter(([op]) => op === -1).map(([, text2]) => text2).join(""),
|
|
8040
|
+
group.filter(([op]) => op === 1).map(([, text2]) => text2).join("")
|
|
8041
|
+
);
|
|
8042
|
+
}
|
|
8043
|
+
}
|
|
8044
|
+
function pushChange(result, deleted, inserted) {
|
|
8045
|
+
if (deleted === inserted) {
|
|
8046
|
+
if (deleted) result.push([0, deleted]);
|
|
8047
|
+
return;
|
|
8048
|
+
}
|
|
8049
|
+
if (deleted) result.push([-1, deleted]);
|
|
8050
|
+
if (inserted) result.push([1, inserted]);
|
|
8051
|
+
}
|
|
8052
|
+
function anchorSharedReferences(diffs, referenceMap) {
|
|
8053
|
+
if (referenceMap.size === 0) return diffs;
|
|
8054
|
+
const result = [];
|
|
8055
|
+
forEachChangeGroup(diffs, (part) => result.push(part), (group, deleted, inserted) => {
|
|
8056
|
+
const deletedRefs = [...deleted].filter((char) => referenceMap.has(char) && inserted.includes(char));
|
|
8057
|
+
const insertedRefs = [...inserted].filter((char) => deletedRefs.includes(char));
|
|
8058
|
+
if (deletedRefs.length === 0 || deletedRefs.join("") !== insertedRefs.join("")) {
|
|
8059
|
+
result.push(...group);
|
|
8060
|
+
return;
|
|
8061
|
+
}
|
|
8062
|
+
let deletedStart = 0;
|
|
8063
|
+
let insertedStart = 0;
|
|
8064
|
+
for (const ref of deletedRefs) {
|
|
8065
|
+
const deletedAt = deleted.indexOf(ref, deletedStart);
|
|
8066
|
+
const insertedAt = inserted.indexOf(ref, insertedStart);
|
|
8067
|
+
pushChange(result, deleted.slice(deletedStart, deletedAt), inserted.slice(insertedStart, insertedAt));
|
|
8068
|
+
result.push([0, ref]);
|
|
8069
|
+
deletedStart = deletedAt + 1;
|
|
8070
|
+
insertedStart = insertedAt + 1;
|
|
8071
|
+
}
|
|
8072
|
+
pushChange(result, deleted.slice(deletedStart), inserted.slice(insertedStart));
|
|
8073
|
+
});
|
|
8074
|
+
return result;
|
|
8075
|
+
}
|
|
8076
|
+
function commonPrefixLength(left, right) {
|
|
8077
|
+
const limit = Math.min(left.length, right.length);
|
|
8078
|
+
let length = 0;
|
|
8079
|
+
while (length < limit && left[length] === right[length]) length++;
|
|
8080
|
+
return length;
|
|
8081
|
+
}
|
|
8082
|
+
function commonSuffixLength(left, right, limit) {
|
|
8083
|
+
let length = 0;
|
|
8084
|
+
while (length < limit && left[left.length - 1 - length] === right[right.length - 1 - length]) length++;
|
|
8085
|
+
return length;
|
|
8086
|
+
}
|
|
8087
|
+
function keepUnchangedTextOutsideWrappers(diffs, mapping) {
|
|
8088
|
+
const wrapperAt = (index) => mapping.getRunProperties(index).wrapper || null;
|
|
8089
|
+
const result = [];
|
|
8090
|
+
let originalOffset = 0;
|
|
8091
|
+
forEachChangeGroup(diffs, (part) => {
|
|
8092
|
+
result.push(part);
|
|
8093
|
+
originalOffset += part[1].length;
|
|
8094
|
+
}, (group, deleted, inserted) => {
|
|
8095
|
+
const start = originalOffset;
|
|
8096
|
+
const end = start + deleted.length;
|
|
8097
|
+
originalOffset = end;
|
|
8098
|
+
const isWrapperBoundary = (index) => index > start && index < end && wrapperAt(index - 1) !== wrapperAt(index);
|
|
8099
|
+
let prefix = 0;
|
|
8100
|
+
let suffix = 0;
|
|
8101
|
+
if (deleted && inserted) {
|
|
8102
|
+
const sharedSuffix = commonSuffixLength(deleted, inserted, Math.min(deleted.length, inserted.length));
|
|
8103
|
+
for (let length = sharedSuffix; length > 0; length--) {
|
|
8104
|
+
if (isWrapperBoundary(end - length)) {
|
|
8105
|
+
suffix = length;
|
|
8106
|
+
break;
|
|
8107
|
+
}
|
|
8108
|
+
}
|
|
8109
|
+
const sharedPrefix = Math.min(
|
|
8110
|
+
commonPrefixLength(deleted, inserted),
|
|
8111
|
+
Math.min(deleted.length, inserted.length) - suffix
|
|
8112
|
+
);
|
|
8113
|
+
for (let length = sharedPrefix; length > 0; length--) {
|
|
8114
|
+
if (isWrapperBoundary(start + length)) {
|
|
8115
|
+
prefix = length;
|
|
8116
|
+
break;
|
|
8117
|
+
}
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
8120
|
+
if (prefix === 0 && suffix === 0) {
|
|
8121
|
+
result.push(...group);
|
|
8122
|
+
return;
|
|
8123
|
+
}
|
|
8124
|
+
if (prefix > 0) result.push([0, deleted.slice(0, prefix)]);
|
|
8125
|
+
pushChange(result, deleted.slice(prefix, deleted.length - suffix), inserted.slice(prefix, inserted.length - suffix));
|
|
8126
|
+
if (suffix > 0) result.push([0, deleted.slice(deleted.length - suffix)]);
|
|
8127
|
+
});
|
|
8128
|
+
return result;
|
|
8129
|
+
}
|
|
7979
8130
|
|
|
7980
8131
|
// engine/table-mode.js
|
|
7981
8132
|
function noChanges(serializer, xmlDoc) {
|
|
@@ -10050,12 +10201,12 @@ function validateParagraphBoundaryMutation(targetParagraph, modifiedText, option
|
|
|
10050
10201
|
}
|
|
10051
10202
|
|
|
10052
10203
|
// core/list-targeting.js
|
|
10053
|
-
function
|
|
10054
|
-
if (!node
|
|
10055
|
-
const
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
return
|
|
10204
|
+
function getDirectChildByLocalName(node, localName2) {
|
|
10205
|
+
if (!node) return null;
|
|
10206
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
10207
|
+
if (child?.nodeType === 1 && child.localName === localName2) return child;
|
|
10208
|
+
}
|
|
10209
|
+
return null;
|
|
10059
10210
|
}
|
|
10060
10211
|
function readValAttribute(element) {
|
|
10061
10212
|
if (!element) return null;
|
|
@@ -10172,15 +10323,15 @@ function buildListEntriesForInsertion(parsedItems, normalizedTargetText, anchorL
|
|
|
10172
10323
|
}
|
|
10173
10324
|
function getParagraphListInfo(paragraph) {
|
|
10174
10325
|
if (!paragraph) return null;
|
|
10175
|
-
const pPr =
|
|
10326
|
+
const pPr = getDirectChildByLocalName(paragraph, "pPr");
|
|
10176
10327
|
if (!pPr) return null;
|
|
10177
|
-
const numPr =
|
|
10328
|
+
const numPr = getDirectChildByLocalName(pPr, "numPr");
|
|
10178
10329
|
if (!numPr) return null;
|
|
10179
|
-
const numIdEl =
|
|
10330
|
+
const numIdEl = getDirectChildByLocalName(numPr, "numId");
|
|
10180
10331
|
if (!numIdEl) return null;
|
|
10181
10332
|
const numId = readValAttribute(numIdEl);
|
|
10182
10333
|
if (!numId || numId === "0") return null;
|
|
10183
|
-
const ilvlEl =
|
|
10334
|
+
const ilvlEl = getDirectChildByLocalName(numPr, "ilvl");
|
|
10184
10335
|
const ilvlRaw = readValAttribute(ilvlEl);
|
|
10185
10336
|
const ilvl = Number.parseInt(ilvlRaw || "0", 10);
|
|
10186
10337
|
return {
|
|
@@ -10590,8 +10741,13 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
|
|
|
10590
10741
|
}
|
|
10591
10742
|
return updated;
|
|
10592
10743
|
}
|
|
10744
|
+
function parseOxmlFragment(oxml) {
|
|
10745
|
+
const text2 = String(oxml || "");
|
|
10746
|
+
const declared = /xmlns:w\s*=/.test(text2);
|
|
10747
|
+
return parseOoxmlSafe(declared ? text2 : `<w:root xmlns:w="${NS_W}">${text2}</w:root>`, "application/xml").doc;
|
|
10748
|
+
}
|
|
10593
10749
|
function getFirstParagraphFromOxml(oxml) {
|
|
10594
|
-
const doc =
|
|
10750
|
+
const doc = parseOxmlFragment(oxml);
|
|
10595
10751
|
if (!doc) return null;
|
|
10596
10752
|
const parseError = getXmlParseError(doc);
|
|
10597
10753
|
if (parseError) return null;
|
|
@@ -10618,17 +10774,17 @@ function setElementVal(element, value) {
|
|
|
10618
10774
|
element.setAttribute("w:val", String(value));
|
|
10619
10775
|
}
|
|
10620
10776
|
function extractFirstParagraphNumIdFromOxml(oxml) {
|
|
10621
|
-
const doc =
|
|
10777
|
+
const doc = parseOxmlFragment(oxml);
|
|
10622
10778
|
if (!doc) return null;
|
|
10623
10779
|
const parseError = getXmlParseError(doc);
|
|
10624
10780
|
if (parseError) return null;
|
|
10625
10781
|
const paragraphs = getDocumentParagraphNodes(doc);
|
|
10626
|
-
const
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10782
|
+
for (const paragraph of paragraphs) {
|
|
10783
|
+
const numIdNodes = Array.from(paragraph.getElementsByTagNameNS("*", "numId"));
|
|
10784
|
+
for (const numIdNode of numIdNodes) {
|
|
10785
|
+
const numId = getElementId(numIdNode, ["w:val", "val"]);
|
|
10786
|
+
if (numId != null) return String(numId);
|
|
10787
|
+
}
|
|
10632
10788
|
}
|
|
10633
10789
|
return null;
|
|
10634
10790
|
}
|
|
@@ -11124,6 +11280,8 @@ function createDynamicNumberingIdState(numberingXml, options = {}) {
|
|
|
11124
11280
|
const baseNumId = Math.max(minId, maxUsedNumId + 1);
|
|
11125
11281
|
const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
|
|
11126
11282
|
return {
|
|
11283
|
+
// Kept so list edits can recognise which existing definition a source list uses and continue it.
|
|
11284
|
+
sourceNumberingXml: String(numberingXml || ""),
|
|
11127
11285
|
nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
|
|
11128
11286
|
nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
|
|
11129
11287
|
usedNumIds,
|
|
@@ -11264,6 +11422,82 @@ function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startA
|
|
|
11264
11422
|
</w:num>
|
|
11265
11423
|
</w:numbering>`.trim();
|
|
11266
11424
|
}
|
|
11425
|
+
function isTrackedDeletedParagraph(node) {
|
|
11426
|
+
if (!node || node.nodeType !== 1 || node.localName !== "p") return false;
|
|
11427
|
+
const pPr = Array.from(node.childNodes || []).find((child) => isDirectWordChild(child, "pPr"));
|
|
11428
|
+
const rPr = pPr && Array.from(pPr.childNodes || []).find((child) => isDirectWordChild(child, "rPr"));
|
|
11429
|
+
return !!rPr && Array.from(rPr.childNodes || []).some((child) => isDirectWordChild(child, "del"));
|
|
11430
|
+
}
|
|
11431
|
+
function paragraphNumIdNodes(node) {
|
|
11432
|
+
return Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []).filter((numIdNode) => numIdNode.parentNode?.localName === "numPr");
|
|
11433
|
+
}
|
|
11434
|
+
function levelFormats(abstractNum) {
|
|
11435
|
+
const formats = /* @__PURE__ */ new Map();
|
|
11436
|
+
for (const lvl of Array.from(abstractNum?.getElementsByTagNameNS?.("*", "lvl") || [])) {
|
|
11437
|
+
const ilvl = getElementId2(lvl, ["w:ilvl", "ilvl"]);
|
|
11438
|
+
const fmt = Array.from(lvl.getElementsByTagNameNS("*", "numFmt"))[0]?.getAttribute("w:val") || null;
|
|
11439
|
+
if (ilvl != null && fmt) formats.set(ilvl, fmt);
|
|
11440
|
+
}
|
|
11441
|
+
return formats;
|
|
11442
|
+
}
|
|
11443
|
+
function normalizeListFormat(format) {
|
|
11444
|
+
return format === "bullet" || format === "circle" || format === "square" ? "bullet" : format;
|
|
11445
|
+
}
|
|
11446
|
+
function abstractNumForNumId(numberingDoc, numId) {
|
|
11447
|
+
const num = Array.from(numberingDoc.getElementsByTagNameNS("*", "num")).find((candidate) => getElementId2(candidate, ["w:numId", "numId"]) === numId);
|
|
11448
|
+
const abstractId = num && getElementId2(Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0], ["w:val", "val"]);
|
|
11449
|
+
if (abstractId == null) return null;
|
|
11450
|
+
return Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum")).find((candidate) => getElementId2(candidate, ["w:abstractNumId", "abstractNumId"]) === abstractId) || null;
|
|
11451
|
+
}
|
|
11452
|
+
function reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, numberingIdState) {
|
|
11453
|
+
const sourceXml = numberingIdState?.sourceNumberingXml;
|
|
11454
|
+
if (!numberingXml || !String(sourceXml || "").trim() || !Array.isArray(replacementNodes)) return null;
|
|
11455
|
+
const sourceNumIds = /* @__PURE__ */ new Set();
|
|
11456
|
+
for (const paragraph of sourceParagraphs || []) {
|
|
11457
|
+
for (const numIdNode of paragraphNumIdNodes(paragraph)) {
|
|
11458
|
+
const id = getElementId2(numIdNode, ["w:val", "val"]);
|
|
11459
|
+
if (id != null && id !== 0) sourceNumIds.add(id);
|
|
11460
|
+
}
|
|
11461
|
+
}
|
|
11462
|
+
if (sourceNumIds.size !== 1) return null;
|
|
11463
|
+
const [sourceNumId] = sourceNumIds;
|
|
11464
|
+
const sourceDoc = parseOoxml(sourceXml);
|
|
11465
|
+
const payloadDoc = parseOoxml(numberingXml);
|
|
11466
|
+
if (hasXmlParseError(sourceDoc) || hasXmlParseError(payloadDoc)) return null;
|
|
11467
|
+
const sourceAbstract = abstractNumForNumId(sourceDoc, sourceNumId);
|
|
11468
|
+
if (!sourceAbstract) return null;
|
|
11469
|
+
const sourceFormats = levelFormats(sourceAbstract);
|
|
11470
|
+
const newParagraphNumIds = [];
|
|
11471
|
+
for (const node of replacementNodes) {
|
|
11472
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
11473
|
+
for (const numIdNode of paragraphNumIdNodes(node)) {
|
|
11474
|
+
const ilvlNode = Array.from(numIdNode.parentNode.childNodes || []).find((child) => child.localName === "ilvl");
|
|
11475
|
+
newParagraphNumIds.push({
|
|
11476
|
+
numId: getElementId2(numIdNode, ["w:val", "val"]),
|
|
11477
|
+
ilvl: ilvlNode ? getElementId2(ilvlNode, ["w:val", "val"]) ?? 0 : 0
|
|
11478
|
+
});
|
|
11479
|
+
}
|
|
11480
|
+
}
|
|
11481
|
+
if (newParagraphNumIds.length === 0) return null;
|
|
11482
|
+
const payloadFormats = /* @__PURE__ */ new Map();
|
|
11483
|
+
for (const { numId, ilvl } of newParagraphNumIds) {
|
|
11484
|
+
if (numId == null) return null;
|
|
11485
|
+
if (!payloadFormats.has(numId)) {
|
|
11486
|
+
const abstract = abstractNumForNumId(payloadDoc, numId);
|
|
11487
|
+
if (!abstract) return null;
|
|
11488
|
+
payloadFormats.set(numId, levelFormats(abstract));
|
|
11489
|
+
}
|
|
11490
|
+
const generated = payloadFormats.get(numId).get(ilvl);
|
|
11491
|
+
const existing = sourceFormats.get(ilvl);
|
|
11492
|
+
if (!generated || !existing || normalizeListFormat(generated) !== normalizeListFormat(existing)) return null;
|
|
11493
|
+
}
|
|
11494
|
+
const clonedNodes = replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node);
|
|
11495
|
+
for (const node of clonedNodes) {
|
|
11496
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
11497
|
+
for (const numIdNode of paragraphNumIdNodes(node)) setElementVal2(numIdNode, sourceNumId);
|
|
11498
|
+
}
|
|
11499
|
+
return { numId: sourceNumId, replacementNodes: clonedNodes };
|
|
11500
|
+
}
|
|
11267
11501
|
function remapNumberingPayloadForDocument(numberingXml, replacementNodes, numberingIdState) {
|
|
11268
11502
|
const numberingDoc = parseOoxml(numberingXml);
|
|
11269
11503
|
if (hasXmlParseError(numberingDoc)) {
|
|
@@ -11301,6 +11535,7 @@ function remapNumberingPayloadForDocument(numberingXml, replacementNodes, number
|
|
|
11301
11535
|
}
|
|
11302
11536
|
const clonedNodes = Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : [];
|
|
11303
11537
|
for (const node of clonedNodes) {
|
|
11538
|
+
if (isTrackedDeletedParagraph(node)) continue;
|
|
11304
11539
|
const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
|
|
11305
11540
|
for (const numIdNode of numIdNodes) {
|
|
11306
11541
|
const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
|
|
@@ -12121,6 +12356,8 @@ function areRevisionTokensEqual(a, b) {
|
|
|
12121
12356
|
var attr2 = (node, name) => node?.getAttribute?.(`w:${name}`) || node?.getAttribute?.(name) || "";
|
|
12122
12357
|
var descendants = (node, name) => Array.from(node?.getElementsByTagNameNS?.(NS_W, name) || []);
|
|
12123
12358
|
var first = (node, name) => descendants(node, name)[0] || null;
|
|
12359
|
+
var directChild2 = (node, name) => Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.localName === name && (!child.namespaceURI || child.namespaceURI === NS_W)) || null;
|
|
12360
|
+
var currentPPr = (paragraph) => directChild2(paragraph, "pPr");
|
|
12124
12361
|
function hasAncestor(node, localName2) {
|
|
12125
12362
|
let cursor = node?.parentNode;
|
|
12126
12363
|
while (cursor) {
|
|
@@ -12136,11 +12373,11 @@ function parseXml2(xml, partName, required = false) {
|
|
|
12136
12373
|
return { doc: parsed.doc, warnings: parsed.warnings || [] };
|
|
12137
12374
|
}
|
|
12138
12375
|
function paragraphListProperties(paragraph) {
|
|
12139
|
-
const numPr =
|
|
12376
|
+
const numPr = directChild2(currentPPr(paragraph), "numPr");
|
|
12140
12377
|
if (!numPr) return null;
|
|
12141
|
-
const numId = attr2(
|
|
12378
|
+
const numId = attr2(directChild2(numPr, "numId"), "val");
|
|
12142
12379
|
if (!numId || numId === "0") return null;
|
|
12143
|
-
return { numId, level: Number.parseInt(attr2(
|
|
12380
|
+
return { numId, level: Number.parseInt(attr2(directChild2(numPr, "ilvl"), "val") || "0", 10) || 0 };
|
|
12144
12381
|
}
|
|
12145
12382
|
function parseNumbering(numberingDoc) {
|
|
12146
12383
|
const abstracts = /* @__PURE__ */ new Map();
|
|
@@ -12225,11 +12462,11 @@ function createNumberingResolver(numberingDoc) {
|
|
|
12225
12462
|
};
|
|
12226
12463
|
}
|
|
12227
12464
|
function headingLevel(paragraph) {
|
|
12228
|
-
const pPr =
|
|
12229
|
-
const style = attr2(
|
|
12465
|
+
const pPr = currentPPr(paragraph);
|
|
12466
|
+
const style = attr2(directChild2(pPr, "pStyle"), "val");
|
|
12230
12467
|
const match = style.match(/^heading\s*([1-9])$/i);
|
|
12231
12468
|
if (match) return Math.min(Number(match[1]), 6);
|
|
12232
|
-
const outline = Number.parseInt(attr2(
|
|
12469
|
+
const outline = Number.parseInt(attr2(directChild2(pPr, "outlineLvl"), "val"), 10);
|
|
12233
12470
|
return Number.isInteger(outline) ? Math.min(outline + 1, 6) : null;
|
|
12234
12471
|
}
|
|
12235
12472
|
function structuralContext(paragraph, text2) {
|
|
@@ -12408,7 +12645,7 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
12408
12645
|
const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
|
|
12409
12646
|
const authors = revisionAuthors(paragraph);
|
|
12410
12647
|
const list = resolveNumbering(paragraphListProperties(paragraph));
|
|
12411
|
-
const styleId = attr2(
|
|
12648
|
+
const styleId = attr2(directChild2(currentPPr(paragraph), "pStyle"), "val") || null;
|
|
12412
12649
|
const structure = structuralContext(paragraph, text2);
|
|
12413
12650
|
const index = zeroIndex + 1;
|
|
12414
12651
|
const provision = list?.label && list.format !== "bullet" ? list.label : null;
|
|
@@ -17809,7 +18046,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
17809
18046
|
paragraphText,
|
|
17810
18047
|
generateRedlines ? createRevisionMetadata(author, xmlDoc) : null,
|
|
17811
18048
|
author,
|
|
17812
|
-
|
|
18049
|
+
// A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
|
|
18050
|
+
// Reject All removes the text but leaves an empty paragraph behind.
|
|
18051
|
+
{ generateRedlines, trackParagraphMark: true }
|
|
17813
18052
|
);
|
|
17814
18053
|
const imported = xmlDoc.importNode(plainParagraph, true);
|
|
17815
18054
|
ensureParagraphIdsOnImportedNode(imported, operationSession);
|
|
@@ -17940,6 +18179,14 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
17940
18179
|
const extracted = extractReplacementNodes(result.oxml);
|
|
17941
18180
|
let replacementNodes = removeListPackagingSentinel(extracted.replacementNodes, result.warnings);
|
|
17942
18181
|
let numberingXml = extracted.numberingXml;
|
|
18182
|
+
if (numberingXml && runtimeContext?.numberingIdState && !useTableScope) {
|
|
18183
|
+
const sourceParagraphs = useListScope ? listScopeEdit.paragraphs : explicitRangeParagraphs || [targetParagraph];
|
|
18184
|
+
const continued = reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, runtimeContext.numberingIdState);
|
|
18185
|
+
if (continued) {
|
|
18186
|
+
replacementNodes = continued.replacementNodes;
|
|
18187
|
+
numberingXml = null;
|
|
18188
|
+
}
|
|
18189
|
+
}
|
|
17943
18190
|
if (numberingXml && runtimeContext?.numberingIdState) {
|
|
17944
18191
|
const normalizedNumbering = remapNumberingPayloadForDocument(numberingXml, replacementNodes, runtimeContext.numberingIdState);
|
|
17945
18192
|
replacementNodes = normalizedNumbering.replacementNodes;
|