@docx4j/core-ts 0.3.1 → 0.4.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 (57) hide show
  1. package/README.md +3 -1
  2. package/dist/html/convert.d.mts +116 -0
  3. package/dist/html/convert.mjs +541 -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.mjs +9 -1
  9. package/dist/model/content/ContentControl.d.mts +5 -1
  10. package/dist/model/content/ContentControl.mjs +18 -5
  11. package/dist/model/content/InlinePicture.d.mts +18 -3
  12. package/dist/model/content/InlinePicture.mjs +44 -7
  13. package/dist/model/content/Paragraph.d.mts +11 -11
  14. package/dist/model/content/Paragraph.mjs +30 -43
  15. package/dist/model/content/Range.mjs +3 -2
  16. package/dist/model/content/Table.d.mts +38 -0
  17. package/dist/model/content/Table.mjs +80 -2
  18. package/dist/model/content/index.d.mts +2 -1
  19. package/dist/model/content/index.mjs +2 -1
  20. package/dist/model/content/stylesFacade.d.mts +4 -2
  21. package/dist/model/content/stylesFacade.mjs +23 -6
  22. package/dist/model/content/tableColumns.d.mts +48 -0
  23. package/dist/model/content/tableColumns.mjs +484 -0
  24. package/dist/model/content/tracking.d.mts +14 -0
  25. package/dist/model/content/tracking.mjs +19 -10
  26. package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
  27. package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
  28. package/dist/model/customxml/XmlMapping.d.mts +20 -4
  29. package/dist/model/customxml/XmlMapping.mjs +44 -6
  30. package/dist/model/customxml/bindings.d.mts +13 -3
  31. package/dist/model/customxml/bindings.mjs +38 -7
  32. package/dist/model/customxml/index.d.mts +4 -1
  33. package/dist/model/customxml/index.mjs +4 -1
  34. package/dist/model/customxml/insert.d.mts +10 -0
  35. package/dist/model/customxml/insert.mjs +13 -0
  36. package/dist/model/customxml/opendope.d.mts +32 -0
  37. package/dist/model/customxml/opendope.mjs +56 -0
  38. package/dist/model/customxml/pictures.d.mts +54 -0
  39. package/dist/model/customxml/pictures.mjs +186 -0
  40. package/dist/model/customxml/xhtml.d.mts +42 -0
  41. package/dist/model/customxml/xhtml.mjs +162 -0
  42. package/dist/model/fonts/RunFontSelector.d.mts +8 -1
  43. package/dist/model/fonts/RunFontSelector.mjs +6 -5
  44. package/dist/model/properties/PropertyResolver.d.mts +110 -10
  45. package/dist/model/properties/PropertyResolver.mjs +300 -31
  46. package/dist/model/properties/index.d.mts +3 -0
  47. package/dist/model/properties/index.mjs +2 -0
  48. package/dist/model/properties/table.d.mts +155 -0
  49. package/dist/model/properties/table.mjs +315 -0
  50. package/dist/model/properties/tableStyleConditions.d.mts +101 -0
  51. package/dist/model/properties/tableStyleConditions.mjs +296 -0
  52. package/dist/packages/WordprocessingMLPackage.mjs +1 -0
  53. package/dist/parts/wml/knownStyles.d.mts +1 -0
  54. package/dist/parts/wml/knownStyles.mjs +2233 -0
  55. package/dist/parts/wml/placeholderStyle.d.mts +3 -0
  56. package/dist/parts/wml/placeholderStyle.mjs +13 -0
  57. 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
+ }
@@ -92,6 +92,20 @@ export declare function canonical(value: unknown): string;
92
92
  export interface TrackedPart {
93
93
  readonly contents?: unknown;
94
94
  }
95
+ /**
96
+ * Makes sure the part's root lists a prefix in `mc:Ignorable`, appending it when absent, so that
97
+ * content in that prefix's namespace may be written in it; the facade then declares the prefix.
98
+ * This is docx4j's pattern - the writer of the content adds its prefix
99
+ * (`Paginate.declareW14Ignorable`) - and Jason's decision (CR-002 section 29). Since objects 0.3.1
100
+ * (its CR-008) the marshaller does it for every writer as well; these calls stay as the writer's
101
+ * own statement. False when there is no unmarshalled root to put it on. Every part a
102
+ * `Body` is over (document, header, footer, notes, comments) has a root that binds `mc:Ignorable`.
103
+ *
104
+ * The prefix must be the facade's conventional one for its namespace (`w15`, `w16du`): an
105
+ * application that does not know the namespace (Word 2010 and `w15`) opens the part only when the
106
+ * prefix is ignorable, as Word's own files declare it (CR-002 section 40).
107
+ */
108
+ export declare function declareIgnorable(part: TrackedPart | undefined, prefix: string): boolean;
95
109
  /**
96
110
  * The per-package writer of revision markup. Created by the package while
97
111
  * `changeTrackingMode` is not `Off`; every content-API mutation asks its `Body` for one and,
@@ -105,24 +105,33 @@ export function canonical(value) {
105
105
  });
106
106
  }
107
107
  /**
108
- * Makes sure the part's root lists `w16du` in `mc:Ignorable`, appending it when absent, so that
109
- * `w16du:dateUtc` may be written in it; the facade then declares the prefix. This is docx4j's
110
- * pattern - the writer of the content adds its prefix (`Paginate.declareW14Ignorable`) - and Jason's
111
- * decision (CR-002 section 29); objects CR-008 would do it for every writer. False when there is no
112
- * unmarshalled root to put it on. Every part a `Body` is over (document, header, footer, notes,
113
- * comments) has a root that binds `mc:Ignorable`.
108
+ * Makes sure the part's root lists a prefix in `mc:Ignorable`, appending it when absent, so that
109
+ * content in that prefix's namespace may be written in it; the facade then declares the prefix.
110
+ * This is docx4j's pattern - the writer of the content adds its prefix
111
+ * (`Paginate.declareW14Ignorable`) - and Jason's decision (CR-002 section 29). Since objects 0.3.1
112
+ * (its CR-008) the marshaller does it for every writer as well; these calls stay as the writer's
113
+ * own statement. False when there is no unmarshalled root to put it on. Every part a
114
+ * `Body` is over (document, header, footer, notes, comments) has a root that binds `mc:Ignorable`.
115
+ *
116
+ * The prefix must be the facade's conventional one for its namespace (`w15`, `w16du`): an
117
+ * application that does not know the namespace (Word 2010 and `w15`) opens the part only when the
118
+ * prefix is ignorable, as Word's own files declare it (CR-002 section 40).
114
119
  */
115
- function declareDateUtc(part) {
120
+ export function declareIgnorable(part, prefix) {
116
121
  var _a;
117
122
  const contents = part === null || part === void 0 ? void 0 : part.contents;
118
123
  if (!contents || typeof contents !== 'object')
119
124
  return false;
120
125
  const root = (contents.value && typeof contents.value === 'object' ? contents.value : contents);
121
- const prefixes = ((_a = root.ignorable) !== null && _a !== void 0 ? _a : '').split(/\s+/).filter((prefix) => prefix.length > 0);
122
- if (!prefixes.includes('w16du'))
123
- root.ignorable = [...prefixes, 'w16du'].join(' ');
126
+ const prefixes = ((_a = root.ignorable) !== null && _a !== void 0 ? _a : '').split(/\s+/).filter((p) => p.length > 0);
127
+ if (!prefixes.includes(prefix))
128
+ root.ignorable = [...prefixes, prefix].join(' ');
124
129
  return true;
125
130
  }
131
+ /** `w16du` in the part's `mc:Ignorable`, so that `w16du:dateUtc` may be written in it. */
132
+ function declareDateUtc(part) {
133
+ return declareIgnorable(part, 'w16du');
134
+ }
126
135
  function element(localPart, value) {
127
136
  return { name: { namespaceURI: W_NS, localPart }, value };
128
137
  }
@@ -5,6 +5,7 @@ import { CustomXmlPart, type CustomXmlPartOwner } from './CustomXmlPart.mjs';
5
5
  import type { CustomXmlPartLookup } from './XmlMapping.mjs';
6
6
  import type { XPathEngine } from './xpath.mjs';
7
7
  import { type BindingResult } from './bindings.mjs';
8
+ import { type XhtmlHost, type XhtmlBindingOptions } from './xhtml.mjs';
8
9
  /**
9
10
  * What the collection needs of its package. A structural interface, so that this module does not
10
11
  * import `WordprocessingMLPackage` (which imports it): the package hands itself in.
@@ -18,6 +19,8 @@ export interface CustomXmlHost {
18
19
  boundBodies(): Body[];
19
20
  /** Where a new custom XML part's relationship goes: the main document part (Word drops parts related from the package alone). */
20
21
  customXmlRelationshipSource(): Part;
22
+ /** The package, for the XHTML pass (list definitions, styles, relationships); absent, XHTML controls are left and noted. */
23
+ readonly xhtmlHost?: XhtmlHost;
21
24
  }
22
25
  /** A subset of Office JS `Word.CustomXmlPartCollection`. */
23
26
  export declare class CustomXmlPartCollection implements CustomXmlPartOwner, CustomXmlPartLookup {
@@ -62,7 +65,9 @@ export declare class CustomXmlPartCollection implements CustomXmlPartOwner, Cust
62
65
  * which is what Word does when it opens the document. Unmarshals the main document part (and the
63
66
  * headers and footers it has) and parses the custom XML parts.
64
67
  */
65
- applyBindings(): Promise<BindingResult>;
68
+ applyBindings(options?: {
69
+ html?: XhtmlBindingOptions;
70
+ }): Promise<BindingResult>;
66
71
  /**
67
72
  * docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
68
73
  * back into the custom XML. The reverse of `applyBindings`, and what makes an edit to a bound
@@ -14,6 +14,8 @@ import { parseXml, decodeXmlText } from '../../xml/dom.mjs';
14
14
  import { createDatastoreItem } from '@docx4j/generated-objects-ts/factory/org_docx4j_customXmlProperties';
15
15
  import { CustomXmlPart } from './CustomXmlPart.mjs';
16
16
  import { applyBindingsTo, updateFromControls } from './bindings.mjs';
17
+ import { applyXhtmlBindingsTo, isXhtmlBound } from './xhtml.mjs';
18
+ import { applyPictureHandlersTo, updateFromPictureControls } from './pictures.mjs';
17
19
  /** A subset of Office JS `Word.CustomXmlPartCollection`. */
18
20
  export class CustomXmlPartCollection {
19
21
  constructor(host) {
@@ -133,9 +135,19 @@ export class CustomXmlPartCollection {
133
135
  * which is what Word does when it opens the document. Unmarshals the main document part (and the
134
136
  * headers and footers it has) and parses the custom XML parts.
135
137
  */
136
- async applyBindings() {
138
+ async applyBindings(options = {}) {
139
+ var _a, _b;
137
140
  await this.load();
138
- return applyBindingsTo(this.controlsOf(await this.host.getBoundBodies()));
141
+ const controls = this.controlsOf(await this.host.getBoundBodies());
142
+ const result = applyBindingsTo(controls);
143
+ // od:Handler=picture through the tag's od:xpath entry (CR-005 section 9): after the text bindings, which took the picture controls.
144
+ applyPictureHandlersTo(controls, this, result);
145
+ // Escaped XHTML through the tag's od:xpath entry (CR-005 section 8.2): after the text bindings, asynchronous.
146
+ if (this.host.xhtmlHost)
147
+ await applyXhtmlBindingsTo(controls, this, this.host.xhtmlHost, result, (_a = options.html) !== null && _a !== void 0 ? _a : {});
148
+ else if (controls.some(isXhtmlBound))
149
+ ((_b = result.notes) !== null && _b !== void 0 ? _b : (result.notes = [])).push('XHTML-bound controls left: the collection has no package to make their lists, styles and links');
150
+ return result;
139
151
  }
140
152
  /**
141
153
  * docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
@@ -144,7 +156,11 @@ export class CustomXmlPartCollection {
144
156
  */
145
157
  async updateFromContentControls() {
146
158
  await this.load();
147
- return updateFromControls(this.controlsOf(await this.host.getBoundBodies()));
159
+ const controls = this.controlsOf(await this.host.getBoundBodies());
160
+ const result = updateFromControls(controls);
161
+ // a mapped picture control's image back to its node, as Word 15 writes it (CR-005 section 9, check 38)
162
+ await updateFromPictureControls(controls, result);
163
+ return result;
148
164
  }
149
165
  controlsOf(bodies) {
150
166
  return bodies.flatMap((body) => body.contentControls);