@adeu/core 1.6.2 → 1.6.5
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/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +38 -38
- package/src/comments.test.ts +37 -37
- package/src/comments.ts +450 -450
- package/src/diff.test.ts +61 -61
- package/src/diff.ts +250 -250
- package/src/docx/bridge.ts +188 -188
- package/src/docx/dom.ts +53 -53
- package/src/docx/primitives.ts +64 -64
- package/src/domain.ts +10 -10
- package/src/engine.atomic.test.ts +57 -57
- package/src/engine.batch.test.ts +92 -92
- package/src/engine.safety.test.ts +41 -41
- package/src/engine.tables.test.ts +165 -165
- package/src/engine.ts +734 -734
- package/src/index.test.ts +7 -7
- package/src/index.ts +13 -13
- package/src/ingest.test.ts +43 -43
- package/src/ingest.ts +399 -399
- package/src/mapper.test.ts +65 -65
- package/src/mapper.ts +834 -834
- package/src/markup.test.ts +149 -149
- package/src/markup.ts +322 -322
- package/src/models.ts +50 -50
- package/src/outline.ts +376 -376
- package/src/pagination.ts +238 -238
- package/src/test-utils.ts +141 -141
- package/src/utils/docx.ts +477 -477
- package/tsconfig.json +21 -21
- package/tsup.config.ts +9 -9
- package/vitest.config.ts +11 -11
|
@@ -1,166 +1,166 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createTestDocument, addParagraph, addTable, setCellText, mergeCells, addNestedTable } from './test-utils.js';
|
|
3
|
-
import { DocumentObject } from './docx/bridge.js';
|
|
4
|
-
import { extractTextFromBuffer } from './ingest.js';
|
|
5
|
-
import { RedlineEngine } from './engine.js';
|
|
6
|
-
import { ModifyText, InsertTableRow, DeleteTableRow, RejectChange } from './models.js';
|
|
7
|
-
|
|
8
|
-
describe('Table Interop & Engine (Node.js Port)', () => {
|
|
9
|
-
it('interleaved tables and text remain ordered', async () => {
|
|
10
|
-
const doc = await createTestDocument();
|
|
11
|
-
addParagraph(doc, "Section 1");
|
|
12
|
-
const tbl = addTable(doc, 1, 1);
|
|
13
|
-
setCellText(tbl, 0, 0, "TableContent");
|
|
14
|
-
addParagraph(doc, "Section 2");
|
|
15
|
-
|
|
16
|
-
const buf = await doc.save();
|
|
17
|
-
const text = await extractTextFromBuffer(buf);
|
|
18
|
-
|
|
19
|
-
expect(text).toContain("Section 1");
|
|
20
|
-
expect(text).toContain("TableContent");
|
|
21
|
-
expect(text).toContain("Section 2");
|
|
22
|
-
|
|
23
|
-
const p1 = text.indexOf("Section 1");
|
|
24
|
-
const tIdx = text.indexOf("TableContent");
|
|
25
|
-
const p2 = text.indexOf("Section 2");
|
|
26
|
-
|
|
27
|
-
expect(p1).toBeLessThan(tIdx);
|
|
28
|
-
expect(tIdx).toBeLessThan(p2);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('extracts and edits nested tables correctly', async () => {
|
|
32
|
-
const doc = await createTestDocument();
|
|
33
|
-
const outerTbl = addTable(doc, 1, 1);
|
|
34
|
-
|
|
35
|
-
const rows = Array.from(outerTbl.childNodes).filter(n => (n as Element).tagName === 'w:tr') as Element[];
|
|
36
|
-
const cells = Array.from(rows[0].childNodes).filter(n => (n as Element).tagName === 'w:tc') as Element[];
|
|
37
|
-
|
|
38
|
-
const nestedTbl = addNestedTable(cells[0], 1, 1);
|
|
39
|
-
setCellText(nestedTbl, 0, 0, "InnerSecret");
|
|
40
|
-
|
|
41
|
-
const buf = await doc.save();
|
|
42
|
-
const text = await extractTextFromBuffer(buf);
|
|
43
|
-
expect(text).toContain("InnerSecret");
|
|
44
|
-
|
|
45
|
-
const midDoc = await DocumentObject.load(buf);
|
|
46
|
-
const engine = new RedlineEngine(midDoc);
|
|
47
|
-
const [applied] = engine.apply_edits([{ type: 'modify', target_text: "InnerSecret", new_text: "OuterSecret" } as ModifyText]);
|
|
48
|
-
expect(applied).toBe(1);
|
|
49
|
-
|
|
50
|
-
const finalBuf = await midDoc.save();
|
|
51
|
-
const final_text = await extractTextFromBuffer(finalBuf);
|
|
52
|
-
expect(final_text).toContain("{--InnerSecret--}{++OuterSecret++}");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('merged cells do not duplicate content extraction', async () => {
|
|
56
|
-
const doc = await createTestDocument();
|
|
57
|
-
const tbl = addTable(doc, 1, 2);
|
|
58
|
-
setCellText(tbl, 0, 0, "MergedUnique");
|
|
59
|
-
|
|
60
|
-
// Simulate python-docx's cell.merge(cell)
|
|
61
|
-
mergeCells(tbl, 0, 0, 1);
|
|
62
|
-
|
|
63
|
-
const buf = await doc.save();
|
|
64
|
-
const text = await extractTextFromBuffer(buf);
|
|
65
|
-
|
|
66
|
-
const count = (text.match(/MergedUnique/g) || []).length;
|
|
67
|
-
expect(count).toBe(1);
|
|
68
|
-
|
|
69
|
-
const midDoc = await DocumentObject.load(buf);
|
|
70
|
-
const engine = new RedlineEngine(midDoc);
|
|
71
|
-
const [applied] = engine.apply_edits([{ type: 'modify', target_text: "MergedUnique", new_text: "ChangedUnique" } as ModifyText]);
|
|
72
|
-
expect(applied).toBe(1);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('empty row mapping alignment stays synchronized', async () => {
|
|
76
|
-
const doc = await createTestDocument();
|
|
77
|
-
const tbl = addTable(doc, 3, 1);
|
|
78
|
-
setCellText(tbl, 0, 0, "RowA");
|
|
79
|
-
setCellText(tbl, 1, 0, ""); // Empty
|
|
80
|
-
setCellText(tbl, 2, 0, "RowB");
|
|
81
|
-
|
|
82
|
-
const buf = await doc.save();
|
|
83
|
-
const midDoc = await DocumentObject.load(buf);
|
|
84
|
-
|
|
85
|
-
const engine = new RedlineEngine(midDoc);
|
|
86
|
-
const [applied] = engine.apply_edits([{ type: 'modify', target_text: "RowB", new_text: "RowC" } as ModifyText]);
|
|
87
|
-
|
|
88
|
-
expect(applied).toBe(1);
|
|
89
|
-
|
|
90
|
-
const resBuf = await midDoc.save();
|
|
91
|
-
const resText = await extractTextFromBuffer(resBuf);
|
|
92
|
-
|
|
93
|
-
expect(resText).toContain("RowA");
|
|
94
|
-
expect(resText).toContain("{--RowB--}{++RowC++}");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('inserts table row below', async () => {
|
|
98
|
-
const doc = await createTestDocument();
|
|
99
|
-
const tbl = addTable(doc, 2, 2);
|
|
100
|
-
setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
|
|
101
|
-
setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
|
|
102
|
-
|
|
103
|
-
const buf = await doc.save();
|
|
104
|
-
const midDoc = await DocumentObject.load(buf);
|
|
105
|
-
|
|
106
|
-
const engine = new RedlineEngine(midDoc);
|
|
107
|
-
const stats = engine.process_batch([
|
|
108
|
-
{ type: 'insert_row', target_text: "A1 | A2", position: "below", cells: ["New B1", "New B2"] } as InsertTableRow
|
|
109
|
-
]);
|
|
110
|
-
|
|
111
|
-
expect(stats.edits_applied).toBe(1);
|
|
112
|
-
|
|
113
|
-
// Call accept_all_revisions (requires implementation in engine.ts)
|
|
114
|
-
(engine as any).accept_all_revisions();
|
|
115
|
-
const finalBuf = await midDoc.save();
|
|
116
|
-
const clean_text = await extractTextFromBuffer(finalBuf, true);
|
|
117
|
-
|
|
118
|
-
expect(clean_text).toContain("A1 | A2");
|
|
119
|
-
expect(clean_text).toContain("New B1 | New B2");
|
|
120
|
-
expect(clean_text).toContain("B1 | B2");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('deletes table row', async () => {
|
|
124
|
-
const doc = await createTestDocument();
|
|
125
|
-
const tbl = addTable(doc, 3, 2);
|
|
126
|
-
setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
|
|
127
|
-
setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
|
|
128
|
-
setCellText(tbl, 2, 0, "C1"); setCellText(tbl, 2, 1, "C2");
|
|
129
|
-
|
|
130
|
-
const buf = await doc.save();
|
|
131
|
-
const midDoc = await DocumentObject.load(buf);
|
|
132
|
-
|
|
133
|
-
const engine = new RedlineEngine(midDoc);
|
|
134
|
-
const stats = engine.process_batch([{ type: 'delete_row', target_text: "B1" } as DeleteTableRow]);
|
|
135
|
-
|
|
136
|
-
expect(stats.edits_applied).toBe(1);
|
|
137
|
-
|
|
138
|
-
(engine as any).accept_all_revisions();
|
|
139
|
-
const finalBuf = await midDoc.save();
|
|
140
|
-
const clean_text = await extractTextFromBuffer(finalBuf, true);
|
|
141
|
-
|
|
142
|
-
expect(clean_text).toContain("A1 | A2");
|
|
143
|
-
expect(clean_text).not.toContain("B1 | B2");
|
|
144
|
-
expect(clean_text).toContain("C1 | C2");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('clean view naturally omits deleted row', async () => {
|
|
148
|
-
const doc = await createTestDocument();
|
|
149
|
-
const tbl = addTable(doc, 2, 2);
|
|
150
|
-
setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
|
|
151
|
-
setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
|
|
152
|
-
|
|
153
|
-
const buf = await doc.save();
|
|
154
|
-
const midDoc = await DocumentObject.load(buf);
|
|
155
|
-
|
|
156
|
-
const engine = new RedlineEngine(midDoc);
|
|
157
|
-
engine.process_batch([{ type: 'delete_row', target_text: "B1" } as DeleteTableRow]);
|
|
158
|
-
|
|
159
|
-
// Do NOT accept revisions, extract as Clean View directly
|
|
160
|
-
const finalBuf = await midDoc.save();
|
|
161
|
-
const clean_text = await extractTextFromBuffer(finalBuf, true);
|
|
162
|
-
|
|
163
|
-
expect(clean_text).toContain("A1 | A2");
|
|
164
|
-
expect(clean_text).not.toContain("B1 | B2");
|
|
165
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { createTestDocument, addParagraph, addTable, setCellText, mergeCells, addNestedTable } from './test-utils.js';
|
|
3
|
+
import { DocumentObject } from './docx/bridge.js';
|
|
4
|
+
import { extractTextFromBuffer } from './ingest.js';
|
|
5
|
+
import { RedlineEngine } from './engine.js';
|
|
6
|
+
import { ModifyText, InsertTableRow, DeleteTableRow, RejectChange } from './models.js';
|
|
7
|
+
|
|
8
|
+
describe('Table Interop & Engine (Node.js Port)', () => {
|
|
9
|
+
it('interleaved tables and text remain ordered', async () => {
|
|
10
|
+
const doc = await createTestDocument();
|
|
11
|
+
addParagraph(doc, "Section 1");
|
|
12
|
+
const tbl = addTable(doc, 1, 1);
|
|
13
|
+
setCellText(tbl, 0, 0, "TableContent");
|
|
14
|
+
addParagraph(doc, "Section 2");
|
|
15
|
+
|
|
16
|
+
const buf = await doc.save();
|
|
17
|
+
const text = await extractTextFromBuffer(buf);
|
|
18
|
+
|
|
19
|
+
expect(text).toContain("Section 1");
|
|
20
|
+
expect(text).toContain("TableContent");
|
|
21
|
+
expect(text).toContain("Section 2");
|
|
22
|
+
|
|
23
|
+
const p1 = text.indexOf("Section 1");
|
|
24
|
+
const tIdx = text.indexOf("TableContent");
|
|
25
|
+
const p2 = text.indexOf("Section 2");
|
|
26
|
+
|
|
27
|
+
expect(p1).toBeLessThan(tIdx);
|
|
28
|
+
expect(tIdx).toBeLessThan(p2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('extracts and edits nested tables correctly', async () => {
|
|
32
|
+
const doc = await createTestDocument();
|
|
33
|
+
const outerTbl = addTable(doc, 1, 1);
|
|
34
|
+
|
|
35
|
+
const rows = Array.from(outerTbl.childNodes).filter(n => (n as Element).tagName === 'w:tr') as Element[];
|
|
36
|
+
const cells = Array.from(rows[0].childNodes).filter(n => (n as Element).tagName === 'w:tc') as Element[];
|
|
37
|
+
|
|
38
|
+
const nestedTbl = addNestedTable(cells[0], 1, 1);
|
|
39
|
+
setCellText(nestedTbl, 0, 0, "InnerSecret");
|
|
40
|
+
|
|
41
|
+
const buf = await doc.save();
|
|
42
|
+
const text = await extractTextFromBuffer(buf);
|
|
43
|
+
expect(text).toContain("InnerSecret");
|
|
44
|
+
|
|
45
|
+
const midDoc = await DocumentObject.load(buf);
|
|
46
|
+
const engine = new RedlineEngine(midDoc);
|
|
47
|
+
const [applied] = engine.apply_edits([{ type: 'modify', target_text: "InnerSecret", new_text: "OuterSecret" } as ModifyText]);
|
|
48
|
+
expect(applied).toBe(1);
|
|
49
|
+
|
|
50
|
+
const finalBuf = await midDoc.save();
|
|
51
|
+
const final_text = await extractTextFromBuffer(finalBuf);
|
|
52
|
+
expect(final_text).toContain("{--InnerSecret--}{++OuterSecret++}");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('merged cells do not duplicate content extraction', async () => {
|
|
56
|
+
const doc = await createTestDocument();
|
|
57
|
+
const tbl = addTable(doc, 1, 2);
|
|
58
|
+
setCellText(tbl, 0, 0, "MergedUnique");
|
|
59
|
+
|
|
60
|
+
// Simulate python-docx's cell.merge(cell)
|
|
61
|
+
mergeCells(tbl, 0, 0, 1);
|
|
62
|
+
|
|
63
|
+
const buf = await doc.save();
|
|
64
|
+
const text = await extractTextFromBuffer(buf);
|
|
65
|
+
|
|
66
|
+
const count = (text.match(/MergedUnique/g) || []).length;
|
|
67
|
+
expect(count).toBe(1);
|
|
68
|
+
|
|
69
|
+
const midDoc = await DocumentObject.load(buf);
|
|
70
|
+
const engine = new RedlineEngine(midDoc);
|
|
71
|
+
const [applied] = engine.apply_edits([{ type: 'modify', target_text: "MergedUnique", new_text: "ChangedUnique" } as ModifyText]);
|
|
72
|
+
expect(applied).toBe(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('empty row mapping alignment stays synchronized', async () => {
|
|
76
|
+
const doc = await createTestDocument();
|
|
77
|
+
const tbl = addTable(doc, 3, 1);
|
|
78
|
+
setCellText(tbl, 0, 0, "RowA");
|
|
79
|
+
setCellText(tbl, 1, 0, ""); // Empty
|
|
80
|
+
setCellText(tbl, 2, 0, "RowB");
|
|
81
|
+
|
|
82
|
+
const buf = await doc.save();
|
|
83
|
+
const midDoc = await DocumentObject.load(buf);
|
|
84
|
+
|
|
85
|
+
const engine = new RedlineEngine(midDoc);
|
|
86
|
+
const [applied] = engine.apply_edits([{ type: 'modify', target_text: "RowB", new_text: "RowC" } as ModifyText]);
|
|
87
|
+
|
|
88
|
+
expect(applied).toBe(1);
|
|
89
|
+
|
|
90
|
+
const resBuf = await midDoc.save();
|
|
91
|
+
const resText = await extractTextFromBuffer(resBuf);
|
|
92
|
+
|
|
93
|
+
expect(resText).toContain("RowA");
|
|
94
|
+
expect(resText).toContain("{--RowB--}{++RowC++}");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('inserts table row below', async () => {
|
|
98
|
+
const doc = await createTestDocument();
|
|
99
|
+
const tbl = addTable(doc, 2, 2);
|
|
100
|
+
setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
|
|
101
|
+
setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
|
|
102
|
+
|
|
103
|
+
const buf = await doc.save();
|
|
104
|
+
const midDoc = await DocumentObject.load(buf);
|
|
105
|
+
|
|
106
|
+
const engine = new RedlineEngine(midDoc);
|
|
107
|
+
const stats = engine.process_batch([
|
|
108
|
+
{ type: 'insert_row', target_text: "A1 | A2", position: "below", cells: ["New B1", "New B2"] } as InsertTableRow
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
expect(stats.edits_applied).toBe(1);
|
|
112
|
+
|
|
113
|
+
// Call accept_all_revisions (requires implementation in engine.ts)
|
|
114
|
+
(engine as any).accept_all_revisions();
|
|
115
|
+
const finalBuf = await midDoc.save();
|
|
116
|
+
const clean_text = await extractTextFromBuffer(finalBuf, true);
|
|
117
|
+
|
|
118
|
+
expect(clean_text).toContain("A1 | A2");
|
|
119
|
+
expect(clean_text).toContain("New B1 | New B2");
|
|
120
|
+
expect(clean_text).toContain("B1 | B2");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('deletes table row', async () => {
|
|
124
|
+
const doc = await createTestDocument();
|
|
125
|
+
const tbl = addTable(doc, 3, 2);
|
|
126
|
+
setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
|
|
127
|
+
setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
|
|
128
|
+
setCellText(tbl, 2, 0, "C1"); setCellText(tbl, 2, 1, "C2");
|
|
129
|
+
|
|
130
|
+
const buf = await doc.save();
|
|
131
|
+
const midDoc = await DocumentObject.load(buf);
|
|
132
|
+
|
|
133
|
+
const engine = new RedlineEngine(midDoc);
|
|
134
|
+
const stats = engine.process_batch([{ type: 'delete_row', target_text: "B1" } as DeleteTableRow]);
|
|
135
|
+
|
|
136
|
+
expect(stats.edits_applied).toBe(1);
|
|
137
|
+
|
|
138
|
+
(engine as any).accept_all_revisions();
|
|
139
|
+
const finalBuf = await midDoc.save();
|
|
140
|
+
const clean_text = await extractTextFromBuffer(finalBuf, true);
|
|
141
|
+
|
|
142
|
+
expect(clean_text).toContain("A1 | A2");
|
|
143
|
+
expect(clean_text).not.toContain("B1 | B2");
|
|
144
|
+
expect(clean_text).toContain("C1 | C2");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('clean view naturally omits deleted row', async () => {
|
|
148
|
+
const doc = await createTestDocument();
|
|
149
|
+
const tbl = addTable(doc, 2, 2);
|
|
150
|
+
setCellText(tbl, 0, 0, "A1"); setCellText(tbl, 0, 1, "A2");
|
|
151
|
+
setCellText(tbl, 1, 0, "B1"); setCellText(tbl, 1, 1, "B2");
|
|
152
|
+
|
|
153
|
+
const buf = await doc.save();
|
|
154
|
+
const midDoc = await DocumentObject.load(buf);
|
|
155
|
+
|
|
156
|
+
const engine = new RedlineEngine(midDoc);
|
|
157
|
+
engine.process_batch([{ type: 'delete_row', target_text: "B1" } as DeleteTableRow]);
|
|
158
|
+
|
|
159
|
+
// Do NOT accept revisions, extract as Clean View directly
|
|
160
|
+
const finalBuf = await midDoc.save();
|
|
161
|
+
const clean_text = await extractTextFromBuffer(finalBuf, true);
|
|
162
|
+
|
|
163
|
+
expect(clean_text).toContain("A1 | A2");
|
|
164
|
+
expect(clean_text).not.toContain("B1 | B2");
|
|
165
|
+
});
|
|
166
166
|
});
|