@usejunior/odf-core 0.9.1
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/dist/.tsbuildinfo +1 -0
- package/dist/comments.d.ts +55 -0
- package/dist/comments.d.ts.map +1 -0
- package/dist/comments.js +230 -0
- package/dist/comments.js.map +1 -0
- package/dist/compare/diff.d.ts +51 -0
- package/dist/compare/diff.d.ts.map +1 -0
- package/dist/compare/diff.js +189 -0
- package/dist/compare/diff.js.map +1 -0
- package/dist/compare/emit.d.ts +64 -0
- package/dist/compare/emit.d.ts.map +1 -0
- package/dist/compare/emit.js +404 -0
- package/dist/compare/emit.js.map +1 -0
- package/dist/compare/index.d.ts +49 -0
- package/dist/compare/index.d.ts.map +1 -0
- package/dist/compare/index.js +59 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/compare/inline_diff.d.ts +32 -0
- package/dist/compare/inline_diff.d.ts.map +1 -0
- package/dist/compare/inline_diff.js +102 -0
- package/dist/compare/inline_diff.js.map +1 -0
- package/dist/compare/inline_map.d.ts +40 -0
- package/dist/compare/inline_map.d.ts.map +1 -0
- package/dist/compare/inline_map.js +153 -0
- package/dist/compare/inline_map.js.map +1 -0
- package/dist/document.d.ts +98 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +172 -0
- package/dist/document.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/odf_archive_safety.d.ts +18 -0
- package/dist/odf_archive_safety.d.ts.map +1 -0
- package/dist/odf_archive_safety.js +72 -0
- package/dist/odf_archive_safety.js.map +1 -0
- package/dist/shared/odf/OdfArchive.d.ts +50 -0
- package/dist/shared/odf/OdfArchive.d.ts.map +1 -0
- package/dist/shared/odf/OdfArchive.js +118 -0
- package/dist/shared/odf/OdfArchive.js.map +1 -0
- package/dist/shared/odf/blocks.d.ts +15 -0
- package/dist/shared/odf/blocks.d.ts.map +1 -0
- package/dist/shared/odf/blocks.js +36 -0
- package/dist/shared/odf/blocks.js.map +1 -0
- package/dist/shared/odf/namespaces.d.ts +27 -0
- package/dist/shared/odf/namespaces.d.ts.map +1 -0
- package/dist/shared/odf/namespaces.js +29 -0
- package/dist/shared/odf/namespaces.js.map +1 -0
- package/dist/shared/odf/text_segments.d.ts +63 -0
- package/dist/shared/odf/text_segments.d.ts.map +1 -0
- package/dist/shared/odf/text_segments.js +90 -0
- package/dist/shared/odf/text_segments.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ODF tracked-changes emitter: paragraph-granularity (Slice 1) + intra-paragraph modify pairs
|
|
3
|
+
* (issue #356).
|
|
4
|
+
*
|
|
5
|
+
* Mutates the REVISED `content.xml` DOM in place, adding a `text:tracked-changes` container (first
|
|
6
|
+
* child of `office:text`) plus the lightweight in-body markers that reference it. The exact markup
|
|
7
|
+
* shapes were confirmed by driving LibreOffice to author each change and inspecting its output
|
|
8
|
+
* (see the `add-odf-compare` and `add-odf-intra-paragraph-compare` design notes):
|
|
9
|
+
*
|
|
10
|
+
* - Insertion: `text:change-start` / `text:change-end` brackets the inserted run, referencing a
|
|
11
|
+
* `text:insertion` region; inserted content stays inline. Forward (a following kept paragraph
|
|
12
|
+
* exists): start at the inserted run's first paragraph, end at the following paragraph's start.
|
|
13
|
+
* End-of-document: start at the preceding paragraph's end, end at the inserted run's last
|
|
14
|
+
* paragraph's end.
|
|
15
|
+
* - Deletion (paragraph-break merge): the deleted paragraphs live out-of-line in a `text:deletion`
|
|
16
|
+
* region; an inline `text:change` point marker sits in the nearest SURVIVING paragraph — at the
|
|
17
|
+
* start of the following one (forward) or the end of the preceding one (backward, for a run
|
|
18
|
+
* reaching end-of-document). Consecutive deletions coalesce into ONE region with all deleted
|
|
19
|
+
* paragraphs plus one empty merge-artifact paragraph (artifact last for forward, first for
|
|
20
|
+
* backward). `text:change` is inline and is never a direct block child of `office:text`.
|
|
21
|
+
* - Modify pair (intra-paragraph): the revised paragraph stays in place. An inserted span keeps
|
|
22
|
+
* its content inline bracketed by `text:change-start`/`text:change-end`; a deleted span leaves
|
|
23
|
+
* one `text:change` point marker and its content is stored out-of-line in a `text:deletion`
|
|
24
|
+
* region holding ONE block mirroring the host (`text:p`/`text:h` + style/outline-level) — no
|
|
25
|
+
* merge-artifact paragraph (no paragraph break died). A replace orders the insertion bracket
|
|
26
|
+
* first and the deletion point after its `text:change-end` (LibreOffice's authored order); at
|
|
27
|
+
* one offset the document order is `change-end`, `change`, `change-start`. A pair whose spans
|
|
28
|
+
* cannot be mapped degrades to the Slice-1 whole-paragraph delete+insert, decided before any
|
|
29
|
+
* markup is written.
|
|
30
|
+
* - A run with no surviving paragraph to anchor to (every paragraph deleted) fails closed.
|
|
31
|
+
*
|
|
32
|
+
* All element creation/matching is by `namespaceURI` + `localName` (ODF prefixes are not guaranteed).
|
|
33
|
+
*/
|
|
34
|
+
import { ODF_NS } from '../shared/odf/namespaces.js';
|
|
35
|
+
import { buildSegments } from '../shared/odf/text_segments.js';
|
|
36
|
+
import { diffInline } from './inline_diff.js';
|
|
37
|
+
import { OdfMapError, extractVisibleRange, resolveOffset } from './inline_map.js';
|
|
38
|
+
export class OdfEmitError extends Error {
|
|
39
|
+
}
|
|
40
|
+
/** Apply the tracked-changes markup for `ops` to `revisedDoc`. */
|
|
41
|
+
export function emitTrackedChanges(params) {
|
|
42
|
+
const { revisedDoc, revisedBlocks, originalBlocks, ops, author, date } = params;
|
|
43
|
+
const m = revisedBlocks.length;
|
|
44
|
+
const officeText = firstElementNS(revisedDoc, ODF_NS.OFFICE, 'text');
|
|
45
|
+
if (!officeText)
|
|
46
|
+
throw new OdfEmitError('No office:text element in revised content.xml.');
|
|
47
|
+
// --- Lane 2/3 pre-pass: plan every modify pair PURELY (diff + content extraction, no DOM
|
|
48
|
+
// mutation of the body). A pair that cannot be planned degrades to whole-paragraph
|
|
49
|
+
// delete+insert here, BEFORE any markup exists — no partial inline state is possible. The
|
|
50
|
+
// mutating half of placement (`resolveOffset`) is deferred to the marker phase: it is total
|
|
51
|
+
// for in-range offsets, and every placement offset comes from `diffInline` over the same
|
|
52
|
+
// visible string it will be resolved against.
|
|
53
|
+
const drafts = new Map();
|
|
54
|
+
const degraded = new Set();
|
|
55
|
+
for (let k = 0; k < ops.length; k++) {
|
|
56
|
+
const op = ops[k];
|
|
57
|
+
if (op.kind !== 'modify')
|
|
58
|
+
continue;
|
|
59
|
+
try {
|
|
60
|
+
const originalBlock = originalBlocks[op.originalIndex];
|
|
61
|
+
const revisedBlock = revisedBlocks[op.revisedIndex];
|
|
62
|
+
// Out-of-range indices are an engine bug, not a mapping limitation: fail closed (degrading
|
|
63
|
+
// would just crash later when lane 1 dereferences the same missing block).
|
|
64
|
+
if (!originalBlock || !revisedBlock)
|
|
65
|
+
throw new OdfEmitError(`modify pair indices out of range at op ${k}`);
|
|
66
|
+
const origVisible = buildSegments(originalBlock).visible;
|
|
67
|
+
const revVisible = buildSegments(revisedBlock).visible;
|
|
68
|
+
const spans = diffInline(origVisible, revVisible);
|
|
69
|
+
const deleteContents = new Map();
|
|
70
|
+
let changed = 0;
|
|
71
|
+
for (let s = 0; s < spans.length; s++) {
|
|
72
|
+
const span = spans[s];
|
|
73
|
+
if (span.kind === 'equal')
|
|
74
|
+
continue;
|
|
75
|
+
changed++;
|
|
76
|
+
if (span.kind === 'delete') {
|
|
77
|
+
deleteContents.set(s, extractVisibleRange(originalBlock, span.origStart, span.origEnd, revisedDoc));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// A pair with identical visible text gets no draft: nothing to mark up, nothing to count.
|
|
81
|
+
if (changed > 0)
|
|
82
|
+
drafts.set(k, { spans, deleteContents });
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err instanceof OdfMapError) {
|
|
86
|
+
degraded.add(k);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// --- Lane 1: group whole-paragraph ops into runs, allocating ids in document order. A modify
|
|
94
|
+
// paragraph is a SURVIVOR for anchoring: it advances the revised cursor exactly like `equal`
|
|
95
|
+
// (so a preceding delete run anchors its point marker at the modified paragraph's start) and
|
|
96
|
+
// terminates any open run. Degraded pairs re-route here as a delete run + insert run at the
|
|
97
|
+
// same slot (the Slice-1 replacement shape).
|
|
98
|
+
const alloc = makeIdAllocator(revisedDoc);
|
|
99
|
+
const insertRuns = [];
|
|
100
|
+
const deleteRuns = [];
|
|
101
|
+
const modifyPlans = [];
|
|
102
|
+
let revisedCursor = 0;
|
|
103
|
+
let k = 0;
|
|
104
|
+
while (k < ops.length) {
|
|
105
|
+
const op = ops[k];
|
|
106
|
+
if (op.kind === 'equal') {
|
|
107
|
+
revisedCursor = op.revisedIndex + 1;
|
|
108
|
+
k++;
|
|
109
|
+
}
|
|
110
|
+
else if (op.kind === 'insert') {
|
|
111
|
+
const a = op.revisedIndex;
|
|
112
|
+
let b = a;
|
|
113
|
+
while (k + 1 < ops.length && ops[k + 1].kind === 'insert') {
|
|
114
|
+
k++;
|
|
115
|
+
b = ops[k].revisedIndex;
|
|
116
|
+
}
|
|
117
|
+
insertRuns.push({ a, b, id: alloc() });
|
|
118
|
+
revisedCursor = b + 1;
|
|
119
|
+
k++;
|
|
120
|
+
}
|
|
121
|
+
else if (op.kind === 'delete') {
|
|
122
|
+
const originalIndices = [op.originalIndex];
|
|
123
|
+
while (k + 1 < ops.length && ops[k + 1].kind === 'delete') {
|
|
124
|
+
k++;
|
|
125
|
+
originalIndices.push(ops[k].originalIndex);
|
|
126
|
+
}
|
|
127
|
+
// A deletion run with no surviving revised paragraph anywhere cannot be anchored inline.
|
|
128
|
+
if (m === 0) {
|
|
129
|
+
throw new OdfEmitError('Cannot emit a deletion when the revised document has no paragraphs to anchor the change marker to.');
|
|
130
|
+
}
|
|
131
|
+
deleteRuns.push({ originalIndices, revisedCursor, id: alloc() });
|
|
132
|
+
k++;
|
|
133
|
+
}
|
|
134
|
+
else if (op.kind === 'modify') {
|
|
135
|
+
if (degraded.has(k)) {
|
|
136
|
+
if (m === 0) {
|
|
137
|
+
throw new OdfEmitError('Cannot emit a deletion when the revised document has no paragraphs to anchor the change marker to.');
|
|
138
|
+
}
|
|
139
|
+
deleteRuns.push({ originalIndices: [op.originalIndex], revisedCursor, id: alloc() });
|
|
140
|
+
insertRuns.push({ a: op.revisedIndex, b: op.revisedIndex, id: alloc() });
|
|
141
|
+
}
|
|
142
|
+
else if (drafts.has(k)) {
|
|
143
|
+
modifyPlans.push(finalizeModifyPlan(op.originalIndex, op.revisedIndex, drafts.get(k), alloc));
|
|
144
|
+
}
|
|
145
|
+
// No-op pairs (identical visible text) emit nothing.
|
|
146
|
+
revisedCursor = op.revisedIndex + 1;
|
|
147
|
+
k++;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
throw new OdfEmitError(`Unknown edit op kind at index ${k}.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const result = {
|
|
154
|
+
modifications: modifyPlans.length,
|
|
155
|
+
degradedModifications: degraded.size,
|
|
156
|
+
inlineInsertions: modifyPlans.reduce((n, p) => n + p.regions.filter((r) => r.kind === 'insert').length, 0),
|
|
157
|
+
inlineDeletions: modifyPlans.reduce((n, p) => n + p.regions.filter((r) => r.kind === 'delete').length, 0),
|
|
158
|
+
};
|
|
159
|
+
// Identical documents: emit nothing (no empty container).
|
|
160
|
+
if (insertRuns.length === 0 && deleteRuns.length === 0 && modifyPlans.length === 0)
|
|
161
|
+
return result;
|
|
162
|
+
// --- Create the changed-region definitions, in ascending id (document) order.
|
|
163
|
+
const tracked = ensureTrackedChanges(revisedDoc, officeText);
|
|
164
|
+
const allRegions = [
|
|
165
|
+
...insertRuns.map((run) => ({ id: run.id, build: () => makeInsertionRegion(revisedDoc, run.id, author, date) })),
|
|
166
|
+
...deleteRuns.map((dr) => ({
|
|
167
|
+
id: dr.id,
|
|
168
|
+
build: () => {
|
|
169
|
+
const forward = dr.revisedCursor < m;
|
|
170
|
+
const deletedPs = dr.originalIndices.map((i) => revisedDoc.importNode(originalBlocks[i], true));
|
|
171
|
+
const artifact = makeEmptyParagraph(revisedDoc, originalBlocks[dr.originalIndices[0]]);
|
|
172
|
+
const stored = forward ? [...deletedPs, artifact] : [artifact, ...deletedPs];
|
|
173
|
+
return makeDeletionRegion(revisedDoc, dr.id, author, date, stored);
|
|
174
|
+
},
|
|
175
|
+
})),
|
|
176
|
+
...modifyPlans.flatMap((plan) => plan.regions.map((region) => ({
|
|
177
|
+
id: region.id,
|
|
178
|
+
build: () => region.kind === 'insert'
|
|
179
|
+
? makeInsertionRegion(revisedDoc, region.id, author, date)
|
|
180
|
+
: makeDeletionRegion(revisedDoc, region.id, author, date, [
|
|
181
|
+
makeBlockMirror(revisedDoc, originalBlocks[plan.originalIndex], region.content),
|
|
182
|
+
]),
|
|
183
|
+
}))),
|
|
184
|
+
].sort((x, y) => idNum(x.id) - idNum(y.id));
|
|
185
|
+
for (const entry of allRegions)
|
|
186
|
+
tracked.appendChild(entry.build());
|
|
187
|
+
// --- Place intra-paragraph markers FIRST: whole-paragraph markers are prepended afterwards, so
|
|
188
|
+
// at a shared paragraph start the whole-paragraph `text:change` serializes BEFORE intra markers.
|
|
189
|
+
for (const plan of modifyPlans) {
|
|
190
|
+
placeModifyMarkers(revisedDoc, revisedBlocks[plan.revisedIndex], plan.placements);
|
|
191
|
+
}
|
|
192
|
+
// --- Place whole-paragraph insertion markers (before deletion markers, as in Slice 1, so a
|
|
193
|
+
// co-located deletion marker can be prepended before them).
|
|
194
|
+
for (const run of insertRuns) {
|
|
195
|
+
placeInsertionMarkers(revisedDoc, revisedBlocks, run, m);
|
|
196
|
+
}
|
|
197
|
+
// --- Place whole-paragraph deletion point markers.
|
|
198
|
+
for (const run of deleteRuns) {
|
|
199
|
+
const forward = run.revisedCursor < m;
|
|
200
|
+
const marker = makeMarker(revisedDoc, 'change', run.id);
|
|
201
|
+
if (forward) {
|
|
202
|
+
prepend(revisedBlocks[run.revisedCursor], marker);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
revisedBlocks[m - 1].appendChild(marker);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Turn a draft into a concrete plan: allocate one region id per changed span in offset order and
|
|
212
|
+
* compute marker placements. A delete span anchors at its revised offset; when it is immediately
|
|
213
|
+
* followed by an insert span (a replacement — both sit at the same revised offset), the point
|
|
214
|
+
* marker bumps past the insertion to its `change-end` offset, matching LibreOffice's authored
|
|
215
|
+
* replace shape (insertion bracket first, deletion point after).
|
|
216
|
+
*/
|
|
217
|
+
function finalizeModifyPlan(originalIndex, revisedIndex, draft, alloc) {
|
|
218
|
+
const placements = [];
|
|
219
|
+
const regions = [];
|
|
220
|
+
for (let s = 0; s < draft.spans.length; s++) {
|
|
221
|
+
const span = draft.spans[s];
|
|
222
|
+
if (span.kind === 'equal')
|
|
223
|
+
continue;
|
|
224
|
+
const id = alloc();
|
|
225
|
+
if (span.kind === 'insert') {
|
|
226
|
+
regions.push({ kind: 'insert', id });
|
|
227
|
+
placements.push({ offset: span.revStart, type: 'change-start', id });
|
|
228
|
+
placements.push({ offset: span.revEnd, type: 'change-end', id });
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
regions.push({ kind: 'delete', id, content: draft.deleteContents.get(s) });
|
|
232
|
+
const next = draft.spans[s + 1];
|
|
233
|
+
const anchor = next && next.kind === 'insert' ? next.revEnd : span.revStart;
|
|
234
|
+
placements.push({ offset: anchor, type: 'change', id });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { originalIndex, revisedIndex, placements, regions };
|
|
238
|
+
}
|
|
239
|
+
/** Document order of co-located markers: `change-end`, then `change`, then `change-start`. */
|
|
240
|
+
const MARKER_RANK = { 'change-end': 0, change: 1, 'change-start': 2 };
|
|
241
|
+
/**
|
|
242
|
+
* Insert a modify pair's markers into its revised paragraph. Offset groups are processed in
|
|
243
|
+
* DESCENDING offset order — marker insertion is zero visible width and `resolveOffset`
|
|
244
|
+
* re-segments per call, so placements at lower offsets stay valid across splits made at higher
|
|
245
|
+
* ones. Each offset group is resolved once and its markers inserted sequentially at that point.
|
|
246
|
+
*/
|
|
247
|
+
function placeModifyMarkers(doc, block, placements) {
|
|
248
|
+
const groups = new Map();
|
|
249
|
+
for (const p of placements) {
|
|
250
|
+
const group = groups.get(p.offset) ?? [];
|
|
251
|
+
group.push(p);
|
|
252
|
+
groups.set(p.offset, group);
|
|
253
|
+
}
|
|
254
|
+
const offsets = [...groups.keys()].sort((a, b) => b - a);
|
|
255
|
+
for (const offset of offsets) {
|
|
256
|
+
const group = groups.get(offset).sort((a, b) => MARKER_RANK[a.type] - MARKER_RANK[b.type]);
|
|
257
|
+
const point = resolveOffset(block, offset);
|
|
258
|
+
for (const p of group) {
|
|
259
|
+
point.parent.insertBefore(makeMarker(doc, p.type, p.id), point.before);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function placeInsertionMarkers(doc, revisedBlocks, run, m) {
|
|
264
|
+
const start = makeMarker(doc, 'change-start', run.id);
|
|
265
|
+
const end = makeMarker(doc, 'change-end', run.id);
|
|
266
|
+
const hasFollowing = run.b + 1 < m;
|
|
267
|
+
const hasPreceding = run.a - 1 >= 0;
|
|
268
|
+
if (hasFollowing) {
|
|
269
|
+
// Forward bracket: start of inserted run … start of following paragraph.
|
|
270
|
+
prepend(revisedBlocks[run.a], start);
|
|
271
|
+
prepend(revisedBlocks[run.b + 1], end);
|
|
272
|
+
}
|
|
273
|
+
else if (hasPreceding) {
|
|
274
|
+
// End-of-document: end of preceding paragraph … end of inserted run.
|
|
275
|
+
revisedBlocks[run.a - 1].appendChild(start);
|
|
276
|
+
revisedBlocks[run.b].appendChild(end);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// Entire revised document is inserted: bracket from the first paragraph's start to the last's end.
|
|
280
|
+
prepend(revisedBlocks[run.a], start);
|
|
281
|
+
revisedBlocks[run.b].appendChild(end);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// --- DOM helpers -------------------------------------------------------------------------------
|
|
285
|
+
function firstElementNS(doc, ns, local) {
|
|
286
|
+
const els = doc.getElementsByTagNameNS(ns, local);
|
|
287
|
+
return els.item(0) ?? null;
|
|
288
|
+
}
|
|
289
|
+
function prepend(block, node) {
|
|
290
|
+
block.insertBefore(node, block.firstChild);
|
|
291
|
+
}
|
|
292
|
+
/** Get the `text:tracked-changes` first child of `officeText`, creating it if absent. */
|
|
293
|
+
function ensureTrackedChanges(doc, officeText) {
|
|
294
|
+
for (let child = officeText.firstChild; child; child = child.nextSibling) {
|
|
295
|
+
if (child.nodeType === 1 &&
|
|
296
|
+
child.namespaceURI === ODF_NS.TEXT &&
|
|
297
|
+
child.localName === 'tracked-changes') {
|
|
298
|
+
return child;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const tracked = doc.createElementNS(ODF_NS.TEXT, 'text:tracked-changes');
|
|
302
|
+
officeText.insertBefore(tracked, officeText.firstChild);
|
|
303
|
+
return tracked;
|
|
304
|
+
}
|
|
305
|
+
function makeChangeInfo(doc, author, date) {
|
|
306
|
+
const info = doc.createElementNS(ODF_NS.OFFICE, 'office:change-info');
|
|
307
|
+
const creator = doc.createElementNS(ODF_NS.DC, 'dc:creator');
|
|
308
|
+
creator.appendChild(doc.createTextNode(author));
|
|
309
|
+
info.appendChild(creator);
|
|
310
|
+
const d = doc.createElementNS(ODF_NS.DC, 'dc:date');
|
|
311
|
+
d.appendChild(doc.createTextNode(date));
|
|
312
|
+
info.appendChild(d);
|
|
313
|
+
return info;
|
|
314
|
+
}
|
|
315
|
+
function makeChangedRegion(doc, id) {
|
|
316
|
+
const region = doc.createElementNS(ODF_NS.TEXT, 'text:changed-region');
|
|
317
|
+
region.setAttributeNS(ODF_NS.XML, 'xml:id', id);
|
|
318
|
+
region.setAttributeNS(ODF_NS.TEXT, 'text:id', id);
|
|
319
|
+
return region;
|
|
320
|
+
}
|
|
321
|
+
function makeInsertionRegion(doc, id, author, date) {
|
|
322
|
+
const region = makeChangedRegion(doc, id);
|
|
323
|
+
const insertion = doc.createElementNS(ODF_NS.TEXT, 'text:insertion');
|
|
324
|
+
insertion.appendChild(makeChangeInfo(doc, author, date));
|
|
325
|
+
region.appendChild(insertion);
|
|
326
|
+
return region;
|
|
327
|
+
}
|
|
328
|
+
function makeDeletionRegion(doc, id, author, date, stored) {
|
|
329
|
+
const region = makeChangedRegion(doc, id);
|
|
330
|
+
const deletion = doc.createElementNS(ODF_NS.TEXT, 'text:deletion');
|
|
331
|
+
deletion.appendChild(makeChangeInfo(doc, author, date));
|
|
332
|
+
for (const p of stored)
|
|
333
|
+
deletion.appendChild(p);
|
|
334
|
+
region.appendChild(deletion);
|
|
335
|
+
return region;
|
|
336
|
+
}
|
|
337
|
+
/** An empty `text:p` merge artifact, inheriting the deleted paragraph's style when present. */
|
|
338
|
+
function makeEmptyParagraph(doc, modelBlock) {
|
|
339
|
+
const p = doc.createElementNS(ODF_NS.TEXT, 'text:p');
|
|
340
|
+
const style = modelBlock.getAttributeNS(ODF_NS.TEXT, 'style-name') ?? modelBlock.getAttribute('text:style-name');
|
|
341
|
+
if (style)
|
|
342
|
+
p.setAttributeNS(ODF_NS.TEXT, 'text:style-name', style);
|
|
343
|
+
return p;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* The storage block for an inline deletion: mirrors the host block's element name and identity
|
|
347
|
+
* attributes (`text:style-name`; `text:outline-level` for headings — the LibreOffice-authored
|
|
348
|
+
* O10 shape), holding the extracted deleted content.
|
|
349
|
+
*/
|
|
350
|
+
function makeBlockMirror(doc, hostBlock, content) {
|
|
351
|
+
const local = hostBlock.localName === 'h' ? 'text:h' : 'text:p';
|
|
352
|
+
const block = doc.createElementNS(ODF_NS.TEXT, local);
|
|
353
|
+
const style = hostBlock.getAttributeNS(ODF_NS.TEXT, 'style-name') ?? hostBlock.getAttribute('text:style-name');
|
|
354
|
+
if (style)
|
|
355
|
+
block.setAttributeNS(ODF_NS.TEXT, 'text:style-name', style);
|
|
356
|
+
const outline = hostBlock.getAttributeNS(ODF_NS.TEXT, 'outline-level') ?? hostBlock.getAttribute('text:outline-level');
|
|
357
|
+
if (outline)
|
|
358
|
+
block.setAttributeNS(ODF_NS.TEXT, 'text:outline-level', outline);
|
|
359
|
+
for (const n of content)
|
|
360
|
+
block.appendChild(n);
|
|
361
|
+
return block;
|
|
362
|
+
}
|
|
363
|
+
function makeMarker(doc, local, id) {
|
|
364
|
+
const el = doc.createElementNS(ODF_NS.TEXT, `text:${local}`);
|
|
365
|
+
el.setAttributeNS(ODF_NS.TEXT, 'text:change-id', id);
|
|
366
|
+
return el;
|
|
367
|
+
}
|
|
368
|
+
const CT_ID_RE = /^ct(\d+)$/;
|
|
369
|
+
function idNum(id) {
|
|
370
|
+
const m = CT_ID_RE.exec(id);
|
|
371
|
+
return m ? Number.parseInt(m[1], 10) : 0;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Allocate fresh `ct<n>` change ids that collide with no existing `xml:id` / `text:id` on the
|
|
375
|
+
* revised document's `text:changed-region`s (so reused/pre-existing tracked-changes are preserved).
|
|
376
|
+
*/
|
|
377
|
+
function makeIdAllocator(doc) {
|
|
378
|
+
const used = new Set();
|
|
379
|
+
const regions = doc.getElementsByTagNameNS(ODF_NS.TEXT, 'changed-region');
|
|
380
|
+
let max = 0;
|
|
381
|
+
for (let i = 0; i < regions.length; i++) {
|
|
382
|
+
const r = regions.item(i);
|
|
383
|
+
for (const id of [
|
|
384
|
+
r.getAttributeNS(ODF_NS.XML, 'id') ?? r.getAttribute('xml:id'),
|
|
385
|
+
r.getAttributeNS(ODF_NS.TEXT, 'id') ?? r.getAttribute('text:id'),
|
|
386
|
+
]) {
|
|
387
|
+
if (!id)
|
|
388
|
+
continue;
|
|
389
|
+
used.add(id);
|
|
390
|
+
const mm = CT_ID_RE.exec(id);
|
|
391
|
+
if (mm)
|
|
392
|
+
max = Math.max(max, Number.parseInt(mm[1], 10));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
let next = max + 1;
|
|
396
|
+
return () => {
|
|
397
|
+
let id = `ct${next++}`;
|
|
398
|
+
while (used.has(id))
|
|
399
|
+
id = `ct${next++}`;
|
|
400
|
+
used.add(id);
|
|
401
|
+
return id;
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit.js","sourceRoot":"","sources":["../../src/compare/emit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ODF document comparison — tracked-changes redline at inline granularity.
|
|
3
|
+
*
|
|
4
|
+
* `compareOdf` is the public entry: it takes two `content.xml` STRINGS, parses each exactly once
|
|
5
|
+
* internally (no DOM Element crosses the package boundary, no public DOM accessor on
|
|
6
|
+
* `OdfDocument`), diffs the body paragraphs, emits ODF tracked-changes into the revised DOM, and
|
|
7
|
+
* returns the redline `content.xml` plus edit stats. Diff and emit live in separate modules so the
|
|
8
|
+
* diff is testable without round-tripping a `.odt`.
|
|
9
|
+
*
|
|
10
|
+
* Granularity: aligned-but-differing paragraph pairs above `similarityThreshold` are diffed
|
|
11
|
+
* WITHIN the paragraph (token-level) and emitted as inline tracked changes; below-threshold
|
|
12
|
+
* replacements and pure adds/removes keep the Slice-1 whole-paragraph shapes.
|
|
13
|
+
*/
|
|
14
|
+
export { OdfEmitError } from './emit.js';
|
|
15
|
+
/**
|
|
16
|
+
* Counts of changed-regions in the redline: whole-paragraph inserts/deletes count one per
|
|
17
|
+
* paragraph; a modified paragraph counts one `modifications` plus one insertion/deletion per
|
|
18
|
+
* inline span inside it; a degraded modify pair counts one insertion plus one deletion.
|
|
19
|
+
*/
|
|
20
|
+
export type OdfCompareStats = {
|
|
21
|
+
insertions: number;
|
|
22
|
+
deletions: number;
|
|
23
|
+
modifications: number;
|
|
24
|
+
};
|
|
25
|
+
export type OdfCompareResult = {
|
|
26
|
+
/** The redline `content.xml` (revised document + tracked-changes markup). */
|
|
27
|
+
contentXml: string;
|
|
28
|
+
stats: OdfCompareStats;
|
|
29
|
+
};
|
|
30
|
+
export type OdfCompareOptions = {
|
|
31
|
+
/** Change author for `dc:creator`. Defaults to `SafeDocX`. */
|
|
32
|
+
author?: string;
|
|
33
|
+
/** Change date; defaults to now. */
|
|
34
|
+
date?: Date;
|
|
35
|
+
/**
|
|
36
|
+
* Minimum Jaccard word-overlap for an aligned delete/insert pair to be treated as an
|
|
37
|
+
* in-place modification (intra-paragraph diff) instead of a whole-paragraph replacement.
|
|
38
|
+
* Defaults to `DEFAULT_ODF_SIMILARITY_THRESHOLD` (0.25).
|
|
39
|
+
*/
|
|
40
|
+
similarityThreshold?: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Compare two `content.xml` strings and produce a tracked-changes redline. The redline is built
|
|
44
|
+
* on the REVISED document (so its styles, manifest, and untouched paragraphs are preserved);
|
|
45
|
+
* deleted content — whole paragraphs and inline spans alike — is stored out-of-line in the
|
|
46
|
+
* tracked-changes container.
|
|
47
|
+
*/
|
|
48
|
+
export declare function compareOdf(originalContentXml: string, revisedContentXml: string, options?: OdfCompareOptions): OdfCompareResult;
|
|
49
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compare/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAOF;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAqClB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ODF document comparison — tracked-changes redline at inline granularity.
|
|
3
|
+
*
|
|
4
|
+
* `compareOdf` is the public entry: it takes two `content.xml` STRINGS, parses each exactly once
|
|
5
|
+
* internally (no DOM Element crosses the package boundary, no public DOM accessor on
|
|
6
|
+
* `OdfDocument`), diffs the body paragraphs, emits ODF tracked-changes into the revised DOM, and
|
|
7
|
+
* returns the redline `content.xml` plus edit stats. Diff and emit live in separate modules so the
|
|
8
|
+
* diff is testable without round-tripping a `.odt`.
|
|
9
|
+
*
|
|
10
|
+
* Granularity: aligned-but-differing paragraph pairs above `similarityThreshold` are diffed
|
|
11
|
+
* WITHIN the paragraph (token-level) and emitted as inline tracked changes; below-threshold
|
|
12
|
+
* replacements and pure adds/removes keep the Slice-1 whole-paragraph shapes.
|
|
13
|
+
*/
|
|
14
|
+
import { parseXml, serializeXml } from '@usejunior/docx-core';
|
|
15
|
+
import { collectBlocks } from '../shared/odf/blocks.js';
|
|
16
|
+
import { buildSegments } from '../shared/odf/text_segments.js';
|
|
17
|
+
import { DEFAULT_ODF_SIMILARITY_THRESHOLD, diffParagraphs, pairModifications } from './diff.js';
|
|
18
|
+
import { emitTrackedChanges } from './emit.js';
|
|
19
|
+
export { OdfEmitError } from './emit.js';
|
|
20
|
+
/** ODF `dc:date` value: ISO 8601, no fractional seconds or trailing `Z` (matches comments.ts). */
|
|
21
|
+
function odfDate(date) {
|
|
22
|
+
return date.toISOString().replace(/\.\d{3}Z$/, '');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Compare two `content.xml` strings and produce a tracked-changes redline. The redline is built
|
|
26
|
+
* on the REVISED document (so its styles, manifest, and untouched paragraphs are preserved);
|
|
27
|
+
* deleted content — whole paragraphs and inline spans alike — is stored out-of-line in the
|
|
28
|
+
* tracked-changes container.
|
|
29
|
+
*/
|
|
30
|
+
export function compareOdf(originalContentXml, revisedContentXml, options = {}) {
|
|
31
|
+
const author = options.author ?? 'SafeDocX';
|
|
32
|
+
const date = odfDate(options.date ?? new Date());
|
|
33
|
+
const similarityThreshold = options.similarityThreshold ?? DEFAULT_ODF_SIMILARITY_THRESHOLD;
|
|
34
|
+
const originalDoc = parseXml(originalContentXml);
|
|
35
|
+
const revisedDoc = parseXml(revisedContentXml);
|
|
36
|
+
const originalBlocks = [];
|
|
37
|
+
collectBlocks(originalDoc.documentElement, originalBlocks);
|
|
38
|
+
const revisedBlocks = [];
|
|
39
|
+
collectBlocks(revisedDoc.documentElement, revisedBlocks);
|
|
40
|
+
const originalTexts = originalBlocks.map((b) => buildSegments(b).visible);
|
|
41
|
+
const revisedTexts = revisedBlocks.map((b) => buildSegments(b).visible);
|
|
42
|
+
const ops = pairModifications(diffParagraphs(originalTexts, revisedTexts), originalTexts, revisedTexts, similarityThreshold);
|
|
43
|
+
const emitted = emitTrackedChanges({ revisedDoc, revisedBlocks, originalBlocks, ops, author, date });
|
|
44
|
+
// Whole-paragraph ops count one each; inline spans and degradations come from the emitter so
|
|
45
|
+
// the stats always describe the markup actually written.
|
|
46
|
+
let insertions = emitted.inlineInsertions + emitted.degradedModifications;
|
|
47
|
+
let deletions = emitted.inlineDeletions + emitted.degradedModifications;
|
|
48
|
+
for (const op of ops) {
|
|
49
|
+
if (op.kind === 'insert')
|
|
50
|
+
insertions++;
|
|
51
|
+
else if (op.kind === 'delete')
|
|
52
|
+
deletions++;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
contentXml: serializeXml(revisedDoc),
|
|
56
|
+
stats: { insertions, deletions, modifications: emitted.modifications },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/compare/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,gCAAgC,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAgCzC,kGAAkG;AAClG,SAAS,OAAO,CAAC,IAAU;IACzB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,kBAA0B,EAC1B,iBAAyB,EACzB,UAA6B,EAAE;IAE/B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC;IAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;IACjD,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,IAAI,gCAAgC,CAAC;IAE5F,MAAM,WAAW,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IAE/C,MAAM,cAAc,GAAc,EAAE,CAAC;IACrC,aAAa,CAAC,WAAW,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAc,EAAE,CAAC;IACpC,aAAa,CAAC,UAAU,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IAEzD,MAAM,aAAa,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC1E,MAAM,YAAY,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACxE,MAAM,GAAG,GAAG,iBAAiB,CAC3B,cAAc,CAAC,aAAa,EAAE,YAAY,CAAC,EAC3C,aAAa,EACb,YAAY,EACZ,mBAAmB,CACpB,CAAC;IAEF,MAAM,OAAO,GAAG,kBAAkB,CAAC,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAErG,6FAA6F;IAC7F,yDAAyD;IACzD,IAAI,UAAU,GAAG,OAAO,CAAC,gBAAgB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAC1E,IAAI,SAAS,GAAG,OAAO,CAAC,eAAe,GAAG,OAAO,CAAC,qBAAqB,CAAC;IACxE,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACrB,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ;YAAE,UAAU,EAAE,CAAC;aAClC,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAS,EAAE,CAAC;IAC7C,CAAC;IAED,OAAO;QACL,UAAU,EAAE,YAAY,CAAC,UAAU,CAAC;QACpC,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE;KACvE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure intra-paragraph diff for ODF comparison (issue #356).
|
|
3
|
+
*
|
|
4
|
+
* Token-level LCS over a single paragraph pair's visible text, returning char-offset spans.
|
|
5
|
+
* Tokens are maximal runs of whitespace or non-whitespace — a partition of the string — so
|
|
6
|
+
* every token boundary maps losslessly back to a character offset and the emitted spans land
|
|
7
|
+
* on clean word boundaries (no ragged mid-word matches). Common token prefix/suffix are
|
|
8
|
+
* trimmed before the O(N·M) DP, so the dominant case — one edited word in a long clause —
|
|
9
|
+
* costs close to the edit size, not the paragraph size.
|
|
10
|
+
*
|
|
11
|
+
* Order convention mirrors `diffParagraphs`: at a mismatch the deletion branch wins, so a
|
|
12
|
+
* replaced word surfaces as a `delete` immediately followed by an `insert` sharing `revStart`.
|
|
13
|
+
* The emitter relies on that ordering when both anchor at the same offset.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* One span of the intra-paragraph edit script, as half-open char offsets into the two visible
|
|
17
|
+
* strings. `insert` spans have `origStart === origEnd`; `delete` spans have
|
|
18
|
+
* `revStart === revEnd`; `equal` spans have the same length on both sides.
|
|
19
|
+
*/
|
|
20
|
+
export type SpanOp = {
|
|
21
|
+
kind: 'equal' | 'insert' | 'delete';
|
|
22
|
+
origStart: number;
|
|
23
|
+
origEnd: number;
|
|
24
|
+
revStart: number;
|
|
25
|
+
revEnd: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Diff two visible-text strings into an ordered span script.
|
|
29
|
+
* Adjacent same-kind spans are coalesced; zero-length spans are never emitted.
|
|
30
|
+
*/
|
|
31
|
+
export declare function diffInline(original: string, revised: string): SpanOp[];
|
|
32
|
+
//# sourceMappingURL=inline_diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline_diff.d.ts","sourceRoot":"","sources":["../../src/compare/inline_diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;GAIG;AACH,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAOF;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAgFtE"}
|