@usejunior/docx-core 0.15.0 → 0.17.0
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/README.md +18 -83
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/conformance-adapter.d.ts +14 -0
- package/dist/cli/conformance-adapter.d.ts.map +1 -1
- package/dist/cli/conformance-adapter.js +104 -12
- package/dist/cli/conformance-adapter.js.map +1 -1
- package/dist/footnotes.d.ts +7 -5
- package/dist/footnotes.d.ts.map +1 -1
- package/dist/footnotes.js +7 -5
- package/dist/footnotes.js.map +1 -1
- package/dist/generated/ecma-376-vocabulary.d.ts +93 -0
- package/dist/generated/ecma-376-vocabulary.d.ts.map +1 -0
- package/dist/generated/ecma-376-vocabulary.js +87 -0
- package/dist/generated/ecma-376-vocabulary.js.map +1 -0
- package/dist/generation/compile.js +2 -2
- package/dist/generation/compile.js.map +1 -1
- package/dist/generation/emit/comments-part.d.ts +2 -0
- package/dist/generation/emit/comments-part.d.ts.map +1 -1
- package/dist/generation/emit/comments-part.js +2 -0
- package/dist/generation/emit/comments-part.js.map +1 -1
- package/dist/generation/emit/paragraph.d.ts +3 -0
- package/dist/generation/emit/paragraph.d.ts.map +1 -1
- package/dist/generation/emit/paragraph.js +3 -0
- package/dist/generation/emit/paragraph.js.map +1 -1
- package/dist/generation/emit/run.d.ts.map +1 -1
- package/dist/generation/emit/run.js +6 -1
- package/dist/generation/emit/run.js.map +1 -1
- package/dist/generation/emit/settings-part.d.ts +12 -3
- package/dist/generation/emit/settings-part.d.ts.map +1 -1
- package/dist/generation/emit/settings-part.js +23 -5
- package/dist/generation/emit/settings-part.js.map +1 -1
- package/dist/generation/ordering.d.ts +3 -1
- package/dist/generation/ordering.d.ts.map +1 -1
- package/dist/generation/ordering.js +3 -1
- package/dist/generation/ordering.js.map +1 -1
- package/dist/generation/schema-enum-domains.d.ts +27 -0
- package/dist/generation/schema-enum-domains.d.ts.map +1 -0
- package/dist/generation/schema-enum-domains.js +69 -0
- package/dist/generation/schema-enum-domains.js.map +1 -0
- package/dist/generation/structural-checks.js +7 -1
- package/dist/generation/structural-checks.js.map +1 -1
- package/dist/generation/validate-spec.d.ts.map +1 -1
- package/dist/generation/validate-spec.js +149 -31
- package/dist/generation/validate-spec.js.map +1 -1
- package/dist/index.d.ts +7 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -46
- package/dist/index.js.map +1 -1
- package/dist/primitives/accept_ai_edits.d.ts +87 -0
- package/dist/primitives/accept_ai_edits.d.ts.map +1 -0
- package/dist/primitives/accept_ai_edits.js +253 -0
- package/dist/primitives/accept_ai_edits.js.map +1 -0
- package/dist/primitives/accept_changes.d.ts +36 -3
- package/dist/primitives/accept_changes.d.ts.map +1 -1
- package/dist/primitives/accept_changes.js +67 -31
- package/dist/primitives/accept_changes.js.map +1 -1
- package/dist/primitives/bookmarks.d.ts +44 -0
- package/dist/primitives/bookmarks.d.ts.map +1 -1
- package/dist/primitives/bookmarks.js +149 -11
- package/dist/primitives/bookmarks.js.map +1 -1
- package/dist/primitives/document.d.ts +53 -1
- package/dist/primitives/document.d.ts.map +1 -1
- package/dist/primitives/document.js +146 -2
- package/dist/primitives/document.js.map +1 -1
- package/dist/primitives/dom-helpers.d.ts.map +1 -1
- package/dist/primitives/dom-helpers.js +7 -2
- package/dist/primitives/dom-helpers.js.map +1 -1
- package/dist/primitives/index.d.ts +2 -1
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +2 -1
- package/dist/primitives/index.js.map +1 -1
- package/dist/primitives/layout.d.ts.map +1 -1
- package/dist/primitives/layout.js +41 -1
- package/dist/primitives/layout.js.map +1 -1
- package/dist/primitives/namespaces.d.ts +2 -0
- package/dist/primitives/namespaces.d.ts.map +1 -1
- package/dist/primitives/namespaces.js +2 -0
- package/dist/primitives/namespaces.js.map +1 -1
- package/dist/primitives/reject_changes.d.ts +21 -2
- package/dist/primitives/reject_changes.d.ts.map +1 -1
- package/dist/primitives/reject_changes.js +71 -31
- package/dist/primitives/reject_changes.js.map +1 -1
- package/dist/primitives/relationships.d.ts +33 -0
- package/dist/primitives/relationships.d.ts.map +1 -1
- package/dist/primitives/relationships.js +84 -1
- package/dist/primitives/relationships.js.map +1 -1
- package/dist/primitives/sectPrAudit.d.ts +11 -2
- package/dist/primitives/sectPrAudit.d.ts.map +1 -1
- package/dist/primitives/sectPrAudit.js +148 -23
- package/dist/primitives/sectPrAudit.js.map +1 -1
- package/dist/primitives/track-changes-emitter.d.ts +4 -0
- package/dist/primitives/track-changes-emitter.d.ts.map +1 -1
- package/dist/primitives/track-changes-emitter.js +5 -2
- package/dist/primitives/track-changes-emitter.js.map +1 -1
- package/dist/primitives/validate_ai_revisions.d.ts.map +1 -1
- package/dist/primitives/validate_ai_revisions.js +13 -5
- package/dist/primitives/validate_ai_revisions.js.map +1 -1
- package/dist/primitives/zip.d.ts.map +1 -1
- package/dist/primitives/zip.js +6 -2
- package/dist/primitives/zip.js.map +1 -1
- package/dist/shared/field-structure.d.ts +6 -0
- package/dist/shared/field-structure.d.ts.map +1 -1
- package/dist/shared/field-structure.js +6 -0
- package/dist/shared/field-structure.js.map +1 -1
- package/package.json +4 -8
- package/dist/atomizer.d.ts +0 -273
- package/dist/atomizer.d.ts.map +0 -1
- package/dist/atomizer.js +0 -1002
- package/dist/atomizer.js.map +0 -1
- package/dist/baselines/atomizer/atomLcs.d.ts +0 -82
- package/dist/baselines/atomizer/atomLcs.d.ts.map +0 -1
- package/dist/baselines/atomizer/atomLcs.js +0 -376
- package/dist/baselines/atomizer/atomLcs.js.map +0 -1
- package/dist/baselines/atomizer/auxiliaryIdCollision.d.ts +0 -99
- package/dist/baselines/atomizer/auxiliaryIdCollision.d.ts.map +0 -1
- package/dist/baselines/atomizer/auxiliaryIdCollision.js +0 -415
- package/dist/baselines/atomizer/auxiliaryIdCollision.js.map +0 -1
- package/dist/baselines/atomizer/consumerCompatibility.d.ts +0 -2
- package/dist/baselines/atomizer/consumerCompatibility.d.ts.map +0 -1
- package/dist/baselines/atomizer/consumerCompatibility.js +0 -188
- package/dist/baselines/atomizer/consumerCompatibility.js.map +0 -1
- package/dist/baselines/atomizer/debug.d.ts +0 -41
- package/dist/baselines/atomizer/debug.d.ts.map +0 -1
- package/dist/baselines/atomizer/debug.js +0 -85
- package/dist/baselines/atomizer/debug.js.map +0 -1
- package/dist/baselines/atomizer/documentReconstructor.d.ts +0 -75
- package/dist/baselines/atomizer/documentReconstructor.d.ts.map +0 -1
- package/dist/baselines/atomizer/documentReconstructor.js +0 -1449
- package/dist/baselines/atomizer/documentReconstructor.js.map +0 -1
- package/dist/baselines/atomizer/formattingFidelity.d.ts +0 -99
- package/dist/baselines/atomizer/formattingFidelity.d.ts.map +0 -1
- package/dist/baselines/atomizer/formattingFidelity.js +0 -449
- package/dist/baselines/atomizer/formattingFidelity.js.map +0 -1
- package/dist/baselines/atomizer/hierarchicalLcs.d.ts +0 -121
- package/dist/baselines/atomizer/hierarchicalLcs.d.ts.map +0 -1
- package/dist/baselines/atomizer/hierarchicalLcs.js +0 -753
- package/dist/baselines/atomizer/hierarchicalLcs.js.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-bookmarks.d.ts +0 -37
- package/dist/baselines/atomizer/inPlaceModifier-bookmarks.d.ts.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-bookmarks.js +0 -189
- package/dist/baselines/atomizer/inPlaceModifier-bookmarks.js.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-containers.d.ts +0 -74
- package/dist/baselines/atomizer/inPlaceModifier-containers.d.ts.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-containers.js +0 -171
- package/dist/baselines/atomizer/inPlaceModifier-containers.js.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-deletion.d.ts +0 -88
- package/dist/baselines/atomizer/inPlaceModifier-deletion.d.ts.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-deletion.js +0 -326
- package/dist/baselines/atomizer/inPlaceModifier-deletion.js.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-postprocess.d.ts +0 -85
- package/dist/baselines/atomizer/inPlaceModifier-postprocess.d.ts.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-postprocess.js +0 -402
- package/dist/baselines/atomizer/inPlaceModifier-postprocess.js.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-presplit.d.ts +0 -39
- package/dist/baselines/atomizer/inPlaceModifier-presplit.d.ts.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-presplit.js +0 -265
- package/dist/baselines/atomizer/inPlaceModifier-presplit.js.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-shared.d.ts +0 -62
- package/dist/baselines/atomizer/inPlaceModifier-shared.d.ts.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-shared.js +0 -139
- package/dist/baselines/atomizer/inPlaceModifier-shared.js.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-wrappers.d.ts +0 -198
- package/dist/baselines/atomizer/inPlaceModifier-wrappers.d.ts.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier-wrappers.js +0 -475
- package/dist/baselines/atomizer/inPlaceModifier-wrappers.js.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier.d.ts +0 -27
- package/dist/baselines/atomizer/inPlaceModifier.d.ts.map +0 -1
- package/dist/baselines/atomizer/inPlaceModifier.js +0 -648
- package/dist/baselines/atomizer/inPlaceModifier.js.map +0 -1
- package/dist/baselines/atomizer/numberingIntegration.d.ts +0 -59
- package/dist/baselines/atomizer/numberingIntegration.d.ts.map +0 -1
- package/dist/baselines/atomizer/numberingIntegration.js +0 -209
- package/dist/baselines/atomizer/numberingIntegration.js.map +0 -1
- package/dist/baselines/atomizer/pipeline.d.ts +0 -103
- package/dist/baselines/atomizer/pipeline.d.ts.map +0 -1
- package/dist/baselines/atomizer/pipeline.js +0 -1160
- package/dist/baselines/atomizer/pipeline.js.map +0 -1
- package/dist/baselines/atomizer/premergeRuns.d.ts +0 -26
- package/dist/baselines/atomizer/premergeRuns.d.ts.map +0 -1
- package/dist/baselines/atomizer/premergeRuns.js +0 -153
- package/dist/baselines/atomizer/premergeRuns.js.map +0 -1
- package/dist/baselines/atomizer/trackChangesAcceptor.d.ts +0 -63
- package/dist/baselines/atomizer/trackChangesAcceptor.d.ts.map +0 -1
- package/dist/baselines/atomizer/trackChangesAcceptor.js +0 -254
- package/dist/baselines/atomizer/trackChangesAcceptor.js.map +0 -1
- package/dist/baselines/atomizer/trackChangesAcceptorAst.d.ts +0 -64
- package/dist/baselines/atomizer/trackChangesAcceptorAst.d.ts.map +0 -1
- package/dist/baselines/atomizer/trackChangesAcceptorAst.js +0 -642
- package/dist/baselines/atomizer/trackChangesAcceptorAst.js.map +0 -1
- package/dist/baselines/atomizer/xmlToWmlElement.d.ts +0 -65
- package/dist/baselines/atomizer/xmlToWmlElement.d.ts.map +0 -1
- package/dist/baselines/atomizer/xmlToWmlElement.js +0 -96
- package/dist/baselines/atomizer/xmlToWmlElement.js.map +0 -1
- package/dist/baselines/wmlcomparer/DocxodusWasm.d.ts +0 -51
- package/dist/baselines/wmlcomparer/DocxodusWasm.d.ts.map +0 -1
- package/dist/baselines/wmlcomparer/DocxodusWasm.js +0 -83
- package/dist/baselines/wmlcomparer/DocxodusWasm.js.map +0 -1
- package/dist/baselines/wmlcomparer/DotnetCli.d.ts +0 -40
- package/dist/baselines/wmlcomparer/DotnetCli.d.ts.map +0 -1
- package/dist/baselines/wmlcomparer/DotnetCli.js +0 -142
- package/dist/baselines/wmlcomparer/DotnetCli.js.map +0 -1
- package/dist/cli/compare-two.d.ts +0 -28
- package/dist/cli/compare-two.d.ts.map +0 -1
- package/dist/cli/compare-two.js +0 -112
- package/dist/cli/compare-two.js.map +0 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -25
- package/dist/cli/index.js.map +0 -1
- package/dist/compare-types.d.ts +0 -197
- package/dist/compare-types.d.ts.map +0 -1
- package/dist/compare-types.js +0 -2
- package/dist/compare-types.js.map +0 -1
- package/dist/format-detection.d.ts +0 -120
- package/dist/format-detection.d.ts.map +0 -1
- package/dist/format-detection.js +0 -339
- package/dist/format-detection.js.map +0 -1
- package/dist/move-detection.d.ts +0 -211
- package/dist/move-detection.d.ts.map +0 -1
- package/dist/move-detection.js +0 -390
- package/dist/move-detection.js.map +0 -1
|
@@ -1,648 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-Place AST Modifier
|
|
3
|
-
*
|
|
4
|
-
* Modifies the revised document's AST in-place to add track changes markup.
|
|
5
|
-
* This replaces the reconstruction-based approach with direct tree manipulation.
|
|
6
|
-
*
|
|
7
|
-
* Key operations:
|
|
8
|
-
* - wrapAsInserted: Wrap run elements with <w:ins> for inserted content
|
|
9
|
-
* - insertDeletedContent: Clone and insert deleted content with <w:del> wrapper
|
|
10
|
-
* - wrapAsMoveFrom/wrapAsMoveTo: Add move tracking with range markers
|
|
11
|
-
* - addFormatChange: Add <w:rPrChange> for formatting differences
|
|
12
|
-
*/
|
|
13
|
-
import { CorrelationStatus } from '../../core-types.js';
|
|
14
|
-
import { childElements, findChildByTagName, insertAfterElement } from '../../primitives/index.js';
|
|
15
|
-
import { enforceConsumerCompatibility } from './consumerCompatibility.js';
|
|
16
|
-
import { XMLSerializer } from '@xmldom/xmldom';
|
|
17
|
-
import { findChild } from '../../primitives/xml-helpers.js';
|
|
18
|
-
import { warn } from './debug.js';
|
|
19
|
-
import { attachSourceElementPointers, createEl, createRevisionIdState, allocateRevisionId, formatDate, paragraphHasParaInsMarker, } from './inPlaceModifier-shared.js';
|
|
20
|
-
import { ContainerResolutionError, findTargetContainerForAtom, isEntireParagraphAtomsWithStatus, } from './inPlaceModifier-containers.js';
|
|
21
|
-
import { cloneParagraphBoundaryBookmarkMarkers, filterEquivalentBookmarkMarkers, insertLeadingMarkers, } from './inPlaceModifier-bookmarks.js';
|
|
22
|
-
import { addFormatChange, getAtomRunAtBoundary, getAtomRuns, getRunInsertionAnchor, wrapAsInserted, wrapAsMoveTo, wrapParagraphAsDeleted, wrapParagraphAsInserted, } from './inPlaceModifier-wrappers.js';
|
|
23
|
-
import { insertDeletedParagraph, insertDeletedRun, insertMoveFromRun } from './inPlaceModifier-deletion.js';
|
|
24
|
-
import { preSplitInterleavedWordRuns, preSplitMixedStatusRuns } from './inPlaceModifier-presplit.js';
|
|
25
|
-
import { coalesceDelInsPairChains, groupDeletionsBeforeInsertions, mergeAdjacentTrackChangeSiblings, mergeWhitespaceBridgedTrackChanges, suppressNoOpChangePairs, } from './inPlaceModifier-postprocess.js';
|
|
26
|
-
export function modifyRevisedDocument(revisedRoot, originalAtoms, revisedAtoms, mergedAtoms, options) {
|
|
27
|
-
const { author, date } = options;
|
|
28
|
-
const dateStr = formatDate(date);
|
|
29
|
-
const state = createRevisionIdState();
|
|
30
|
-
// In-place mode needs concrete AST node pointers for run/paragraph edits.
|
|
31
|
-
// Populate these once up-front so handlers don't have to rescan ancestor chains.
|
|
32
|
-
attachSourceElementPointers(originalAtoms);
|
|
33
|
-
attachSourceElementPointers(revisedAtoms);
|
|
34
|
-
preSplitMixedStatusRuns(mergedAtoms);
|
|
35
|
-
preSplitInterleavedWordRuns(mergedAtoms);
|
|
36
|
-
// Process atoms and apply track changes to the revised tree
|
|
37
|
-
// Group atoms by paragraph for efficient processing
|
|
38
|
-
const ctx = processAtoms(mergedAtoms, originalAtoms, revisedAtoms, author, dateStr, state, revisedRoot);
|
|
39
|
-
// Add paragraph-mark revision markers for whole-paragraph insert/delete cases.
|
|
40
|
-
// This is required for idempotency in Word:
|
|
41
|
-
// - Reject All should remove inserted paragraphs entirely
|
|
42
|
-
// - Accept All should remove deleted paragraphs entirely
|
|
43
|
-
applyWholeParagraphRevisionMarkers(mergedAtoms, ctx);
|
|
44
|
-
// Suppress field-adjacent no-op del/ins pairs (issue #42, Bug 1).
|
|
45
|
-
// Must run BEFORE merge — after merge, pairwise comparison is impossible.
|
|
46
|
-
suppressNoOpChangePairs(ctx.body);
|
|
47
|
-
// Merge adjacent <w:ins>/<w:del> siblings to reduce revision fragmentation.
|
|
48
|
-
mergeAdjacentTrackChangeSiblings(ctx.body);
|
|
49
|
-
// Coalesce del/ins pair chains across whitespace (issue #42, Bug 2b).
|
|
50
|
-
// Merges [del:A][ins:X][ws][del:B][ins:Y] → [del:A ws B][ins:X ws Y]
|
|
51
|
-
coalesceDelInsPairChains(ctx.body);
|
|
52
|
-
// Merge whitespace-bridged track change siblings (issue #42, Bug 2).
|
|
53
|
-
// Runs AFTER coalesce — handles ins+ws+ins and moveTo+ws+moveTo bridging.
|
|
54
|
-
mergeWhitespaceBridgedTrackChanges(ctx.body);
|
|
55
|
-
// Apply strict post-render consumer compatibility pass
|
|
56
|
-
enforceConsumerCompatibility(revisedRoot, () => allocateRevisionId(state));
|
|
57
|
-
// Serialize the modified tree
|
|
58
|
-
return new XMLSerializer().serializeToString(revisedRoot.ownerDocument || revisedRoot);
|
|
59
|
-
}
|
|
60
|
-
function isParagraphRemovedOnRejectInContext(paragraph, ctx) {
|
|
61
|
-
if (paragraphHasParaInsMarker(paragraph)) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
const unifiedIndex = ctx.revisedParagraphToUnifiedIndex.get(paragraph);
|
|
65
|
-
return unifiedIndex !== undefined && ctx.fullyInsertedParagraphIndices.has(unifiedIndex);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Handle Inserted atoms - wrap the run with <w:ins>.
|
|
69
|
-
* Inserted atoms have sourceRunElement in the REVISED tree.
|
|
70
|
-
*/
|
|
71
|
-
function handleInserted(atom, ctx) {
|
|
72
|
-
const runs = getAtomRuns(atom);
|
|
73
|
-
if (runs.length > 0) {
|
|
74
|
-
// ECMA-376 Part 4 permits w:fldChar inside <w:ins> (only <w:del> bars it),
|
|
75
|
-
// so an inserted complete field stays wrapped as a single <w:ins>. The
|
|
76
|
-
// fragmentation work for issue #217 is scoped to the <w:del> side via
|
|
77
|
-
// insertDeletedRun.
|
|
78
|
-
for (const run of runs) {
|
|
79
|
-
wrapAsInserted(run, ctx.author, ctx.dateStr, ctx.state);
|
|
80
|
-
}
|
|
81
|
-
const endRun = getAtomRunAtBoundary(atom, 'end') ?? runs[runs.length - 1];
|
|
82
|
-
const insertionPoint = getRunInsertionAnchor(endRun);
|
|
83
|
-
if (insertionPoint === ctx.lastRevisedRunAnchor) {
|
|
84
|
-
return {
|
|
85
|
-
newLastParagraph: atom.sourceParagraphElement ?? ctx.lastProcessedParagraph,
|
|
86
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
newLastRun: insertionPoint,
|
|
91
|
-
newLastRevisedRunAnchor: insertionPoint,
|
|
92
|
-
newLastParagraph: atom.sourceParagraphElement ?? ctx.lastProcessedParagraph,
|
|
93
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
else if (atom.isEmptyParagraph && atom.sourceParagraphElement) {
|
|
97
|
-
// Empty inserted paragraph: mark paragraph properties instead of wrapping <w:p>.
|
|
98
|
-
wrapParagraphAsInserted(atom.sourceParagraphElement, ctx.author, ctx.dateStr, ctx.state);
|
|
99
|
-
return {
|
|
100
|
-
newLastParagraph: atom.sourceParagraphElement,
|
|
101
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
return {};
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Handle Deleted atoms - clone from original and insert with <w:del>.
|
|
108
|
-
* Deleted atoms have sourceRunElement in the ORIGINAL tree.
|
|
109
|
-
* We need to clone and insert into the REVISED tree.
|
|
110
|
-
*
|
|
111
|
-
* Paragraph placement logic:
|
|
112
|
-
* 1. If the atom's unified paragraph exists in the revised document, insert there
|
|
113
|
-
* 2. If we've already created a paragraph for this unified index, use it
|
|
114
|
-
* 3. Otherwise, create a new paragraph and insert it at the correct position
|
|
115
|
-
*/
|
|
116
|
-
function handleDeleted(atom, ctx) {
|
|
117
|
-
const bookmarkSurvivalContext = {
|
|
118
|
-
isParagraphRemovedOnReject: (paragraph) => isParagraphRemovedOnRejectInContext(paragraph, ctx),
|
|
119
|
-
};
|
|
120
|
-
// Handle empty deleted paragraphs specially
|
|
121
|
-
if (atom.isEmptyParagraph && atom.sourceParagraphElement) {
|
|
122
|
-
// Container-aware insertion (issue #65)
|
|
123
|
-
const emptyTarget = findTargetContainerForAtom(atom, ctx);
|
|
124
|
-
if (!emptyTarget) {
|
|
125
|
-
throw new ContainerResolutionError('Container topology mismatch for empty deleted paragraph');
|
|
126
|
-
}
|
|
127
|
-
const createdPara = insertDeletedParagraph(atom, emptyTarget.insertAfter, emptyTarget.container, ctx.author, ctx.dateStr, ctx.state);
|
|
128
|
-
if (createdPara && atom.paragraphIndex !== undefined) {
|
|
129
|
-
ctx.createdParagraphs.set(atom.paragraphIndex, createdPara);
|
|
130
|
-
}
|
|
131
|
-
if (createdPara) {
|
|
132
|
-
wrapParagraphAsDeleted(createdPara, ctx.author, ctx.dateStr, ctx.state);
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
newLastParagraph: createdPara ?? ctx.lastProcessedParagraph,
|
|
136
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
if (atom.sourceRunElement) {
|
|
140
|
-
const unifiedPara = atom.paragraphIndex;
|
|
141
|
-
let targetParagraph;
|
|
142
|
-
let insertAfterRun = null;
|
|
143
|
-
// Determine target paragraph and insertion point
|
|
144
|
-
if (unifiedPara !== undefined) {
|
|
145
|
-
// Check if this unified paragraph exists in the revised document
|
|
146
|
-
const revisedPara = ctx.unifiedParaToElement.get(unifiedPara);
|
|
147
|
-
const revisedParagraphRemovedOnReject = revisedPara !== undefined &&
|
|
148
|
-
(ctx.fullyInsertedParagraphIndices.has(unifiedPara) || paragraphHasParaInsMarker(revisedPara));
|
|
149
|
-
if (revisedPara && !revisedParagraphRemovedOnReject) {
|
|
150
|
-
// Paragraph exists in revised and survives Reject All - insert into it.
|
|
151
|
-
targetParagraph = revisedPara;
|
|
152
|
-
// If this is the same paragraph we last processed, use lastProcessedRun
|
|
153
|
-
if (ctx.lastParagraphIndex === unifiedPara) {
|
|
154
|
-
insertAfterRun = ctx.lastProcessedRun;
|
|
155
|
-
}
|
|
156
|
-
// Otherwise, insert at the beginning of the paragraph (insertAfterRun = null)
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
// Paragraph is absent in revised OR will be removed on Reject All.
|
|
160
|
-
// Route deleted content into a created paragraph so reject output keeps
|
|
161
|
-
// original-order text and bookmark markers.
|
|
162
|
-
const createdPara = ctx.createdParagraphs.get(unifiedPara);
|
|
163
|
-
if (createdPara) {
|
|
164
|
-
targetParagraph = createdPara;
|
|
165
|
-
insertAfterRun = ctx.createdParagraphLastRun.get(unifiedPara) ?? null;
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
// Need to create a new paragraph for this deleted content
|
|
169
|
-
const newPara = createEl('w:p');
|
|
170
|
-
const boundaryMarkers = cloneParagraphBoundaryBookmarkMarkers(atom.sourceParagraphElement);
|
|
171
|
-
for (const marker of [...boundaryMarkers.sourceLeading, ...boundaryMarkers.sourceTrailing]) {
|
|
172
|
-
ctx.state.emittedSourceBookmarkMarkers.add(marker);
|
|
173
|
-
}
|
|
174
|
-
const leadingMarkers = filterEquivalentBookmarkMarkers(boundaryMarkers.leading, ctx.body, bookmarkSurvivalContext);
|
|
175
|
-
const trailingMarkers = filterEquivalentBookmarkMarkers(boundaryMarkers.trailing, ctx.body, bookmarkSurvivalContext);
|
|
176
|
-
// Preserve paragraph properties from the original paragraph for fidelity.
|
|
177
|
-
const srcP = atom.sourceParagraphElement;
|
|
178
|
-
const srcPPr = srcP ? findChildByTagName(srcP, 'w:pPr') : null;
|
|
179
|
-
if (srcPPr) {
|
|
180
|
-
const clonedPPr = srcPPr.cloneNode(true);
|
|
181
|
-
newPara.appendChild(clonedPPr);
|
|
182
|
-
}
|
|
183
|
-
// Container-aware insertion (issue #65)
|
|
184
|
-
const delTarget = findTargetContainerForAtom(atom, ctx);
|
|
185
|
-
if (!delTarget) {
|
|
186
|
-
throw new ContainerResolutionError('Container topology mismatch for deleted paragraph');
|
|
187
|
-
}
|
|
188
|
-
if (delTarget.insertAfter) {
|
|
189
|
-
insertAfterElement(delTarget.insertAfter, newPara);
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
const propsEl = delTarget.container.tagName === 'w:tc'
|
|
193
|
-
? findChildByTagName(delTarget.container, 'w:tcPr')
|
|
194
|
-
: null;
|
|
195
|
-
if (propsEl) {
|
|
196
|
-
insertAfterElement(propsEl, newPara);
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
delTarget.container.insertBefore(newPara, delTarget.container.firstChild);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
ctx.createdParagraphs.set(unifiedPara, newPara);
|
|
203
|
-
const leadingTail = insertLeadingMarkers(newPara, leadingMarkers);
|
|
204
|
-
if (leadingTail) {
|
|
205
|
-
ctx.createdParagraphLastRun.set(unifiedPara, leadingTail);
|
|
206
|
-
}
|
|
207
|
-
if (trailingMarkers.length > 0) {
|
|
208
|
-
ctx.createdParagraphTrailingBookmarks.set(unifiedPara, trailingMarkers);
|
|
209
|
-
}
|
|
210
|
-
targetParagraph = newPara;
|
|
211
|
-
insertAfterRun = leadingTail;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
// Fall back to last processed paragraph if we couldn't determine target
|
|
216
|
-
if (!targetParagraph) {
|
|
217
|
-
targetParagraph = ctx.lastProcessedParagraph ??
|
|
218
|
-
childElements(ctx.body).find(c => c.tagName === 'w:p');
|
|
219
|
-
}
|
|
220
|
-
if (!targetParagraph) {
|
|
221
|
-
warn('inPlaceModifier', 'Cannot insert deleted content: no target paragraph found', {
|
|
222
|
-
atomText: atom.contentElement?.textContent,
|
|
223
|
-
});
|
|
224
|
-
return {};
|
|
225
|
-
}
|
|
226
|
-
const del = insertDeletedRun(atom, insertAfterRun, targetParagraph, ctx.author, ctx.dateStr, ctx.state, bookmarkSurvivalContext);
|
|
227
|
-
if (del) {
|
|
228
|
-
// Track last run in created paragraphs
|
|
229
|
-
if (unifiedPara !== undefined && ctx.createdParagraphs.has(unifiedPara)) {
|
|
230
|
-
ctx.createdParagraphLastRun.set(unifiedPara, del);
|
|
231
|
-
}
|
|
232
|
-
return {
|
|
233
|
-
newLastRun: del,
|
|
234
|
-
newLastParagraph: targetParagraph,
|
|
235
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return {};
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Handle MovedSource atoms - clone from original and insert with <w:moveFrom>.
|
|
243
|
-
*
|
|
244
|
-
* MovedSource atoms have sourceRunElement pointing to the ORIGINAL tree.
|
|
245
|
-
* We need to clone the content and insert it into the REVISED tree.
|
|
246
|
-
*
|
|
247
|
-
* Paragraph placement logic (same as handleDeleted):
|
|
248
|
-
* 1. If the atom's unified paragraph exists in the revised document, insert there
|
|
249
|
-
* 2. If we've already created a paragraph for this unified index, use it
|
|
250
|
-
* 3. Otherwise, create a new paragraph and insert it at the correct position
|
|
251
|
-
*/
|
|
252
|
-
function handleMovedSource(atom, ctx) {
|
|
253
|
-
const bookmarkSurvivalContext = {
|
|
254
|
-
isParagraphRemovedOnReject: (paragraph) => isParagraphRemovedOnRejectInContext(paragraph, ctx),
|
|
255
|
-
};
|
|
256
|
-
if (atom.sourceRunElement) {
|
|
257
|
-
const unifiedPara = atom.paragraphIndex;
|
|
258
|
-
let targetParagraph;
|
|
259
|
-
let insertAfterRun = null;
|
|
260
|
-
// Determine target paragraph and insertion point
|
|
261
|
-
if (unifiedPara !== undefined) {
|
|
262
|
-
// Check if this unified paragraph exists in the revised document
|
|
263
|
-
const revisedPara = ctx.unifiedParaToElement.get(unifiedPara);
|
|
264
|
-
const revisedParagraphRemovedOnReject = revisedPara !== undefined &&
|
|
265
|
-
(ctx.fullyInsertedParagraphIndices.has(unifiedPara) || paragraphHasParaInsMarker(revisedPara));
|
|
266
|
-
if (revisedPara && !revisedParagraphRemovedOnReject) {
|
|
267
|
-
// Paragraph exists in revised and survives Reject All - insert into it.
|
|
268
|
-
targetParagraph = revisedPara;
|
|
269
|
-
// If this is the same paragraph we last processed, use lastProcessedRun
|
|
270
|
-
if (ctx.lastParagraphIndex === unifiedPara) {
|
|
271
|
-
insertAfterRun = ctx.lastProcessedRun;
|
|
272
|
-
}
|
|
273
|
-
// Otherwise, insert at the beginning of the paragraph (insertAfterRun = null)
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
// Paragraph is absent in revised OR will be removed on Reject All.
|
|
277
|
-
// Route moved-from content into a created paragraph for reject fidelity.
|
|
278
|
-
const createdPara = ctx.createdParagraphs.get(unifiedPara);
|
|
279
|
-
if (createdPara) {
|
|
280
|
-
targetParagraph = createdPara;
|
|
281
|
-
insertAfterRun = ctx.createdParagraphLastRun.get(unifiedPara) ?? null;
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
// Need to create a new paragraph for this moved-from content
|
|
285
|
-
const newPara = createEl('w:p');
|
|
286
|
-
const boundaryMarkers = cloneParagraphBoundaryBookmarkMarkers(atom.sourceParagraphElement);
|
|
287
|
-
for (const marker of [...boundaryMarkers.sourceLeading, ...boundaryMarkers.sourceTrailing]) {
|
|
288
|
-
ctx.state.emittedSourceBookmarkMarkers.add(marker);
|
|
289
|
-
}
|
|
290
|
-
const leadingMarkers = filterEquivalentBookmarkMarkers(boundaryMarkers.leading, ctx.body, bookmarkSurvivalContext);
|
|
291
|
-
const trailingMarkers = filterEquivalentBookmarkMarkers(boundaryMarkers.trailing, ctx.body, bookmarkSurvivalContext);
|
|
292
|
-
// Preserve paragraph properties from the original paragraph for fidelity.
|
|
293
|
-
const srcP = atom.sourceParagraphElement;
|
|
294
|
-
const srcPPr = srcP ? findChildByTagName(srcP, 'w:pPr') : null;
|
|
295
|
-
if (srcPPr) {
|
|
296
|
-
const clonedPPr = srcPPr.cloneNode(true);
|
|
297
|
-
newPara.appendChild(clonedPPr);
|
|
298
|
-
}
|
|
299
|
-
// Container-aware insertion (issue #65)
|
|
300
|
-
const moveTarget = findTargetContainerForAtom(atom, ctx);
|
|
301
|
-
if (!moveTarget) {
|
|
302
|
-
throw new ContainerResolutionError('Container topology mismatch for moved-from paragraph');
|
|
303
|
-
}
|
|
304
|
-
if (moveTarget.insertAfter) {
|
|
305
|
-
insertAfterElement(moveTarget.insertAfter, newPara);
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
const propsEl = moveTarget.container.tagName === 'w:tc'
|
|
309
|
-
? findChildByTagName(moveTarget.container, 'w:tcPr')
|
|
310
|
-
: null;
|
|
311
|
-
if (propsEl) {
|
|
312
|
-
insertAfterElement(propsEl, newPara);
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
moveTarget.container.insertBefore(newPara, moveTarget.container.firstChild);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
ctx.createdParagraphs.set(unifiedPara, newPara);
|
|
319
|
-
const leadingTail = insertLeadingMarkers(newPara, leadingMarkers);
|
|
320
|
-
if (leadingTail) {
|
|
321
|
-
ctx.createdParagraphLastRun.set(unifiedPara, leadingTail);
|
|
322
|
-
}
|
|
323
|
-
if (trailingMarkers.length > 0) {
|
|
324
|
-
ctx.createdParagraphTrailingBookmarks.set(unifiedPara, trailingMarkers);
|
|
325
|
-
}
|
|
326
|
-
targetParagraph = newPara;
|
|
327
|
-
insertAfterRun = leadingTail;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
// Fall back to last processed paragraph if we couldn't determine target
|
|
332
|
-
if (!targetParagraph) {
|
|
333
|
-
targetParagraph = ctx.lastProcessedParagraph ??
|
|
334
|
-
childElements(ctx.body).find(c => c.tagName === 'w:p');
|
|
335
|
-
}
|
|
336
|
-
if (!targetParagraph) {
|
|
337
|
-
warn('inPlaceModifier', 'Cannot insert moved-from content: no target paragraph found', {
|
|
338
|
-
atomText: atom.contentElement?.textContent,
|
|
339
|
-
});
|
|
340
|
-
return {};
|
|
341
|
-
}
|
|
342
|
-
const moveFrom = insertMoveFromRun(atom, atom.moveName || 'move1', insertAfterRun, targetParagraph, ctx.author, ctx.dateStr, ctx.state, bookmarkSurvivalContext);
|
|
343
|
-
if (moveFrom) {
|
|
344
|
-
// Track last run in created paragraphs
|
|
345
|
-
if (unifiedPara !== undefined && ctx.createdParagraphs.has(unifiedPara)) {
|
|
346
|
-
ctx.createdParagraphLastRun.set(unifiedPara, moveFrom);
|
|
347
|
-
}
|
|
348
|
-
return {
|
|
349
|
-
newLastRun: moveFrom,
|
|
350
|
-
newLastParagraph: targetParagraph,
|
|
351
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return {};
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Handle MovedDestination atoms - wrap with <w:moveTo>.
|
|
359
|
-
* MovedDestination atoms have sourceRunElement in the REVISED tree.
|
|
360
|
-
*/
|
|
361
|
-
function handleMovedDestination(atom, ctx) {
|
|
362
|
-
const runs = getAtomRuns(atom);
|
|
363
|
-
if (runs.length > 0) {
|
|
364
|
-
// Move destinations behave like insertions; ECMA-376 does not bar w:fldChar
|
|
365
|
-
// from <w:moveTo> (only <w:del> is explicitly forbidden). Keep the existing
|
|
366
|
-
// single-wrapper behavior to avoid fragmenting a moved-in field.
|
|
367
|
-
for (const run of runs) {
|
|
368
|
-
wrapAsMoveTo(run, atom.moveName || 'move1', ctx.author, ctx.dateStr, ctx.state);
|
|
369
|
-
}
|
|
370
|
-
const endRun = getAtomRunAtBoundary(atom, 'end') ?? runs[runs.length - 1];
|
|
371
|
-
const insertionPoint = getRunInsertionAnchor(endRun);
|
|
372
|
-
if (insertionPoint === ctx.lastRevisedRunAnchor) {
|
|
373
|
-
return {
|
|
374
|
-
newLastParagraph: atom.sourceParagraphElement ?? ctx.lastProcessedParagraph,
|
|
375
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
return {
|
|
379
|
-
newLastRun: insertionPoint,
|
|
380
|
-
newLastRevisedRunAnchor: insertionPoint,
|
|
381
|
-
newLastParagraph: atom.sourceParagraphElement ?? ctx.lastProcessedParagraph,
|
|
382
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
return {};
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Handle FormatChanged atoms - add <w:rPrChange>.
|
|
389
|
-
* FormatChanged atoms have sourceRunElement in the REVISED tree.
|
|
390
|
-
*/
|
|
391
|
-
function handleFormatChanged(atom, ctx) {
|
|
392
|
-
const run = getAtomRunAtBoundary(atom, 'start');
|
|
393
|
-
if (run && atom.formatChange?.oldRunProperties) {
|
|
394
|
-
addFormatChange(run, atom.formatChange.oldRunProperties, ctx.author, ctx.dateStr, ctx.state);
|
|
395
|
-
const endRun = getAtomRunAtBoundary(atom, 'end') ?? run;
|
|
396
|
-
const insertionPoint = getRunInsertionAnchor(endRun);
|
|
397
|
-
if (insertionPoint === ctx.lastRevisedRunAnchor) {
|
|
398
|
-
return {
|
|
399
|
-
newLastParagraph: atom.sourceParagraphElement ?? ctx.lastProcessedParagraph,
|
|
400
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
return {
|
|
404
|
-
newLastRun: insertionPoint,
|
|
405
|
-
newLastRevisedRunAnchor: insertionPoint,
|
|
406
|
-
newLastParagraph: atom.sourceParagraphElement ?? ctx.lastProcessedParagraph,
|
|
407
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
return {};
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Handle Equal/Unknown atoms - just track position.
|
|
414
|
-
*
|
|
415
|
-
* IMPORTANT: For inplace mode, we must track positions in the REVISED tree.
|
|
416
|
-
* - Non-empty Equal atoms come from the revised tree (sourceRunElement/sourceParagraphElement point to revised)
|
|
417
|
-
* - Empty paragraph Equal atoms come from the ORIGINAL tree (see createMergedAtomList)
|
|
418
|
-
*
|
|
419
|
-
* For empty paragraphs, we need to look up the corresponding revised paragraph
|
|
420
|
-
* from unifiedParaToElement, not use the atom's sourceParagraphElement (which is from original tree).
|
|
421
|
-
*
|
|
422
|
-
* CRITICAL: When the paragraph index changes, we MUST reset newLastRun to null.
|
|
423
|
-
* This ensures that subsequent content is not incorrectly inserted after a run
|
|
424
|
-
* from a previous paragraph. See the "Gross Asset Value" bug fix.
|
|
425
|
-
*/
|
|
426
|
-
function handleEqual(atom, ctx) {
|
|
427
|
-
// For non-empty atoms, sourceRunElement points to revised tree - safe to use directly
|
|
428
|
-
const run = getAtomRunAtBoundary(atom, 'end');
|
|
429
|
-
if (run) {
|
|
430
|
-
const insertionPoint = getRunInsertionAnchor(run);
|
|
431
|
-
// BUG FIX: Don't move the insertion point backwards if we are still in the same run.
|
|
432
|
-
// This prevents Deleted atoms inserted between words of the same run from being reversed.
|
|
433
|
-
if (insertionPoint === ctx.lastRevisedRunAnchor) {
|
|
434
|
-
return {
|
|
435
|
-
newLastParagraph: atom.sourceParagraphElement ?? ctx.lastProcessedParagraph,
|
|
436
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
newLastRun: insertionPoint,
|
|
441
|
-
newLastRevisedRunAnchor: insertionPoint,
|
|
442
|
-
newLastParagraph: atom.sourceParagraphElement ?? ctx.lastProcessedParagraph,
|
|
443
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
// For empty paragraphs (no sourceRunElement), the atom comes from the ORIGINAL tree!
|
|
447
|
-
// We must NOT use atom.sourceParagraphElement for position tracking in inplace mode.
|
|
448
|
-
// Instead, look up the corresponding REVISED paragraph from unifiedParaToElement.
|
|
449
|
-
if (atom.paragraphIndex !== undefined) {
|
|
450
|
-
// Look up the revised paragraph for this unified paragraph index
|
|
451
|
-
const revisedParagraph = ctx.unifiedParaToElement.get(atom.paragraphIndex);
|
|
452
|
-
// IMPORTANT: When we move to a new paragraph (empty or not), we MUST reset
|
|
453
|
-
// lastProcessedRun to null. Otherwise, subsequent inserts might use a stale
|
|
454
|
-
// run from a previous paragraph, causing content to be inserted in the wrong place.
|
|
455
|
-
// Setting newLastRun to null explicitly resets it.
|
|
456
|
-
return {
|
|
457
|
-
newLastRun: null, // Reset - we're in a new paragraph with no runs yet
|
|
458
|
-
newLastRevisedRunAnchor: null,
|
|
459
|
-
// Use the revised paragraph (not the original's sourceParagraphElement!)
|
|
460
|
-
newLastParagraph: revisedParagraph ?? ctx.lastProcessedParagraph,
|
|
461
|
-
newLastParagraphIndex: atom.paragraphIndex,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
return {};
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Strategy map for handling atoms by correlation status.
|
|
468
|
-
* This pattern makes it easy to add new status types without modifying processAtoms.
|
|
469
|
-
*/
|
|
470
|
-
const ATOM_HANDLERS = {
|
|
471
|
-
[CorrelationStatus.Inserted]: handleInserted,
|
|
472
|
-
[CorrelationStatus.Deleted]: handleDeleted,
|
|
473
|
-
[CorrelationStatus.MovedSource]: handleMovedSource,
|
|
474
|
-
[CorrelationStatus.MovedDestination]: handleMovedDestination,
|
|
475
|
-
[CorrelationStatus.FormatChanged]: handleFormatChanged,
|
|
476
|
-
[CorrelationStatus.Equal]: handleEqual,
|
|
477
|
-
[CorrelationStatus.Unknown]: handleEqual,
|
|
478
|
-
};
|
|
479
|
-
function processAtoms(mergedAtoms, _originalAtoms, revisedAtoms, author, dateStr, state, revisedRoot) {
|
|
480
|
-
const body = findChild(revisedRoot, 'w:body');
|
|
481
|
-
if (!body) {
|
|
482
|
-
warn('inPlaceModifier', 'Cannot process atoms: no w:body element found');
|
|
483
|
-
// Return a minimal context to avoid callers having to handle undefined.
|
|
484
|
-
return {
|
|
485
|
-
author,
|
|
486
|
-
dateStr,
|
|
487
|
-
state,
|
|
488
|
-
body: revisedRoot,
|
|
489
|
-
lastProcessedRun: null,
|
|
490
|
-
lastRevisedRunAnchor: null,
|
|
491
|
-
lastProcessedParagraph: null,
|
|
492
|
-
lastParagraphIndex: undefined,
|
|
493
|
-
unifiedParaToElement: new Map(),
|
|
494
|
-
revisedParagraphToUnifiedIndex: new Map(),
|
|
495
|
-
fullyInsertedParagraphIndices: new Set(),
|
|
496
|
-
createdParagraphs: new Map(),
|
|
497
|
-
createdParagraphLastRun: new Map(),
|
|
498
|
-
createdParagraphTrailingBookmarks: new Map(),
|
|
499
|
-
lastParaByContainer: new Map(),
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
// Build map from unified paragraph index to revised paragraph element.
|
|
503
|
-
// This tells us which paragraphs exist in the revised document.
|
|
504
|
-
// Revised atoms have their paragraphIndex already set to unified indices
|
|
505
|
-
// after assignUnifiedParagraphIndices was called.
|
|
506
|
-
const unifiedParaToElement = new Map();
|
|
507
|
-
const revisedParagraphToUnifiedIndex = new Map();
|
|
508
|
-
for (const atom of revisedAtoms) {
|
|
509
|
-
if (atom.paragraphIndex !== undefined && atom.sourceParagraphElement) {
|
|
510
|
-
if (!unifiedParaToElement.has(atom.paragraphIndex)) {
|
|
511
|
-
unifiedParaToElement.set(atom.paragraphIndex, atom.sourceParagraphElement);
|
|
512
|
-
}
|
|
513
|
-
if (!revisedParagraphToUnifiedIndex.has(atom.sourceParagraphElement)) {
|
|
514
|
-
revisedParagraphToUnifiedIndex.set(atom.sourceParagraphElement, atom.paragraphIndex);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
const atomsByPara = new Map();
|
|
519
|
-
for (const atom of mergedAtoms) {
|
|
520
|
-
if (atom.paragraphIndex === undefined)
|
|
521
|
-
continue;
|
|
522
|
-
const existing = atomsByPara.get(atom.paragraphIndex) ?? [];
|
|
523
|
-
existing.push(atom);
|
|
524
|
-
atomsByPara.set(atom.paragraphIndex, existing);
|
|
525
|
-
}
|
|
526
|
-
const fullyInsertedParagraphIndices = new Set();
|
|
527
|
-
for (const [paraIdx, atoms] of atomsByPara.entries()) {
|
|
528
|
-
if (isEntireParagraphAtomsWithStatus(atoms, CorrelationStatus.Inserted)) {
|
|
529
|
-
fullyInsertedParagraphIndices.add(paraIdx);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
// Initialize processing context with position tracking
|
|
533
|
-
const ctx = {
|
|
534
|
-
author,
|
|
535
|
-
dateStr,
|
|
536
|
-
state,
|
|
537
|
-
body,
|
|
538
|
-
lastProcessedRun: null,
|
|
539
|
-
lastRevisedRunAnchor: null,
|
|
540
|
-
lastProcessedParagraph: null,
|
|
541
|
-
lastParagraphIndex: undefined,
|
|
542
|
-
unifiedParaToElement,
|
|
543
|
-
revisedParagraphToUnifiedIndex,
|
|
544
|
-
fullyInsertedParagraphIndices,
|
|
545
|
-
createdParagraphs: new Map(),
|
|
546
|
-
createdParagraphLastRun: new Map(),
|
|
547
|
-
createdParagraphTrailingBookmarks: new Map(),
|
|
548
|
-
lastParaByContainer: new Map(),
|
|
549
|
-
};
|
|
550
|
-
// Reorder atoms so consecutive deletions precede consecutive insertions.
|
|
551
|
-
// This produces grouped tracked changes (all <w:del> then all <w:ins>)
|
|
552
|
-
// instead of alternating word-by-word del/ins pairs.
|
|
553
|
-
const reorderedAtoms = groupDeletionsBeforeInsertions(mergedAtoms);
|
|
554
|
-
for (const atom of reorderedAtoms) {
|
|
555
|
-
const handler = ATOM_HANDLERS[atom.correlationStatus];
|
|
556
|
-
const result = handler(atom, ctx);
|
|
557
|
-
// Update position tracking based on handler result
|
|
558
|
-
if (result.newLastRun !== undefined) {
|
|
559
|
-
ctx.lastProcessedRun = result.newLastRun;
|
|
560
|
-
}
|
|
561
|
-
if (result.newLastRevisedRunAnchor !== undefined) {
|
|
562
|
-
ctx.lastRevisedRunAnchor = result.newLastRevisedRunAnchor;
|
|
563
|
-
}
|
|
564
|
-
if (result.newLastParagraph !== undefined) {
|
|
565
|
-
ctx.lastProcessedParagraph = result.newLastParagraph;
|
|
566
|
-
// Track per-container anchor for container-aware insertion (issue #65)
|
|
567
|
-
if (result.newLastParagraph) {
|
|
568
|
-
const container = result.newLastParagraph.parentNode;
|
|
569
|
-
if (container) {
|
|
570
|
-
ctx.lastParaByContainer.set(container, result.newLastParagraph);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
if (result.newLastParagraphIndex !== undefined) {
|
|
575
|
-
ctx.lastParagraphIndex = result.newLastParagraphIndex;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
finalizeCreatedParagraphTrailingBookmarks(ctx);
|
|
579
|
-
return ctx;
|
|
580
|
-
}
|
|
581
|
-
function finalizeCreatedParagraphTrailingBookmarks(ctx) {
|
|
582
|
-
for (const [paraIdx, markers] of ctx.createdParagraphTrailingBookmarks.entries()) {
|
|
583
|
-
if (markers.length === 0)
|
|
584
|
-
continue;
|
|
585
|
-
const paragraph = ctx.createdParagraphs.get(paraIdx);
|
|
586
|
-
if (!paragraph)
|
|
587
|
-
continue;
|
|
588
|
-
let anchor = ctx.createdParagraphLastRun.get(paraIdx) ?? null;
|
|
589
|
-
if (!anchor) {
|
|
590
|
-
const pPr = findChildByTagName(paragraph, 'w:pPr');
|
|
591
|
-
const kids = childElements(paragraph);
|
|
592
|
-
const leadingBookmark = [...kids]
|
|
593
|
-
.reverse()
|
|
594
|
-
.find((c) => c.tagName === 'w:bookmarkStart') ?? null;
|
|
595
|
-
anchor = leadingBookmark ?? pPr;
|
|
596
|
-
}
|
|
597
|
-
if (!anchor) {
|
|
598
|
-
for (const marker of markers) {
|
|
599
|
-
paragraph.appendChild(marker);
|
|
600
|
-
}
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
let current = anchor;
|
|
604
|
-
for (const marker of markers) {
|
|
605
|
-
insertAfterElement(current, marker);
|
|
606
|
-
current = marker;
|
|
607
|
-
}
|
|
608
|
-
ctx.createdParagraphLastRun.set(paraIdx, current);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Apply whole-paragraph revision markers (w:pPr/w:rPr) based on merged atoms.
|
|
613
|
-
*
|
|
614
|
-
* This intentionally runs as a post-pass so the inplace algorithm can keep its
|
|
615
|
-
* fine-grained run edits while still enforcing Word/Aspose paragraph invariants.
|
|
616
|
-
*/
|
|
617
|
-
function applyWholeParagraphRevisionMarkers(mergedAtoms, ctx) {
|
|
618
|
-
const atomsByPara = new Map();
|
|
619
|
-
for (const atom of mergedAtoms) {
|
|
620
|
-
if (atom.paragraphIndex === undefined)
|
|
621
|
-
continue;
|
|
622
|
-
const list = atomsByPara.get(atom.paragraphIndex) ?? [];
|
|
623
|
-
list.push(atom);
|
|
624
|
-
atomsByPara.set(atom.paragraphIndex, list);
|
|
625
|
-
}
|
|
626
|
-
for (const [paraIdx, atoms] of atomsByPara.entries()) {
|
|
627
|
-
if (isEntireParagraphAtomsWithStatus(atoms, CorrelationStatus.Inserted)) {
|
|
628
|
-
const para = ctx.unifiedParaToElement.get(paraIdx);
|
|
629
|
-
if (para) {
|
|
630
|
-
wrapParagraphAsInserted(para, ctx.author, ctx.dateStr, ctx.state);
|
|
631
|
-
}
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
if (isEntireParagraphAtomsWithStatus(atoms, CorrelationStatus.Deleted)) {
|
|
635
|
-
const para = ctx.createdParagraphs.get(paraIdx) ?? ctx.unifiedParaToElement.get(paraIdx);
|
|
636
|
-
if (para) {
|
|
637
|
-
wrapParagraphAsDeleted(para, ctx.author, ctx.dateStr, ctx.state);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
export { createRevisionIdState, } from './inPlaceModifier-shared.js';
|
|
643
|
-
export { ContainerResolutionError, getContainerPath, resolveContainerInRevised, validateContainerTopology, } from './inPlaceModifier-containers.js';
|
|
644
|
-
export { addFormatChange, addParagraphPropertyChange, runHasVisibleContent, wrapAsDeleted, wrapAsInserted, wrapAsMoveFrom, wrapAsMoveTo, wrapParagraphAsDeleted, wrapParagraphAsInserted, } from './inPlaceModifier-wrappers.js';
|
|
645
|
-
export { insertDeletedParagraph, insertDeletedRun, insertMoveFromRun, } from './inPlaceModifier-deletion.js';
|
|
646
|
-
export { preSplitInterleavedWordRuns, preSplitMixedStatusRuns, } from './inPlaceModifier-presplit.js';
|
|
647
|
-
export { coalesceDelInsPairChains, groupDeletionsBeforeInsertions, isNoOpPair, mergeWhitespaceBridgedTrackChanges, suppressNoOpChangePairs, } from './inPlaceModifier-postprocess.js';
|
|
648
|
-
//# sourceMappingURL=inPlaceModifier.js.map
|