@docx4j/core-ts 0.3.2 → 0.5.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.
Files changed (72) hide show
  1. package/README.md +8 -3
  2. package/dist/html/convert.d.mts +116 -0
  3. package/dist/html/convert.mjs +565 -0
  4. package/dist/html/elements.d.mts +19 -0
  5. package/dist/html/elements.mjs +182 -0
  6. package/dist/html/index.d.mts +2 -0
  7. package/dist/html/index.mjs +4 -0
  8. package/dist/model/content/Body.d.mts +13 -1
  9. package/dist/model/content/Body.mjs +97 -19
  10. package/dist/model/content/Comment.mjs +7 -0
  11. package/dist/model/content/InlinePicture.d.mts +89 -10
  12. package/dist/model/content/InlinePicture.mjs +245 -35
  13. package/dist/model/content/List.mjs +10 -1
  14. package/dist/model/content/Paragraph.d.mts +28 -5
  15. package/dist/model/content/Paragraph.mjs +77 -37
  16. package/dist/model/content/Range.d.mts +18 -2
  17. package/dist/model/content/Range.mjs +194 -64
  18. package/dist/model/content/Table.d.mts +38 -0
  19. package/dist/model/content/Table.mjs +80 -2
  20. package/dist/model/content/TrackedChange.d.mts +129 -3
  21. package/dist/model/content/TrackedChange.mjs +577 -12
  22. package/dist/model/content/binder.d.mts +2 -1
  23. package/dist/model/content/binder.mjs +48 -1
  24. package/dist/model/content/index.d.mts +4 -3
  25. package/dist/model/content/index.mjs +3 -2
  26. package/dist/model/content/relationships.d.mts +30 -0
  27. package/dist/model/content/relationships.mjs +133 -0
  28. package/dist/model/content/stylesFacade.d.mts +4 -2
  29. package/dist/model/content/stylesFacade.mjs +20 -6
  30. package/dist/model/content/tableColumns.d.mts +48 -0
  31. package/dist/model/content/tableColumns.mjs +484 -0
  32. package/dist/model/content/tree.d.mts +52 -2
  33. package/dist/model/content/tree.mjs +48 -3
  34. package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
  35. package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
  36. package/dist/model/customxml/bindings.d.mts +5 -2
  37. package/dist/model/customxml/bindings.mjs +21 -6
  38. package/dist/model/customxml/index.d.mts +3 -0
  39. package/dist/model/customxml/index.mjs +3 -0
  40. package/dist/model/customxml/opendope.d.mts +32 -0
  41. package/dist/model/customxml/opendope.mjs +56 -0
  42. package/dist/model/customxml/pictures.d.mts +57 -0
  43. package/dist/model/customxml/pictures.mjs +192 -0
  44. package/dist/model/customxml/xhtml.d.mts +42 -0
  45. package/dist/model/customxml/xhtml.mjs +162 -0
  46. package/dist/model/fonts/families.generated.mjs +2 -2
  47. package/dist/model/fonts/registry.mjs +7 -1
  48. package/dist/model/fonts/substitutions.generated.mjs +2 -2
  49. package/dist/model/fonts/themes.generated.mjs +1 -1
  50. package/dist/model/listnumbering/definitions.mjs +18 -0
  51. package/dist/model/properties/PropertyResolver.d.mts +27 -6
  52. package/dist/model/properties/PropertyResolver.mjs +83 -16
  53. package/dist/model/properties/catalogue.d.mts +15 -1
  54. package/dist/model/properties/catalogue.mjs +80 -27
  55. package/dist/model/properties/table.d.mts +16 -2
  56. package/dist/model/properties/table.mjs +13 -3
  57. package/dist/opc/Load.d.mts +9 -2
  58. package/dist/opc/index.d.mts +2 -2
  59. package/dist/opc/index.mjs +2 -2
  60. package/dist/opc/mce/preprocessor.d.mts +62 -6
  61. package/dist/opc/mce/preprocessor.mjs +142 -35
  62. package/dist/opc/mce/understood.d.mts +10 -0
  63. package/dist/opc/mce/understood.mjs +25 -3
  64. package/dist/packages/OpcPackage.d.mts +7 -0
  65. package/dist/packages/OpcPackage.mjs +15 -3
  66. package/dist/packages/WordprocessingMLPackage.d.mts +35 -2
  67. package/dist/packages/WordprocessingMLPackage.mjs +99 -2
  68. package/dist/parts/XmlPart.d.mts +26 -5
  69. package/dist/parts/XmlPart.mjs +67 -18
  70. package/dist/parts/wml/knownStyles.d.mts +1 -0
  71. package/dist/parts/wml/knownStyles.mjs +2233 -0
  72. package/package.json +7 -2
@@ -0,0 +1,484 @@
1
+ import { deepCopy } from '@docx4j/generated-objects-ts';
2
+ import { tc as cellElementOf } from '@docx4j/generated-objects-ts/builders/wml';
3
+ import { Docx4JException } from '../../opc/exceptions.mjs';
4
+ import { rowsOf, cellsOf, childrenOf, linkParents, paragraphOf } from './tree.mjs';
5
+ import { wrapInsertedRuns } from './tracking.mjs';
6
+ /** The default text width in twips (A4 with 2.54 cm margins), as the objects package's `tbl` builder uses. */
7
+ export const DEFAULT_TEXT_WIDTH = 9026;
8
+ /** A table's width in fiftieths of a percent: `w:tblW w:type="pct" w:w="5000"` is the text width. */
9
+ const FULL_PCT = 5000;
10
+ /** Which of Word's three layout modes a table is in, read as Word's Table Properties reads the file. */
11
+ export function layoutModeOf(tbl) {
12
+ var _a, _b, _c, _d;
13
+ if (((_b = (_a = tbl.tblPr) === null || _a === void 0 ? void 0 : _a.tblLayout) === null || _b === void 0 ? void 0 : _b.type) === 'fixed')
14
+ return 'fixed';
15
+ if (((_d = (_c = tbl.tblPr) === null || _c === void 0 ? void 0 : _c.tblW) === null || _d === void 0 ? void 0 : _d.type) === 'pct')
16
+ return 'window';
17
+ return 'contents';
18
+ }
19
+ /** The section's text width in twips: the page less its margins and gutter, else the default. */
20
+ export function textWidthTwips(container) {
21
+ var _a, _b, _c, _d, _e, _f;
22
+ const sectPr = container === null || container === void 0 ? void 0 : container.sectPr;
23
+ const pgSz = sectPr === null || sectPr === void 0 ? void 0 : sectPr.pgSz;
24
+ if (!(pgSz === null || pgSz === void 0 ? void 0 : pgSz.w))
25
+ return DEFAULT_TEXT_WIDTH;
26
+ const width = pgSz.w - ((_b = (_a = sectPr === null || sectPr === void 0 ? void 0 : sectPr.pgMar) === null || _a === void 0 ? void 0 : _a.left) !== null && _b !== void 0 ? _b : 0) - ((_d = (_c = sectPr === null || sectPr === void 0 ? void 0 : sectPr.pgMar) === null || _c === void 0 ? void 0 : _c.right) !== null && _d !== void 0 ? _d : 0) - ((_f = (_e = sectPr === null || sectPr === void 0 ? void 0 : sectPr.pgMar) === null || _e === void 0 ? void 0 : _e.gutter) !== null && _f !== void 0 ? _f : 0);
27
+ return width > 0 ? width : DEFAULT_TEXT_WIDTH;
28
+ }
29
+ function rowMember(tr, name) {
30
+ var _a, _b;
31
+ return ((_b = (_a = tr.trPr) === null || _a === void 0 ? void 0 : _a.cnfStyleOrDivIdOrGridBefore) !== null && _b !== void 0 ? _b : []).find((item) => { var _a; return ((_a = item.name) === null || _a === void 0 ? void 0 : _a.localPart) === name; });
32
+ }
33
+ function spanOf(tc) {
34
+ var _a, _b, _c;
35
+ return Number((_c = (_b = (_a = tc.tcPr) === null || _a === void 0 ? void 0 : _a.gridSpan) === null || _b === void 0 ? void 0 : _b.val) !== null && _c !== void 0 ? _c : 1) || 1;
36
+ }
37
+ function setSpan(tc, count) {
38
+ var _a, _b;
39
+ const tcPr = ((_a = tc.tcPr) !== null && _a !== void 0 ? _a : (tc.tcPr = {}));
40
+ if (count > 1)
41
+ tcPr.gridSpan = { ...((_b = tcPr.gridSpan) !== null && _b !== void 0 ? _b : {}), val: count };
42
+ else
43
+ delete tcPr.gridSpan;
44
+ }
45
+ /** The grid's column widths in twips; a table without a `w:tblGrid` gets one from its first row (equal columns over the text width when the cells state none). */
46
+ export function gridWidthsOf(tbl, textWidth) {
47
+ var _a, _b, _c, _d;
48
+ const grid = (_a = tbl.tblGrid) === null || _a === void 0 ? void 0 : _a.gridCol;
49
+ if (grid && grid.length > 0)
50
+ return grid.map((col) => { var _a; return Number((_a = col.w) !== null && _a !== void 0 ? _a : 0); });
51
+ const first = rowsOf(tbl)[0];
52
+ const cells = first ? cellsOf(first.element.value) : [];
53
+ const columns = Math.max(1, cells.reduce((n, cell) => n + spanOf(cell.element.value), 0));
54
+ const each = Math.floor(textWidth / columns);
55
+ const widths = [];
56
+ for (const cell of cells) {
57
+ const tc = cell.element.value;
58
+ const span = spanOf(tc);
59
+ const stated = ((_c = (_b = tc.tcPr) === null || _b === void 0 ? void 0 : _b.tcW) === null || _c === void 0 ? void 0 : _c.type) === 'dxa' ? Number((_d = tc.tcPr.tcW.w) !== null && _d !== void 0 ? _d : 0) : 0;
60
+ for (let i = 0; i < span; i++)
61
+ widths.push(stated > 0 ? Math.floor(stated / span) : each);
62
+ }
63
+ return widths.length > 0 ? widths : [each];
64
+ }
65
+ function writeGrid(tbl, widths) {
66
+ var _a;
67
+ const grid = ((_a = tbl.tblGrid) !== null && _a !== void 0 ? _a : (tbl.tblGrid = {}));
68
+ grid.gridCol = widths.map((w) => ({ TYPE_NAME: 'org_docx4j_wml.TblGridCol', w }));
69
+ linkParents(grid, tbl);
70
+ }
71
+ /**
72
+ * Widths rescaled to a total: each floored, the remainder given to the columns with the largest
73
+ * fractions, the earlier first on a tie, so that equal columns inserted and then removed come
74
+ * back equal (Word's own grid, 3005/3006/3006, is its layout's measurement and not reproduced).
75
+ */
76
+ function rescaled(widths, total) {
77
+ const sum = widths.reduce((a, b) => a + b, 0);
78
+ if (sum <= 0 || total <= 0)
79
+ return widths.slice();
80
+ const exact = widths.map((w) => (w * total) / sum);
81
+ const out = exact.map((w) => Math.floor(w));
82
+ let remainder = total - out.reduce((a, b) => a + b, 0);
83
+ const order = exact.map((w, i) => ({ i, fraction: w - Math.floor(w) })).sort((a, b) => b.fraction - a.fraction || a.i - b.i);
84
+ for (const { i } of order) {
85
+ if (remainder <= 0)
86
+ break;
87
+ out[i] += 1;
88
+ remainder--;
89
+ }
90
+ return out;
91
+ }
92
+ /** A cell's share of the table in fiftieths of a percent over grid columns [start, end): the cumulative shares floored, as Word writes them. */
93
+ function pctShare(widths, start, end) {
94
+ const total = widths.reduce((a, b) => a + b, 0);
95
+ if (total <= 0)
96
+ return 0;
97
+ const cumulative = (to) => Math.floor((FULL_PCT * widths.slice(0, to).reduce((a, b) => a + b, 0)) / total);
98
+ return cumulative(end) - cumulative(start);
99
+ }
100
+ /** The form a cell's width takes: its own `w:tcW` type, else the table's `w:tblW` type, else twips (section 42.2 item 4). */
101
+ export function widthTypeOf(tc, tbl) {
102
+ var _a, _b, _c, _d, _e, _f;
103
+ return (_f = (_c = (_b = (_a = tc === null || tc === void 0 ? void 0 : tc.tcPr) === null || _a === void 0 ? void 0 : _a.tcW) === null || _b === void 0 ? void 0 : _b.type) !== null && _c !== void 0 ? _c : (_e = (_d = tbl.tblPr) === null || _d === void 0 ? void 0 : _d.tblW) === null || _e === void 0 ? void 0 : _e.type) !== null && _f !== void 0 ? _f : 'dxa';
104
+ }
105
+ /** Writes a cell's `w:tcW` for the grid columns it covers, in the form it takes; `auto` and `nil` widths are left as they are. */
106
+ function writeCellWidth(tc, tbl, widths, start, end) {
107
+ var _a, _b, _c;
108
+ const type = widthTypeOf(tc, tbl);
109
+ const tcPr = ((_a = tc.tcPr) !== null && _a !== void 0 ? _a : (tc.tcPr = {}));
110
+ if (type === 'pct')
111
+ tcPr.tcW = { ...((_b = tcPr.tcW) !== null && _b !== void 0 ? _b : {}), w: pctShare(widths, start, end), type: 'pct' };
112
+ else if (type === 'dxa')
113
+ tcPr.tcW = { ...((_c = tcPr.tcW) !== null && _c !== void 0 ? _c : {}), w: widths.slice(start, end).reduce((a, b) => a + b, 0), type: 'dxa' };
114
+ else if (!tcPr.tcW)
115
+ tcPr.tcW = { w: 0, type };
116
+ }
117
+ /** The typed object a row's cell array belongs to: the row, or a cell-level control's content. */
118
+ function ownerOfCells(tr, container) {
119
+ var _a, _b;
120
+ if (tr.content === container)
121
+ return tr;
122
+ for (const item of ((_a = tr.content) !== null && _a !== void 0 ? _a : [])) {
123
+ const v = item.value;
124
+ if (v && typeof v === 'object' && ((_b = v.sdtContent) === null || _b === void 0 ? void 0 : _b.content) === container)
125
+ return v.sdtContent;
126
+ }
127
+ return tr;
128
+ }
129
+ /**
130
+ * The cell properties a new cell takes from the cell to the right of the boundary: its width (the
131
+ * form; the value is written from the grid) and its shading, and nothing else - check 41 saw Word
132
+ * copy the shading and leave the borders and the vertical alignment behind.
133
+ */
134
+ const COPIED = ['tcW', 'shd'];
135
+ function cellPropertiesFrom(template) {
136
+ if (!(template === null || template === void 0 ? void 0 : template.tcPr))
137
+ return undefined;
138
+ const out = {};
139
+ for (const [key, value] of Object.entries(template.tcPr))
140
+ if (COPIED.includes(key))
141
+ out[key] = deepCopy(value);
142
+ return Object.keys(out).length > 0 ? out : undefined;
143
+ }
144
+ /** The paragraph properties a new cell's paragraph takes: the template's first paragraph's, its mark's run properties included, less any revision (check 41: centred bold, right-aligned italic, as the neighbour's). */
145
+ function paragraphPropertiesFrom(template) {
146
+ var _a, _b;
147
+ const first = ((_a = childrenOf(template !== null && template !== void 0 ? template : {})) !== null && _a !== void 0 ? _a : []).find((item) => { var _a; return ((_a = item.value) === null || _a === void 0 ? void 0 : _a.TYPE_NAME) === 'org_docx4j_wml.P'; });
148
+ const pPr = (_b = first === null || first === void 0 ? void 0 : first.value) === null || _b === void 0 ? void 0 : _b.pPr;
149
+ if (!pPr)
150
+ return undefined;
151
+ const out = {};
152
+ for (const [key, value] of Object.entries(pPr))
153
+ if (!['pPrChange', 'sectPr', 'PARENT'].includes(key))
154
+ out[key] = deepCopy(value);
155
+ const rPr = out.rPr;
156
+ if (rPr)
157
+ for (const key of ['ins', 'del', 'rPrChange', 'moveFrom', 'moveTo'])
158
+ delete rPr[key];
159
+ return Object.keys(out).length > 0 ? out : undefined;
160
+ }
161
+ /** The new cells of one row, each a `w:tc` of one paragraph with the template's shading and paragraph properties. */
162
+ function newCells(count, values, template) {
163
+ var _a, _b;
164
+ const out = [];
165
+ for (let i = 0; i < count; i++) {
166
+ // an empty cell holds an empty `w:p`, as Word writes one; a value its text
167
+ const text = (_a = values === null || values === void 0 ? void 0 : values[i]) !== null && _a !== void 0 ? _a : '';
168
+ const cell = cellElementOf(text === '' ? [paragraphOf([])] : text);
169
+ const tcPr = cellPropertiesFrom(template);
170
+ if (tcPr)
171
+ cell.value.tcPr = tcPr;
172
+ else
173
+ delete cell.value.tcPr;
174
+ const pPr = paragraphPropertiesFrom(template);
175
+ const paragraph = ((_b = childrenOf(cell.value)) !== null && _b !== void 0 ? _b : []).find((item) => item.value.TYPE_NAME === 'org_docx4j_wml.P');
176
+ if (pPr && paragraph) {
177
+ paragraph.value.pPr = pPr;
178
+ linkParents(pPr, paragraph.value);
179
+ }
180
+ out.push(cell);
181
+ }
182
+ return out;
183
+ }
184
+ /**
185
+ * Inserts `count` grid columns at `gridIndex` (0 to the column count). The new column copies the
186
+ * width of the column to the right of the boundary when there is one, else the left (check 40:
187
+ * Insert Right in a narrow cell beside a wide one took the wide width; Insert Right in a spanning
188
+ * cell took the column after it), and each row's new cell takes the properties of the cell on the
189
+ * same side, less its span and merges. A cell spanning the boundary keeps its span and the new
190
+ * cell goes before it (check 40b1); a row's `w:gridBefore` or `w:gridAfter` covering the boundary
191
+ * grows instead. `values` is by row, then by new column. While changes are tracked the table
192
+ * change is recorded as Word's Office JS records one (check 40c): the old properties, every
193
+ * cell's properties - the new cells' as made - the grid as it is after, and the new cells' text in
194
+ * a `w:ins`, with no mark insertion.
195
+ */
196
+ export function insertGridColumns(tbl, textWidth, gridIndex, count, values, tracker) {
197
+ var _a, _b;
198
+ if (!Number.isInteger(count) || count < 1)
199
+ throw new Docx4JException(`columnCount must be a positive integer, not ${count}`);
200
+ const old = gridWidthsOf(tbl, textWidth);
201
+ if (gridIndex < 0 || gridIndex > old.length)
202
+ throw new Docx4JException(`No column boundary ${gridIndex} in a grid of ${old.length} columns`);
203
+ const mode = layoutModeOf(tbl);
204
+ const copied = old[gridIndex < old.length ? gridIndex : old.length - 1];
205
+ const unscaled = [...old.slice(0, gridIndex), ...Array.from({ length: count }, () => copied), ...old.slice(gridIndex)];
206
+ const widths = mode === 'window' ? rescaled(unscaled, old.reduce((a, b) => a + b, 0)) : unscaled;
207
+ const rows = rowsOf(tbl).map((row) => row.element.value);
208
+ const plans = rows.map((tr) => planRow(tr, gridIndex));
209
+ // an AutoFit-to-contents add changes no width, and Office JS records nothing for it (check 41c)
210
+ const record = tracker && mode !== 'contents' ? recordTable(tbl, rows, tracker) : undefined;
211
+ const result = { cells: [] };
212
+ plans.forEach((plan, rowIndex) => {
213
+ var _a, _b;
214
+ const tr = plan.tr;
215
+ if (plan.skipped) {
216
+ plan.skipped.value.val = Number((_a = plan.skipped.value.val) !== null && _a !== void 0 ? _a : 0) + count;
217
+ result.cells.push([]);
218
+ return;
219
+ }
220
+ const cells = newCells(count, values === null || values === void 0 ? void 0 : values[rowIndex], plan.template);
221
+ plan.container.splice(plan.at, 0, ...cells);
222
+ const owner = ownerOfCells(tr, plan.container);
223
+ for (const cell of cells)
224
+ linkParents(cell, owner);
225
+ if (tracker)
226
+ for (const cell of cells)
227
+ for (const block of (_b = childrenOf(cell.value)) !== null && _b !== void 0 ? _b : [])
228
+ wrapInsertedRuns(tracker, childrenOf(block.value), block.value);
229
+ result.cells.push(cells);
230
+ });
231
+ // the grid, the table's width, and the cells' widths in their forms
232
+ writeGrid(tbl, widths);
233
+ const tblW = (_a = tbl.tblPr) === null || _a === void 0 ? void 0 : _a.tblW;
234
+ if (mode === 'fixed' && (tblW === null || tblW === void 0 ? void 0 : tblW.type) === 'dxa')
235
+ tblW.w = Number((_b = tblW.w) !== null && _b !== void 0 ? _b : 0) + copied * count;
236
+ writeCellWidths(tbl, widths, mode === 'window' ? 'all' : result.cells.flat().map((c) => c.value));
237
+ if (record)
238
+ record(result.cells.flat().map((c) => c.value), { unscaled, scaled: widths });
239
+ return result;
240
+ }
241
+ /** Where the boundary falls in a row: at or inside a cell (the new cells go before it), in its skipped columns, or after its last cell. */
242
+ function planRow(tr, gridIndex) {
243
+ var _a, _b, _c;
244
+ const before = rowMember(tr, 'gridBefore');
245
+ const after = rowMember(tr, 'gridAfter');
246
+ const skippedBefore = Number((_a = before === null || before === void 0 ? void 0 : before.value.val) !== null && _a !== void 0 ? _a : 0);
247
+ if (before && gridIndex < skippedBefore)
248
+ return { tr, skipped: before };
249
+ const cells = cellsOf(tr);
250
+ let at = skippedBefore;
251
+ for (const cell of cells) {
252
+ const tc = cell.element.value;
253
+ const end = at + spanOf(tc);
254
+ // at the cell's start, or inside its span (check 40b1: the span kept, the new cell before it)
255
+ if (gridIndex >= at && gridIndex < end)
256
+ return { tr, container: cell.container, at: cell.container.indexOf(cell.element), template: tc };
257
+ at = end;
258
+ }
259
+ const skippedAfter = Number((_b = after === null || after === void 0 ? void 0 : after.value.val) !== null && _b !== void 0 ? _b : 0);
260
+ if (after && gridIndex < at + skippedAfter)
261
+ return { tr, skipped: after };
262
+ const last = cells[cells.length - 1];
263
+ if (!last)
264
+ return { tr, container: ((_c = tr.content) !== null && _c !== void 0 ? _c : (tr.content = [])), at: tr.content.length, template: undefined };
265
+ return { tr, container: last.container, at: last.container.indexOf(last.element) + 1, template: last.element.value };
266
+ }
267
+ /** Every cell's `w:tcW` from the grid (`all`), or only the given cells', each in the form it takes. */
268
+ function writeCellWidths(tbl, widths, which) {
269
+ var _a, _b;
270
+ for (const row of rowsOf(tbl)) {
271
+ const tr = row.element.value;
272
+ let at = Number((_b = (_a = rowMember(tr, 'gridBefore')) === null || _a === void 0 ? void 0 : _a.value.val) !== null && _b !== void 0 ? _b : 0);
273
+ for (const cell of cellsOf(tr)) {
274
+ const tc = cell.element.value;
275
+ const end = at + spanOf(tc);
276
+ if (which === 'all' || which.includes(tc))
277
+ writeCellWidth(tc, tbl, widths, at, end);
278
+ at = end;
279
+ }
280
+ }
281
+ }
282
+ /**
283
+ * What Word records for a column added or removed through Office JS with tracking on (checks 40c
284
+ * and 41c, the form of section 35's table change): a `w:tblPrChange` with the properties as they
285
+ * were, a `w:tcPrChange` on every cell with its properties as they were - a new cell's as made -
286
+ * and a `w:tblGridChange` holding the grid **as it is after the change**. Where the change
287
+ * rescaled the columns (AutoFit to window), the recorded grid is instead the old and new column
288
+ * edges together - the unscaled grid's edges and the scaled grid's, merged - with every cell's
289
+ * record spanning the merged columns its unscaled extent covers, and a new cell's recorded width a
290
+ * placeholder (`1 pct`; check 27's form, as Word wrote it in check 41c), so that rejecting puts
291
+ * the old widths back over a grid that `collapseGrid` then folds. Taken before the change and
292
+ * written after it, so that `TrackedChange` lists the whole as one table change. A record already
293
+ * there (an earlier pending change) is kept, so the original state stays the recorded one.
294
+ */
295
+ function recordTable(tbl, rows, tracker) {
296
+ var _a, _b;
297
+ const tblPr = tbl.tblPr;
298
+ const oldTblPr = tblPr && !tblPr.tblPrChange ? without(tblPr, ['tblPrChange']) : undefined;
299
+ const recordGrid = !((_a = tbl.tblGrid) === null || _a === void 0 ? void 0 : _a.tblGridChange);
300
+ const oldCells = [];
301
+ for (const tr of rows)
302
+ for (const cell of cellsOf(tr)) {
303
+ const tc = cell.element.value;
304
+ if (!((_b = tc.tcPr) === null || _b === void 0 ? void 0 : _b.tcPrChange))
305
+ oldCells.push([tc, recordedCellProperties(tc)]);
306
+ }
307
+ return (added = [], rescale) => {
308
+ var _a, _b, _c;
309
+ if (oldTblPr && tbl.tblPr) {
310
+ tbl.tblPr.tblPrChange = { TYPE_NAME: 'org_docx4j_wml.CTTblPrChange', ...tracker.markup(), tblPr: { TYPE_NAME: 'org_docx4j_wml.CTTblPrBase', ...oldTblPr } };
311
+ linkParents(tbl.tblPr.tblPrChange, tbl.tblPr);
312
+ }
313
+ const merged = rescale && rescale.unscaled.some((w, i) => w !== rescale.scaled[i]) ? mergedEdges(rescale.unscaled, rescale.scaled) : undefined;
314
+ if (recordGrid && tbl.tblGrid) {
315
+ const gridCol = merged ? merged.widths.map((w) => ({ TYPE_NAME: 'org_docx4j_wml.TblGridCol', w })) : ((_a = tbl.tblGrid.gridCol) !== null && _a !== void 0 ? _a : []).map((col) => deepCopy(col));
316
+ tbl.tblGrid.tblGridChange = { TYPE_NAME: 'org_docx4j_wml.CTTblGridChange', id: tracker.nextId(), tblGrid: { TYPE_NAME: 'org_docx4j_wml.TblGridBase', gridCol } };
317
+ linkParents(tbl.tblGrid.tblGridChange, tbl.tblGrid);
318
+ }
319
+ const extents = merged ? cellExtents(tbl) : undefined;
320
+ const cells = [...oldCells.filter(([tc]) => isInTable(tc, tbl)), ...added.map((tc) => [tc, recordedCellProperties(tc)])];
321
+ for (const [tc, old] of cells) {
322
+ const tcPr = ((_b = tc.tcPr) !== null && _b !== void 0 ? _b : (tc.tcPr = {}));
323
+ const recorded = { ...old };
324
+ if (merged && extents) {
325
+ // over the merged grid: the columns the cell's unscaled extent covers
326
+ const extent = extents.get(tc);
327
+ if (extent) {
328
+ const span = merged.index(unscaledEdge(rescale.unscaled, extent.end)) - merged.index(unscaledEdge(rescale.unscaled, extent.start));
329
+ if (span > 1)
330
+ recorded.gridSpan = { val: span };
331
+ else
332
+ delete recorded.gridSpan;
333
+ }
334
+ if (added.includes(tc) && ((_c = recorded.tcW) === null || _c === void 0 ? void 0 : _c.type) === 'pct')
335
+ recorded.tcW = { w: 1, type: 'pct' };
336
+ }
337
+ tcPr.tcPrChange = { TYPE_NAME: 'org_docx4j_wml.CTTcPrChange', ...tracker.markup(), tcPr: { TYPE_NAME: 'org_docx4j_wml.TcPrInner', ...recorded } };
338
+ linkParents(tcPr.tcPrChange, tcPr);
339
+ }
340
+ };
341
+ }
342
+ /** The cumulative edge, in twips, of a column index over a grid. */
343
+ function unscaledEdge(widths, column) {
344
+ return widths.slice(0, column).reduce((a, b) => a + b, 0);
345
+ }
346
+ /** The old and new grids' edges together, as merged columns, and where an edge falls among them. */
347
+ function mergedEdges(unscaled, scaled) {
348
+ const edges = new Set([0]);
349
+ let at = 0;
350
+ for (const w of unscaled) {
351
+ at += w;
352
+ edges.add(at);
353
+ }
354
+ at = 0;
355
+ for (const w of scaled) {
356
+ at += w;
357
+ edges.add(at);
358
+ }
359
+ const sorted = [...edges].sort((a, b) => a - b);
360
+ return { widths: sorted.slice(1).map((edge, i) => edge - sorted[i]), index: (edge) => sorted.indexOf(edge) };
361
+ }
362
+ /** Each cell's column extent in the grid as it is now, through the rows' spans and skipped columns. */
363
+ function cellExtents(tbl) {
364
+ var _a, _b;
365
+ const out = new Map();
366
+ for (const row of rowsOf(tbl)) {
367
+ const tr = row.element.value;
368
+ let at = Number((_b = (_a = rowMember(tr, 'gridBefore')) === null || _a === void 0 ? void 0 : _a.value.val) !== null && _b !== void 0 ? _b : 0);
369
+ for (const cell of cellsOf(tr)) {
370
+ const tc = cell.element.value;
371
+ const end = at + spanOf(tc);
372
+ out.set(tc, { start: at, end });
373
+ at = end;
374
+ }
375
+ }
376
+ return out;
377
+ }
378
+ /** A cell's properties as a record holds them: everything but the markers and an earlier record. */
379
+ function recordedCellProperties(tc) {
380
+ return without(tc.tcPr, ['tcPrChange', 'cellIns', 'cellDel', 'cellMerge']);
381
+ }
382
+ /** Whether a cell is still in the table (a deleted column's cells are not). */
383
+ function isInTable(tc, tbl) {
384
+ return rowsOf(tbl).some((row) => cellsOf(row.element.value).some((cell) => cell.element.value === tc));
385
+ }
386
+ function without(value, keys) {
387
+ const out = {};
388
+ for (const [key, v] of Object.entries(value !== null && value !== void 0 ? value : {}))
389
+ if (!keys.includes(key) && key !== 'PARENT')
390
+ out[key] = deepCopy(v);
391
+ return out;
392
+ }
393
+ /**
394
+ * Removes `count` grid columns from `columnIndex`: a cell covering only removed columns goes, one
395
+ * spanning further shrinks, a row's skipped columns shrink; the grid loses the columns, a `w:tblW`
396
+ * in twips loses their width, and in window mode the grid is rescaled to the width it had and the
397
+ * cells' percentages written again. While changes are tracked the cells still go, untracked, as
398
+ * Word's own Delete Columns and Office JS's `deleteColumns` leave them (known issues, entry 2;
399
+ * check 40c), and the table change is recorded as Office JS records it: the old properties, the
400
+ * remaining cells' properties, the grid as it is after.
401
+ */
402
+ export function deleteGridColumns(tbl, textWidth, columnIndex, count, tracker) {
403
+ var _a, _b, _c, _d;
404
+ if (!Number.isInteger(count) || count < 1)
405
+ throw new Docx4JException(`columnCount must be a positive integer, not ${count}`);
406
+ const old = gridWidthsOf(tbl, textWidth);
407
+ if (columnIndex < 0 || columnIndex + count > old.length)
408
+ throw new Docx4JException(`No columns ${columnIndex} to ${columnIndex + count - 1} in a grid of ${old.length} columns`);
409
+ const mode = layoutModeOf(tbl);
410
+ const record = tracker && mode !== 'contents' ? recordTable(tbl, rowsOf(tbl).map((row) => row.element.value), tracker) : undefined;
411
+ const from = columnIndex;
412
+ const to = columnIndex + count;
413
+ const overlap = (start, end) => Math.max(0, Math.min(end, to) - Math.max(start, from));
414
+ for (const row of rowsOf(tbl)) {
415
+ const tr = row.element.value;
416
+ const before = rowMember(tr, 'gridBefore');
417
+ const skippedBefore = Number((_a = before === null || before === void 0 ? void 0 : before.value.val) !== null && _a !== void 0 ? _a : 0);
418
+ if (before)
419
+ before.value.val = skippedBefore - overlap(0, skippedBefore);
420
+ let at = skippedBefore;
421
+ for (const cell of cellsOf(tr)) {
422
+ const tc = cell.element.value;
423
+ const span = spanOf(tc);
424
+ const end = at + span;
425
+ const covered = overlap(at, end);
426
+ if (covered === span) {
427
+ const i = cell.container.indexOf(cell.element);
428
+ if (i >= 0)
429
+ cell.container.splice(i, 1);
430
+ }
431
+ else if (covered > 0) {
432
+ setSpan(tc, span - covered);
433
+ }
434
+ at = end;
435
+ }
436
+ const after = rowMember(tr, 'gridAfter');
437
+ if (after) {
438
+ const skippedAfter = Number((_b = after.value.val) !== null && _b !== void 0 ? _b : 0);
439
+ after.value.val = skippedAfter - overlap(at, at + skippedAfter);
440
+ }
441
+ }
442
+ const removed = old.slice(from, to).reduce((a, b) => a + b, 0);
443
+ let widths = [...old.slice(0, from), ...old.slice(to)];
444
+ if (mode === 'window')
445
+ widths = rescaled(widths, old.reduce((a, b) => a + b, 0));
446
+ writeGrid(tbl, widths);
447
+ const tblW = (_c = tbl.tblPr) === null || _c === void 0 ? void 0 : _c.tblW;
448
+ if ((tblW === null || tblW === void 0 ? void 0 : tblW.type) === 'dxa')
449
+ tblW.w = Math.max(0, Number((_d = tblW.w) !== null && _d !== void 0 ? _d : 0) - removed);
450
+ writeCellWidths(tbl, widths, mode === 'window' ? 'all' : shrunkCells(tbl));
451
+ if (record)
452
+ record();
453
+ }
454
+ /** The cells whose span no longer matches their stated twips width: those that shrank. */
455
+ function shrunkCells(tbl) {
456
+ var _a, _b, _c, _d, _e, _f, _g;
457
+ const out = [];
458
+ const widths = (_c = (_b = (_a = tbl.tblGrid) === null || _a === void 0 ? void 0 : _a.gridCol) === null || _b === void 0 ? void 0 : _b.map((col) => { var _a; return Number((_a = col.w) !== null && _a !== void 0 ? _a : 0); })) !== null && _c !== void 0 ? _c : [];
459
+ for (const row of rowsOf(tbl)) {
460
+ const tr = row.element.value;
461
+ let at = Number((_e = (_d = rowMember(tr, 'gridBefore')) === null || _d === void 0 ? void 0 : _d.value.val) !== null && _e !== void 0 ? _e : 0);
462
+ for (const cell of cellsOf(tr)) {
463
+ const tc = cell.element.value;
464
+ const end = at + spanOf(tc);
465
+ const tcW = (_f = tc.tcPr) === null || _f === void 0 ? void 0 : _f.tcW;
466
+ if ((tcW === null || tcW === void 0 ? void 0 : tcW.type) === 'dxa' && Number((_g = tcW.w) !== null && _g !== void 0 ? _g : 0) !== widths.slice(at, end).reduce((a, b) => a + b, 0))
467
+ out.push(tc);
468
+ at = end;
469
+ }
470
+ }
471
+ return out;
472
+ }
473
+ /** The grid columns a cell covers: its start and the one past its end. */
474
+ export function gridRangeOf(tr, tc) {
475
+ var _a, _b;
476
+ let at = Number((_b = (_a = rowMember(tr, 'gridBefore')) === null || _a === void 0 ? void 0 : _a.value.val) !== null && _b !== void 0 ? _b : 0);
477
+ for (const cell of cellsOf(tr)) {
478
+ const end = at + spanOf(cell.element.value);
479
+ if (cell.element.value === tc)
480
+ return { start: at, end };
481
+ at = end;
482
+ }
483
+ return undefined;
484
+ }
@@ -1,4 +1,5 @@
1
1
  import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import type * as mce from '@docx4j/generated-objects-ts/modules/org_docx4j_mce';
2
3
  import { isElement, runItemsOf, type Element } from '@docx4j/generated-objects-ts/builders/wml';
3
4
  export { isElement, typeNameOf, walk, find, linkParents, textOf, W_NS, type Element } from '@docx4j/generated-objects-ts/builders/wml';
4
5
  /** The two views of a tracked document: as it will read once accepted, or as it read before the changes. */
@@ -7,6 +8,47 @@ export interface TextViewOptions {
7
8
  /** 'accepted' (the default: w:ins included, w:del excluded) or 'original' (the other way). */
8
9
  view?: TextView;
9
10
  }
11
+ /** `segmentsOf`'s options: a view, and whether drawn objects get a segment of their own. */
12
+ export interface SegmentOptions extends TextViewOptions {
13
+ /**
14
+ * True to list each picture or other drawn object (`OBJECT_ITEMS`) as a segment of no text at its
15
+ * place in the text (`object` set), so that a deletion of a span can find it (CR-002 section 48).
16
+ */
17
+ objects?: boolean;
18
+ }
19
+ /**
20
+ * Run content that stands in the text and has no text of its own: a `w:drawing` (a picture, a chart, a
21
+ * text box), a VML `w:pict` and an embedded `w:object`. Office JS counts an inline picture as in a
22
+ * range, so a span's deletion takes the objects in it (CR-002 section 48). A kept `mc:AlternateContent`
23
+ * in a run is one such object too, whatever its branches hold (`ALTERNATE_CONTENT`, CR-010 phase 3).
24
+ */
25
+ export declare const OBJECT_ITEMS: ReadonlySet<string>;
26
+ /** The TYPE_NAME of a kept `mc:AlternateContent` (the default load since CR-010 phase 4; under `KEPT_HOSTS` with `mcePreprocess: true`). */
27
+ export declare const ALTERNATE_CONTENT = "org_docx4j_mce.AlternateContent";
28
+ /**
29
+ * The branch of a kept `mc:AlternateContent` the content API reads (docx4j's `McMode.READ`, CR-010
30
+ * phase 3): the objects package's `mcBranchOf` over the prefixes load understands
31
+ * (`UNDERSTOOD_PREFIXES`), so a reader of the kept element takes the branch the preprocessor would have
32
+ * resolved it to. A mutator walks every branch instead (docx4j's `McMode.ALL`). Undefined for an element
33
+ * with no branches.
34
+ */
35
+ export declare function selectedBranchOf(value: mce.AlternateContent | undefined): Element[] | undefined;
36
+ /** True for a kept `mc:AlternateContent`: its element pair, or its value. */
37
+ export declare function isAlternateContent(value: unknown): boolean;
38
+ /**
39
+ * Which objects at a span's edges are in the span. An object stands between two characters, so one at
40
+ * a span's start or end offset may be inside it or outside it, and offsets alone cannot say which. A
41
+ * paragraph's whole range holds the objects at both of its edges (`WHOLE`); a span of text, such as a
42
+ * search found, holds only the objects strictly inside it (CR-002 section 48).
43
+ */
44
+ export interface SpanEdges {
45
+ start: boolean;
46
+ end: boolean;
47
+ }
48
+ /** Both edges: the objects at a span's start and at its end are in it. */
49
+ export declare const WHOLE: SpanEdges;
50
+ /** True when an object at offset `at` is in the span [start, end) with these edges. */
51
+ export declare function objectInSpan(at: number, start: number, end: number, edges?: SpanEdges): boolean;
10
52
  /** The four run-level revision holders (CR-002 phase F); their runs live under `customXmlOrSmartTagOrSdt`. */
11
53
  export type RevisionKind = 'ins' | 'del' | 'moveFrom' | 'moveTo';
12
54
  /** The run-level revision a run sits in. */
@@ -37,6 +79,8 @@ export interface TextSegment {
37
79
  end: number;
38
80
  /** True for w:t: text that can be edited in place. */
39
81
  editable: boolean;
82
+ /** True for a drawn object (`OBJECT_ITEMS`, or a kept `mc:AlternateContent`), whose segment has no text: listed only when asked for. */
83
+ object?: boolean;
40
84
  /**
41
85
  * The nearest enclosing w:ins / w:del / w:moveFrom / w:moveTo, when the run is in one.
42
86
  * A deletion of this run goes inside it (`runOwner` is already its item list); an insertion
@@ -53,9 +97,15 @@ declare function itemText(item: Element): string | undefined;
53
97
  /**
54
98
  * The text segments of a paragraph (or any run holder) in order. The default view is the
55
99
  * accepted one: the runs of a `w:ins` or `w:moveTo` count, those of a `w:del` or `w:moveFrom`
56
- * do not. `{ view: 'original' }` is the other way round (CR-002 phase F).
100
+ * do not. `{ view: 'original' }` is the other way round (CR-002 phase F). `{ objects: true }`
101
+ * lists the drawn objects too, each a segment of no text where it stands (CR-002 section 48).
102
+ *
103
+ * A kept `mc:AlternateContent` in a run is one object segment, whatever its branches hold, so that
104
+ * removing a span removes every branch with it (CR-010 phase 3). A drawing adds no text, so the
105
+ * offsets are a resolved load's. One in a paragraph's content, outside a run, is not listed: Word
106
+ * writes none, and the corpus has none (CR-010 section 11.3).
57
107
  */
58
- export declare function segmentsOf(container: object, options?: TextViewOptions): TextSegment[];
108
+ export declare function segmentsOf(container: object, options?: SegmentOptions): TextSegment[];
59
109
  /** The text of a paragraph or container in one of the two views (`textOf` is the accepted one). */
60
110
  export declare function textOfView(container: object, options?: TextViewOptions): string;
61
111
  /** The runs of a paragraph, direct and nested in hyperlinks, content controls and insertions, in order. */
@@ -1,5 +1,36 @@
1
- import { isElement, typeNameOf, runItemsOf } from '@docx4j/generated-objects-ts/builders/wml';
1
+ import { isElement, typeNameOf, runItemsOf, mcBranchOf } from '@docx4j/generated-objects-ts/builders/wml';
2
+ import { UNDERSTOOD_PREFIXES } from '../../opc/mce/understood.mjs';
2
3
  export { isElement, typeNameOf, walk, find, linkParents, textOf, W_NS } from '@docx4j/generated-objects-ts/builders/wml';
4
+ /**
5
+ * Run content that stands in the text and has no text of its own: a `w:drawing` (a picture, a chart, a
6
+ * text box), a VML `w:pict` and an embedded `w:object`. Office JS counts an inline picture as in a
7
+ * range, so a span's deletion takes the objects in it (CR-002 section 48). A kept `mc:AlternateContent`
8
+ * in a run is one such object too, whatever its branches hold (`ALTERNATE_CONTENT`, CR-010 phase 3).
9
+ */
10
+ export const OBJECT_ITEMS = new Set(['drawing', 'pict', 'object']);
11
+ /** The TYPE_NAME of a kept `mc:AlternateContent` (the default load since CR-010 phase 4; under `KEPT_HOSTS` with `mcePreprocess: true`). */
12
+ export const ALTERNATE_CONTENT = 'org_docx4j_mce.AlternateContent';
13
+ /**
14
+ * The branch of a kept `mc:AlternateContent` the content API reads (docx4j's `McMode.READ`, CR-010
15
+ * phase 3): the objects package's `mcBranchOf` over the prefixes load understands
16
+ * (`UNDERSTOOD_PREFIXES`), so a reader of the kept element takes the branch the preprocessor would have
17
+ * resolved it to. A mutator walks every branch instead (docx4j's `McMode.ALL`). Undefined for an element
18
+ * with no branches.
19
+ */
20
+ export function selectedBranchOf(value) {
21
+ return mcBranchOf(value, { understood: UNDERSTOOD_PREFIXES });
22
+ }
23
+ /** True for a kept `mc:AlternateContent`: its element pair, or its value. */
24
+ export function isAlternateContent(value) {
25
+ const v = isElement(value) ? value.value : value;
26
+ return typeof v === 'object' && v !== null && v.TYPE_NAME === ALTERNATE_CONTENT;
27
+ }
28
+ /** Both edges: the objects at a span's start and at its end are in it. */
29
+ export const WHOLE = Object.freeze({ start: true, end: true });
30
+ /** True when an object at offset `at` is in the span [start, end) with these edges. */
31
+ export function objectInSpan(at, start, end, edges) {
32
+ return (start < at && at < end) || (at === start && (edges === null || edges === void 0 ? void 0 : edges.start) === true) || (at === end && (edges === null || edges === void 0 ? void 0 : edges.end) === true);
33
+ }
3
34
  /** Types whose value holds runs (directly or through sdtContent); the revision holders are separate. */
4
35
  const RUN_HOLDERS = new Set([
5
36
  'org_docx4j_wml.P.Hyperlink', 'org_docx4j_wml.SdtRun', 'org_docx4j_wml.CTSmartTagRun',
@@ -43,11 +74,18 @@ function itemText(item) {
43
74
  /**
44
75
  * The text segments of a paragraph (or any run holder) in order. The default view is the
45
76
  * accepted one: the runs of a `w:ins` or `w:moveTo` count, those of a `w:del` or `w:moveFrom`
46
- * do not. `{ view: 'original' }` is the other way round (CR-002 phase F).
77
+ * do not. `{ view: 'original' }` is the other way round (CR-002 phase F). `{ objects: true }`
78
+ * lists the drawn objects too, each a segment of no text where it stands (CR-002 section 48).
79
+ *
80
+ * A kept `mc:AlternateContent` in a run is one object segment, whatever its branches hold, so that
81
+ * removing a span removes every branch with it (CR-010 phase 3). A drawing adds no text, so the
82
+ * offsets are a resolved load's. One in a paragraph's content, outside a run, is not listed: Word
83
+ * writes none, and the corpus has none (CR-010 section 11.3).
47
84
  */
48
85
  export function segmentsOf(container, options) {
49
86
  var _a;
50
87
  const view = (_a = options === null || options === void 0 ? void 0 : options.view) !== null && _a !== void 0 ? _a : 'accepted';
88
+ const objects = (options === null || options === void 0 ? void 0 : options.objects) === true;
51
89
  const out = [];
52
90
  let pos = 0;
53
91
  const visitRuns = (items, revision) => {
@@ -63,8 +101,15 @@ export function segmentsOf(container, options) {
63
101
  for (let j = 0; j < content.length; j++) {
64
102
  const item = content[j];
65
103
  const text = itemText(item);
66
- if (text === undefined)
104
+ if (text === undefined) {
105
+ if (objects && (OBJECT_ITEMS.has(item.name.localPart) || isAlternateContent(item))) {
106
+ const seg = { item, owner: content, index: j, run, runOwner: items, runIndex: i, text: '', start: pos, end: pos, editable: false, object: true };
107
+ if (revision)
108
+ seg.revision = revision;
109
+ out.push(seg);
110
+ }
67
111
  continue;
112
+ }
68
113
  const seg = {
69
114
  item, owner: content, index: j, run, runOwner: items, runIndex: i, text,
70
115
  start: pos, end: pos + text.length, editable: item.name.localPart === 't',