@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,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure paragraph-level diff for ODF comparison (Slice 1).
|
|
3
|
+
*
|
|
4
|
+
* A language-agnostic O(N·M) LCS over two arrays of paragraph visible-text strings, returning a
|
|
5
|
+
* structured edit script in standard diff order. No DOM — this mirrors docx-core's `atomLcs.ts`
|
|
6
|
+
* (kept separate from emission so it is testable in isolation).
|
|
7
|
+
*
|
|
8
|
+
* Order convention: a paragraph "replaced" in place (text differs at a matched slot) surfaces as
|
|
9
|
+
* a `delete` of the original immediately followed by an `insert` of the revised, because at a
|
|
10
|
+
* mismatch we prefer the deletion branch. The emitter relies on this delete-before-insert order
|
|
11
|
+
* when both anchor at the same position.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Diff two paragraph-text arrays into an ordered edit script.
|
|
15
|
+
* `equal` ops carry both indices; `insert` carries the revised index; `delete` the original index.
|
|
16
|
+
*/
|
|
17
|
+
export function diffParagraphs(original, revised) {
|
|
18
|
+
const n = original.length;
|
|
19
|
+
const m = revised.length;
|
|
20
|
+
// dp[i][j] = LCS length of original[i..] and revised[j..].
|
|
21
|
+
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
22
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
23
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
24
|
+
dp[i][j] =
|
|
25
|
+
original[i] === revised[j]
|
|
26
|
+
? dp[i + 1][j + 1] + 1
|
|
27
|
+
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const ops = [];
|
|
31
|
+
let i = 0;
|
|
32
|
+
let j = 0;
|
|
33
|
+
while (i < n && j < m) {
|
|
34
|
+
if (original[i] === revised[j]) {
|
|
35
|
+
ops.push({ kind: 'equal', originalIndex: i, revisedIndex: j });
|
|
36
|
+
i++;
|
|
37
|
+
j++;
|
|
38
|
+
}
|
|
39
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
40
|
+
// Prefer deletion at a tie so a replace surfaces as delete-then-insert.
|
|
41
|
+
ops.push({ kind: 'delete', originalIndex: i });
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
ops.push({ kind: 'insert', revisedIndex: j });
|
|
46
|
+
j++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
while (i < n)
|
|
50
|
+
ops.push({ kind: 'delete', originalIndex: i++ });
|
|
51
|
+
while (j < m)
|
|
52
|
+
ops.push({ kind: 'insert', revisedIndex: j++ });
|
|
53
|
+
return ops;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Below this Jaccard word-overlap, an aligned delete/insert pair is a replacement, not an
|
|
57
|
+
* in-place edit. Matches docx-core's `DEFAULT_PARAGRAPH_SIMILARITY_THRESHOLD` reference point.
|
|
58
|
+
*/
|
|
59
|
+
export const DEFAULT_ODF_SIMILARITY_THRESHOLD = 0.25;
|
|
60
|
+
/** Normalized word set for similarity: lowercase, punctuation stripped, whitespace collapsed. */
|
|
61
|
+
function similarityWords(text) {
|
|
62
|
+
return new Set(text
|
|
63
|
+
.replace(/[^\w\s]/g, ' ')
|
|
64
|
+
.toLowerCase()
|
|
65
|
+
.split(/\s+/)
|
|
66
|
+
.filter((w) => w.length > 0));
|
|
67
|
+
}
|
|
68
|
+
/** Jaccard word overlap; 0 when either side has no words (empty paragraphs never pair). */
|
|
69
|
+
function jaccard(a, b) {
|
|
70
|
+
if (a.size === 0 || b.size === 0)
|
|
71
|
+
return 0;
|
|
72
|
+
let intersection = 0;
|
|
73
|
+
for (const w of a)
|
|
74
|
+
if (b.has(w))
|
|
75
|
+
intersection++;
|
|
76
|
+
return intersection / (a.size + b.size - intersection);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Post-pass over a `diffParagraphs` script: inside each gap (a run of deletes followed by a run
|
|
80
|
+
* of inserts between two anchors — the order `diffParagraphs`'s tie-break guarantees), convert
|
|
81
|
+
* similar delete/insert pairs into `modify` ops.
|
|
82
|
+
*
|
|
83
|
+
* Pairing is an order-constrained DP, deterministic by construction: maximize the pair count,
|
|
84
|
+
* then the total Jaccard similarity; a pair is admissible only when similarity ≥
|
|
85
|
+
* `similarityThreshold`. At ties, pairing beats skipping, and skipping a delete beats skipping
|
|
86
|
+
* an insert (keeps the delete-first convention). Output preserves merged document order:
|
|
87
|
+
* between pairs, unpaired deletes precede unpaired inserts, exactly as Slice 1 emitted them.
|
|
88
|
+
*/
|
|
89
|
+
export function pairModifications(ops, original, revised, similarityThreshold = DEFAULT_ODF_SIMILARITY_THRESHOLD) {
|
|
90
|
+
const out = [];
|
|
91
|
+
let gapDeletes = [];
|
|
92
|
+
let gapInserts = [];
|
|
93
|
+
const flushGap = () => {
|
|
94
|
+
if (gapDeletes.length === 0 || gapInserts.length === 0) {
|
|
95
|
+
for (const d of gapDeletes)
|
|
96
|
+
out.push({ kind: 'delete', originalIndex: d });
|
|
97
|
+
for (const ins of gapInserts)
|
|
98
|
+
out.push({ kind: 'insert', revisedIndex: ins });
|
|
99
|
+
gapDeletes = [];
|
|
100
|
+
gapInserts = [];
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const delWords = gapDeletes.map((d) => similarityWords(original[d] ?? ''));
|
|
104
|
+
const insWords = gapInserts.map((ins) => similarityWords(revised[ins] ?? ''));
|
|
105
|
+
const sim = (a, b) => jaccard(delWords[a], insWords[b]);
|
|
106
|
+
// dp[a][b] = best (pairCount, simSum) over gapDeletes[a..] × gapInserts[b..].
|
|
107
|
+
const d = gapDeletes.length;
|
|
108
|
+
const m2 = gapInserts.length;
|
|
109
|
+
const count = Array.from({ length: d + 1 }, () => new Array(m2 + 1).fill(0));
|
|
110
|
+
const sum = Array.from({ length: d + 1 }, () => new Array(m2 + 1).fill(0));
|
|
111
|
+
for (let a = d - 1; a >= 0; a--) {
|
|
112
|
+
for (let b = m2 - 1; b >= 0; b--) {
|
|
113
|
+
let bestCount = count[a + 1][b];
|
|
114
|
+
let bestSum = sum[a + 1][b];
|
|
115
|
+
if (count[a][b + 1] > bestCount || (count[a][b + 1] === bestCount && sum[a][b + 1] > bestSum)) {
|
|
116
|
+
bestCount = count[a][b + 1];
|
|
117
|
+
bestSum = sum[a][b + 1];
|
|
118
|
+
}
|
|
119
|
+
const s = sim(a, b);
|
|
120
|
+
if (s >= similarityThreshold) {
|
|
121
|
+
const pairCount = count[a + 1][b + 1] + 1;
|
|
122
|
+
const pairSum = sum[a + 1][b + 1] + s;
|
|
123
|
+
if (pairCount > bestCount || (pairCount === bestCount && pairSum > bestSum)) {
|
|
124
|
+
bestCount = pairCount;
|
|
125
|
+
bestSum = pairSum;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
count[a][b] = bestCount;
|
|
129
|
+
sum[a][b] = bestSum;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Backtrack with the stated preference order: pair > skip-delete > skip-insert. Skipped ops
|
|
133
|
+
// buffer until the next pair (or the end) so each segment emits deletes-then-inserts, the
|
|
134
|
+
// same per-gap order Slice 1 produced.
|
|
135
|
+
let a = 0;
|
|
136
|
+
let b = 0;
|
|
137
|
+
let pendingDeletes = [];
|
|
138
|
+
let pendingInserts = [];
|
|
139
|
+
const flushPending = () => {
|
|
140
|
+
for (const pd of pendingDeletes)
|
|
141
|
+
out.push({ kind: 'delete', originalIndex: pd });
|
|
142
|
+
for (const pi of pendingInserts)
|
|
143
|
+
out.push({ kind: 'insert', revisedIndex: pi });
|
|
144
|
+
pendingDeletes = [];
|
|
145
|
+
pendingInserts = [];
|
|
146
|
+
};
|
|
147
|
+
while (a < d && b < m2) {
|
|
148
|
+
const s = sim(a, b);
|
|
149
|
+
const pairCount = s >= similarityThreshold ? count[a + 1][b + 1] + 1 : -1;
|
|
150
|
+
const pairSum = s >= similarityThreshold ? sum[a + 1][b + 1] + s : 0;
|
|
151
|
+
if (pairCount === count[a][b] && pairSum === sum[a][b]) {
|
|
152
|
+
flushPending();
|
|
153
|
+
out.push({ kind: 'modify', originalIndex: gapDeletes[a], revisedIndex: gapInserts[b] });
|
|
154
|
+
a++;
|
|
155
|
+
b++;
|
|
156
|
+
}
|
|
157
|
+
else if (count[a + 1][b] === count[a][b] && sum[a + 1][b] === sum[a][b]) {
|
|
158
|
+
pendingDeletes.push(gapDeletes[a]);
|
|
159
|
+
a++;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
pendingInserts.push(gapInserts[b]);
|
|
163
|
+
b++;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
while (a < d)
|
|
167
|
+
pendingDeletes.push(gapDeletes[a++]);
|
|
168
|
+
while (b < m2)
|
|
169
|
+
pendingInserts.push(gapInserts[b++]);
|
|
170
|
+
flushPending();
|
|
171
|
+
gapDeletes = [];
|
|
172
|
+
gapInserts = [];
|
|
173
|
+
};
|
|
174
|
+
for (const op of ops) {
|
|
175
|
+
if (op.kind === 'delete') {
|
|
176
|
+
gapDeletes.push(op.originalIndex);
|
|
177
|
+
}
|
|
178
|
+
else if (op.kind === 'insert') {
|
|
179
|
+
gapInserts.push(op.revisedIndex);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
flushGap();
|
|
183
|
+
out.push(op);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
flushGap();
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/compare/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
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 type { EditOp } from './diff.js';
|
|
35
|
+
export type EmitParams = {
|
|
36
|
+
/** The revised `content.xml` DOM; mutated in place. */
|
|
37
|
+
revisedDoc: Document;
|
|
38
|
+
/** Revised body paragraphs in document order (excludes `text:tracked-changes`). */
|
|
39
|
+
revisedBlocks: Element[];
|
|
40
|
+
/** Original body paragraphs in document order (source of deleted content). */
|
|
41
|
+
originalBlocks: Element[];
|
|
42
|
+
/** The edit script from `diffParagraphs(original, revised)`. */
|
|
43
|
+
ops: EditOp[];
|
|
44
|
+
/** Change author for `dc:creator`. */
|
|
45
|
+
author: string;
|
|
46
|
+
/** Change date for `dc:date` (ODF 8601, no fractional seconds). */
|
|
47
|
+
date: string;
|
|
48
|
+
};
|
|
49
|
+
export declare class OdfEmitError extends Error {
|
|
50
|
+
}
|
|
51
|
+
/** Per-emission accounting so reported stats always match the emitted markup. */
|
|
52
|
+
export type EmitResult = {
|
|
53
|
+
/** Modify pairs that survived as inline markup. */
|
|
54
|
+
modifications: number;
|
|
55
|
+
/** Modify pairs that fell back to whole-paragraph delete+insert. */
|
|
56
|
+
degradedModifications: number;
|
|
57
|
+
/** Inserted spans inside surviving modify pairs (one `text:insertion` region each). */
|
|
58
|
+
inlineInsertions: number;
|
|
59
|
+
/** Deleted spans inside surviving modify pairs (one `text:deletion` region each). */
|
|
60
|
+
inlineDeletions: number;
|
|
61
|
+
};
|
|
62
|
+
/** Apply the tracked-changes markup for `ops` to `revisedDoc`. */
|
|
63
|
+
export declare function emitTrackedChanges(params: EmitParams): EmitResult;
|
|
64
|
+
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/compare/emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAIxC,MAAM,MAAM,UAAU,GAAG;IACvB,uDAAuD;IACvD,UAAU,EAAE,QAAQ,CAAC;IACrB,mFAAmF;IACnF,aAAa,EAAE,OAAO,EAAE,CAAC;IACzB,8EAA8E;IAC9E,cAAc,EAAE,OAAO,EAAE,CAAC;IAC1B,gEAAgE;IAChE,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,qBAAa,YAAa,SAAQ,KAAK;CAAG;AAE1C,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG;IACvB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,uFAAuF;IACvF,gBAAgB,EAAE,MAAM,CAAC;IACzB,qFAAqF;IACrF,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAyBF,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAuKjE"}
|