@docx4j/core-ts 0.1.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 (92) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +12 -0
  3. package/README.md +126 -0
  4. package/dist/index.d.mts +7 -0
  5. package/dist/index.mjs +10 -0
  6. package/dist/model/content/Body.d.mts +107 -0
  7. package/dist/model/content/Body.mjs +363 -0
  8. package/dist/model/content/Font.d.mts +46 -0
  9. package/dist/model/content/Font.mjs +50 -0
  10. package/dist/model/content/Paragraph.d.mts +99 -0
  11. package/dist/model/content/Paragraph.mjs +364 -0
  12. package/dist/model/content/Range.d.mts +34 -0
  13. package/dist/model/content/Range.mjs +94 -0
  14. package/dist/model/content/index.d.mts +7 -0
  15. package/dist/model/content/index.mjs +9 -0
  16. package/dist/model/content/search.d.mts +12 -0
  17. package/dist/model/content/search.mjs +68 -0
  18. package/dist/model/content/tree.d.mts +40 -0
  19. package/dist/model/content/tree.mjs +120 -0
  20. package/dist/model/index.d.mts +1 -0
  21. package/dist/model/index.mjs +2 -0
  22. package/dist/opc/ContentTypeManager.d.mts +37 -0
  23. package/dist/opc/ContentTypeManager.mjs +126 -0
  24. package/dist/opc/ContentTypes.d.mts +118 -0
  25. package/dist/opc/ContentTypes.mjs +139 -0
  26. package/dist/opc/FlatOpcPartStore.d.mts +42 -0
  27. package/dist/opc/FlatOpcPartStore.mjs +151 -0
  28. package/dist/opc/Load.d.mts +18 -0
  29. package/dist/opc/Load.mjs +123 -0
  30. package/dist/opc/PartName.d.mts +46 -0
  31. package/dist/opc/PartName.mjs +132 -0
  32. package/dist/opc/PartStore.d.mts +45 -0
  33. package/dist/opc/PartStore.mjs +54 -0
  34. package/dist/opc/Save.d.mts +9 -0
  35. package/dist/opc/Save.mjs +52 -0
  36. package/dist/opc/ZipPartStore.d.mts +32 -0
  37. package/dist/opc/ZipPartStore.mjs +117 -0
  38. package/dist/opc/exceptions.d.mts +17 -0
  39. package/dist/opc/exceptions.mjs +22 -0
  40. package/dist/opc/index.d.mts +12 -0
  41. package/dist/opc/index.mjs +13 -0
  42. package/dist/opc/mce/preprocessor.d.mts +15 -0
  43. package/dist/opc/mce/preprocessor.mjs +74 -0
  44. package/dist/opc/mce/understood.d.mts +1 -0
  45. package/dist/opc/mce/understood.mjs +98 -0
  46. package/dist/packages/OpcPackage.d.mts +62 -0
  47. package/dist/packages/OpcPackage.mjs +133 -0
  48. package/dist/packages/PresentationMLPackage.d.mts +12 -0
  49. package/dist/packages/PresentationMLPackage.mjs +39 -0
  50. package/dist/packages/SpreadsheetMLPackage.d.mts +12 -0
  51. package/dist/packages/SpreadsheetMLPackage.mjs +38 -0
  52. package/dist/packages/WordprocessingMLPackage.d.mts +49 -0
  53. package/dist/packages/WordprocessingMLPackage.mjs +130 -0
  54. package/dist/packages/index.d.mts +5 -0
  55. package/dist/packages/index.mjs +6 -0
  56. package/dist/packages/registry.d.mts +7 -0
  57. package/dist/packages/registry.mjs +18 -0
  58. package/dist/parts/BinaryPart.d.mts +38 -0
  59. package/dist/parts/BinaryPart.mjs +72 -0
  60. package/dist/parts/DefaultXmlPart.d.mts +29 -0
  61. package/dist/parts/DefaultXmlPart.mjs +63 -0
  62. package/dist/parts/Namespaces.d.mts +99 -0
  63. package/dist/parts/Namespaces.mjs +99 -0
  64. package/dist/parts/Part.d.mts +50 -0
  65. package/dist/parts/Part.mjs +68 -0
  66. package/dist/parts/PartRegistry.d.mts +20 -0
  67. package/dist/parts/PartRegistry.mjs +142 -0
  68. package/dist/parts/Parts.d.mts +13 -0
  69. package/dist/parts/Parts.mjs +28 -0
  70. package/dist/parts/RelationshipsPart.d.mts +66 -0
  71. package/dist/parts/RelationshipsPart.mjs +224 -0
  72. package/dist/parts/XmlPart.d.mts +46 -0
  73. package/dist/parts/XmlPart.mjs +87 -0
  74. package/dist/parts/customXml/index.d.mts +9 -0
  75. package/dist/parts/customXml/index.mjs +11 -0
  76. package/dist/parts/dml/index.d.mts +54 -0
  77. package/dist/parts/dml/index.mjs +83 -0
  78. package/dist/parts/docProps/index.d.mts +14 -0
  79. package/dist/parts/docProps/index.mjs +18 -0
  80. package/dist/parts/index.d.mts +14 -0
  81. package/dist/parts/index.mjs +15 -0
  82. package/dist/parts/pml/index.d.mts +53 -0
  83. package/dist/parts/pml/index.mjs +90 -0
  84. package/dist/parts/sml/index.d.mts +61 -0
  85. package/dist/parts/sml/index.mjs +107 -0
  86. package/dist/parts/wml/defaultStyles.d.mts +1 -0
  87. package/dist/parts/wml/defaultStyles.mjs +500 -0
  88. package/dist/parts/wml/index.d.mts +102 -0
  89. package/dist/parts/wml/index.mjs +213 -0
  90. package/dist/xml/dom.d.mts +22 -0
  91. package/dist/xml/dom.mjs +92 -0
  92. package/package.json +75 -0
@@ -0,0 +1,364 @@
1
+ import { deepCopy, marshalString } from '@docx4j/generated-objects-ts';
2
+ import { r as textRun, t as textItem, br as breakItem } from '@docx4j/generated-objects-ts/builders/wml';
3
+ import { segmentsOf, runsOf, runItemsOf, linkParents, textOf, W_NS, runOf, paragraphOf } from './tree.mjs';
4
+ function withRPr(run, rPr) {
5
+ if (rPr)
6
+ run.value.rPr = rPr;
7
+ return run;
8
+ }
9
+ import { Font } from './Font.mjs';
10
+ import { Range } from './Range.mjs';
11
+ import { searchPattern, findAll } from './search.mjs';
12
+ const TWIPS_PER_POINT = 20;
13
+ /**
14
+ * A subset of Office JS `Word.Paragraph` over a `w:p` in a body, cell, header, footer or
15
+ * content control. A light view: it holds the element and the array that contains it; nothing
16
+ * is cached, so views may be created and discarded freely. Every write mutates the tree in
17
+ * place and links PARENT on what it adds.
18
+ */
19
+ export class Paragraph {
20
+ constructor(
21
+ /** The `w:p` element pair. */
22
+ element,
23
+ /** The content array holding the element (a body's, a cell's, a content control's). */
24
+ container,
25
+ /** The nearest body view (the part's), for addresses and paragraph ids. */
26
+ parentBody) {
27
+ this.element = element;
28
+ this.container = container;
29
+ this.parentBody = parentBody;
30
+ }
31
+ /** The paragraph value (docx4j: the P). */
32
+ get p() {
33
+ return this.element.value;
34
+ }
35
+ get index() {
36
+ return this.container.indexOf(this.element);
37
+ }
38
+ /** The paragraph's text: runs joined, tabs as \t, breaks as \n; deleted text excluded. */
39
+ get text() {
40
+ return textOf(this.p);
41
+ }
42
+ /** Replaces the content with one run of the text, keeping the first run's formatting. */
43
+ set text(value) {
44
+ var _a;
45
+ const rPr = (_a = runsOf(this.p)[0]) === null || _a === void 0 ? void 0 : _a.value.rPr;
46
+ this.p.content = [withRPr(textRun(value), rPr ? deepCopy(rPr) : undefined)];
47
+ linkParents(this.p.content, this.p);
48
+ }
49
+ /** The style id (w:pStyle); 'Normal' when none is set. */
50
+ get style() {
51
+ var _a, _b, _c;
52
+ return (_c = (_b = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.pStyle) === null || _b === void 0 ? void 0 : _b.val) !== null && _c !== void 0 ? _c : 'Normal';
53
+ }
54
+ set style(id) {
55
+ if (id === '' || id === 'Normal') {
56
+ if (this.p.pPr)
57
+ delete this.p.pPr.pStyle;
58
+ return;
59
+ }
60
+ this.pPr().pStyle = { val: id };
61
+ }
62
+ /** Word's display name of the style ('Heading 1'); mapped to and from ids by removing spaces. */
63
+ get styleBuiltIn() {
64
+ return this.style.replace(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Za-z])(?=[0-9])/g, ' ');
65
+ }
66
+ set styleBuiltIn(name) {
67
+ this.style = name.replace(/\s+/g, '');
68
+ }
69
+ get alignment() {
70
+ var _a, _b;
71
+ switch ((_b = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.jc) === null || _b === void 0 ? void 0 : _b.val) {
72
+ case 'left':
73
+ case 'start': return 'Left';
74
+ case 'center': return 'Centered';
75
+ case 'right':
76
+ case 'end': return 'Right';
77
+ case 'both':
78
+ case 'distribute': return 'Justified';
79
+ default: return 'Unknown';
80
+ }
81
+ }
82
+ set alignment(v) {
83
+ const val = v === 'Left' ? 'left' : v === 'Centered' ? 'center' : v === 'Right' ? 'right' : v === 'Justified' ? 'both' : undefined;
84
+ if (val === undefined) {
85
+ if (this.p.pPr)
86
+ delete this.p.pPr.jc;
87
+ return;
88
+ }
89
+ this.pPr().jc = { val };
90
+ }
91
+ /** Indents and spacing in points, as Office JS; 0 when not set directly. */
92
+ get leftIndent() { var _a, _b, _c; return ((_c = (_b = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.ind) === null || _b === void 0 ? void 0 : _b.left) !== null && _c !== void 0 ? _c : 0) / TWIPS_PER_POINT; }
93
+ set leftIndent(pt) { this.ind().left = Math.round(pt * TWIPS_PER_POINT); }
94
+ get rightIndent() { var _a, _b, _c; return ((_c = (_b = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.ind) === null || _b === void 0 ? void 0 : _b.right) !== null && _c !== void 0 ? _c : 0) / TWIPS_PER_POINT; }
95
+ set rightIndent(pt) { this.ind().right = Math.round(pt * TWIPS_PER_POINT); }
96
+ /** First-line indent; negative for a hanging indent, as Office JS. */
97
+ get firstLineIndent() {
98
+ var _a, _b;
99
+ const ind = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.ind;
100
+ if ((ind === null || ind === void 0 ? void 0 : ind.hanging) !== undefined)
101
+ return -ind.hanging / TWIPS_PER_POINT;
102
+ return ((_b = ind === null || ind === void 0 ? void 0 : ind.firstLine) !== null && _b !== void 0 ? _b : 0) / TWIPS_PER_POINT;
103
+ }
104
+ set firstLineIndent(pt) {
105
+ const ind = this.ind();
106
+ if (pt < 0) {
107
+ ind.hanging = Math.round(-pt * TWIPS_PER_POINT);
108
+ delete ind.firstLine;
109
+ }
110
+ else {
111
+ ind.firstLine = Math.round(pt * TWIPS_PER_POINT);
112
+ delete ind.hanging;
113
+ }
114
+ }
115
+ get spaceBefore() { var _a, _b, _c; return ((_c = (_b = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.spacing) === null || _b === void 0 ? void 0 : _b.before) !== null && _c !== void 0 ? _c : 0) / TWIPS_PER_POINT; }
116
+ set spaceBefore(pt) { this.spacing().before = Math.round(pt * TWIPS_PER_POINT); }
117
+ get spaceAfter() { var _a, _b, _c; return ((_c = (_b = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.spacing) === null || _b === void 0 ? void 0 : _b.after) !== null && _c !== void 0 ? _c : 0) / TWIPS_PER_POINT; }
118
+ set spaceAfter(pt) { this.spacing().after = Math.round(pt * TWIPS_PER_POINT); }
119
+ /** Line spacing in points: w:line/240 lines of 12pt when the rule is auto, else w:line twips; 0 when not set. */
120
+ get lineSpacing() {
121
+ var _a;
122
+ const s = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.spacing;
123
+ if ((s === null || s === void 0 ? void 0 : s.line) === undefined)
124
+ return 0;
125
+ return s.lineRule === undefined || s.lineRule === 'auto' ? (s.line / 240) * 12 : s.line / TWIPS_PER_POINT;
126
+ }
127
+ set lineSpacing(pt) { const s = this.spacing(); s.line = Math.round(pt * TWIPS_PER_POINT); s.lineRule = 'exact'; }
128
+ /** w:outlineLvl + 1 (1 to 9); 10 for body text, as Office JS. */
129
+ get outlineLevel() { var _a, _b; const l = (_b = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.outlineLvl) === null || _b === void 0 ? void 0 : _b.val; return l === undefined ? 10 : l + 1; }
130
+ set outlineLevel(level) { if (level >= 10 || level < 1) {
131
+ if (this.p.pPr)
132
+ delete this.p.pPr.outlineLvl;
133
+ }
134
+ else
135
+ this.pPr().outlineLvl = { val: level - 1 }; }
136
+ /** Direct formatting of the runs: reads the first run, writes all (extension: `Font` is over runs, not the paragraph mark). */
137
+ get font() {
138
+ return new Font(() => runsOf(this.p).map((r) => r.value));
139
+ }
140
+ /** w14:paraId, the stable address Word gives paragraphs. */
141
+ get paraId() {
142
+ return this.p.paraId;
143
+ }
144
+ set paraId(id) {
145
+ if (id === undefined)
146
+ delete this.p.paraId;
147
+ else
148
+ this.p.paraId = id;
149
+ }
150
+ /** The runs, direct and nested (hyperlinks, content controls, insertions), in order. */
151
+ get runs() {
152
+ return runsOf(this.p);
153
+ }
154
+ /** Text inserted at the start, the end, or replacing the whole paragraph's text. */
155
+ insertText(text, location) {
156
+ const length = this.text.length;
157
+ if (location === 'Start')
158
+ return this.splice(0, 0, text);
159
+ if (location === 'End')
160
+ return this.splice(length, length, text);
161
+ return this.splice(0, length, text);
162
+ }
163
+ /** A new paragraph of the text before or after this one, with this paragraph's properties (as Word). */
164
+ insertParagraph(text, location) {
165
+ const pPr = this.p.pPr ? deepCopy(this.p.pPr) : undefined;
166
+ if (pPr) {
167
+ delete pPr.rPr;
168
+ delete pPr.sectPr;
169
+ }
170
+ const el = paragraphOf(text === '' ? [] : [textRun(text)], pPr);
171
+ return this.parentBody.insertElement(el, location, this);
172
+ }
173
+ insertBreak(type, location) {
174
+ var _a;
175
+ var _b;
176
+ const brk = breakItem(type === 'Page' ? 'page' : undefined);
177
+ if (location === 'Before' || location === 'After') {
178
+ const p = this.insertParagraph('', location);
179
+ p.p.content.push(runOf([brk]));
180
+ linkParents(p.p.content, p.p);
181
+ return;
182
+ }
183
+ const run = runOf([brk]);
184
+ const content = ((_a = (_b = this.p).content) !== null && _a !== void 0 ? _a : (_b.content = []));
185
+ if (location === 'Start')
186
+ content.unshift(run);
187
+ else
188
+ content.push(run);
189
+ linkParents(run, this.p);
190
+ }
191
+ /** Matches within this paragraph. */
192
+ search(text, options) {
193
+ return findAll(this.text, searchPattern(text, options)).map(([s, e]) => new Range(this, s, e));
194
+ }
195
+ getRange(location = 'Whole') {
196
+ const length = this.text.length;
197
+ if (location === 'Start')
198
+ return new Range(this, 0, 0);
199
+ if (location === 'End')
200
+ return new Range(this, length, length);
201
+ return new Range(this, 0, length);
202
+ }
203
+ /** Removes the paragraph from its container. */
204
+ delete() {
205
+ const i = this.index;
206
+ if (i >= 0)
207
+ this.container.splice(i, 1);
208
+ }
209
+ /** The paragraph as XML (extension; Office JS getOoxml wraps it in a package). */
210
+ getXml() {
211
+ return marshalString(this.element);
212
+ }
213
+ // --- docx4j names ---
214
+ /** docx4j: the P's content list. */
215
+ getContent() {
216
+ var _a;
217
+ var _b;
218
+ return ((_a = (_b = this.p).content) !== null && _a !== void 0 ? _a : (_b.content = []));
219
+ }
220
+ // --- editing primitives, shared with Range ---
221
+ pPr() {
222
+ var _a;
223
+ var _b;
224
+ return ((_a = (_b = this.p).pPr) !== null && _a !== void 0 ? _a : (_b.pPr = { TYPE_NAME: 'org_docx4j_wml.PPr' }));
225
+ }
226
+ ind() {
227
+ var _a;
228
+ var _b;
229
+ return ((_a = (_b = this.pPr()).ind) !== null && _a !== void 0 ? _a : (_b.ind = {}));
230
+ }
231
+ spacing() {
232
+ var _a;
233
+ var _b;
234
+ return ((_a = (_b = this.pPr()).spacing) !== null && _a !== void 0 ? _a : (_b.spacing = {}));
235
+ }
236
+ /** The text segments (w:t, w:tab, ...) with their offsets. */
237
+ segments() {
238
+ return segmentsOf(this.p);
239
+ }
240
+ /**
241
+ * Replaces the text in [start, end) with `text` and returns the range of the inserted text.
242
+ * Runs are edited in place: a w:t loses or gains characters, a tab or break inside the range
243
+ * is removed, a run left empty is removed. Text is inserted into the w:t at `start`,
244
+ * else into a new run with the formatting of the nearest run.
245
+ */
246
+ splice(start, end, text) {
247
+ var _a, _b, _c, _d;
248
+ var _e;
249
+ const length = this.text.length;
250
+ start = Math.max(0, Math.min(start, length));
251
+ end = Math.max(start, Math.min(end, length));
252
+ let segs = this.segments();
253
+ let inserted = false;
254
+ if (end > start) {
255
+ // Replace: the new text takes the place (and formatting) of the first replaced character, as Word does.
256
+ const first = segs.find((s) => s.start <= start && start < s.end);
257
+ const toRemove = [];
258
+ for (const seg of segs) {
259
+ if (seg.end <= start || seg.start >= end)
260
+ continue;
261
+ const from = Math.max(0, start - seg.start);
262
+ const to = Math.min(seg.text.length, end - seg.start);
263
+ if (seg.editable) {
264
+ const t = seg.item.value;
265
+ const middle = seg === first && !inserted ? text : '';
266
+ if (seg === first)
267
+ inserted = true;
268
+ const value = seg.text.substring(0, from) + middle + seg.text.substring(to);
269
+ if (value === '')
270
+ toRemove.push(seg);
271
+ else {
272
+ t.value = value;
273
+ if (/^\s|\s$|\s\s/.test(value))
274
+ t.space = 'preserve';
275
+ }
276
+ }
277
+ else
278
+ toRemove.push(seg);
279
+ }
280
+ for (const seg of toRemove.reverse()) {
281
+ const i = seg.owner.indexOf(seg.item);
282
+ if (i >= 0)
283
+ seg.owner.splice(i, 1);
284
+ }
285
+ if (inserted && text.length > 0) {
286
+ this.removeEmptyRuns();
287
+ return new Range(this, start, start + text.length);
288
+ }
289
+ this.removeEmptyRuns();
290
+ segs = this.segments();
291
+ }
292
+ if (text.length > 0) {
293
+ // Insert: extend the w:t at `start` (the one containing it, else the one ending there, else the one starting there)
294
+ const target = (_b = (_a = segs.find((s) => s.editable && s.start < start && start < s.end)) !== null && _a !== void 0 ? _a : segs.find((s) => s.editable && s.end === start)) !== null && _b !== void 0 ? _b : segs.find((s) => s.editable && s.start === start);
295
+ if (target) {
296
+ const t = target.item.value;
297
+ const at = start - target.start;
298
+ t.value = target.text.substring(0, at) + text + target.text.substring(at);
299
+ if (/^\s|\s$|\s\s/.test(t.value))
300
+ t.space = 'preserve';
301
+ }
302
+ else {
303
+ // no w:t to extend: a new run next to the segment at `start`, with that run's formatting
304
+ const before = [...segs].reverse().find((s) => s.end <= start);
305
+ const after = segs.find((s) => s.start >= start);
306
+ const neighbour = before !== null && before !== void 0 ? before : after;
307
+ const firstRun = (_c = runsOf(this.p)[0]) === null || _c === void 0 ? void 0 : _c.value.rPr;
308
+ const rPr = (neighbour === null || neighbour === void 0 ? void 0 : neighbour.run.rPr) ? deepCopy(neighbour.run.rPr) : firstRun ? deepCopy(firstRun) : undefined;
309
+ const run = runOf([textItem(text)], rPr);
310
+ if (before)
311
+ before.runOwner.splice(before.runIndex + 1, 0, run);
312
+ else if (after)
313
+ after.runOwner.splice(after.runIndex, 0, run);
314
+ else {
315
+ const content = ((_d = (_e = this.p).content) !== null && _d !== void 0 ? _d : (_e.content = []));
316
+ if (start === 0)
317
+ content.unshift(run);
318
+ else
319
+ content.push(run);
320
+ }
321
+ linkParents(run, this.p);
322
+ }
323
+ }
324
+ return new Range(this, start, start + text.length);
325
+ }
326
+ /** Splits the run at a text offset so that [offset, ...) begins a run; returns nothing when the offset is already a boundary. */
327
+ splitAt(offset) {
328
+ var _a;
329
+ const seg = this.segments().find((s) => s.editable && s.start < offset && offset < s.end);
330
+ if (!seg)
331
+ return;
332
+ const at = offset - seg.start;
333
+ const t = seg.item.value;
334
+ const head = seg.text.substring(0, at);
335
+ const tail = seg.text.substring(at);
336
+ t.value = head;
337
+ if (/^\s|\s$|\s\s/.test(head))
338
+ t.space = 'preserve';
339
+ const rest = seg.owner.splice(seg.index + 1);
340
+ const second = runOf([textItem(tail), ...rest], seg.run.rPr ? deepCopy(seg.run.rPr) : undefined);
341
+ seg.runOwner.splice(seg.runIndex + 1, 0, second);
342
+ linkParents(second, (_a = seg.run.PARENT) !== null && _a !== void 0 ? _a : this.p);
343
+ }
344
+ removeEmptyRuns() {
345
+ var _a;
346
+ const prune = (items) => {
347
+ for (let i = items.length - 1; i >= 0; i--) {
348
+ const el = items[i];
349
+ const v = el.value;
350
+ if (v.TYPE_NAME === 'org_docx4j_wml.R') {
351
+ if (!v.content || v.content.length === 0)
352
+ items.splice(i, 1);
353
+ }
354
+ else if (v.TYPE_NAME !== 'org_docx4j_wml.P') {
355
+ const nested = runItemsOf(v);
356
+ if (nested)
357
+ prune(nested);
358
+ }
359
+ }
360
+ };
361
+ prune((_a = this.p.content) !== null && _a !== void 0 ? _a : []);
362
+ }
363
+ }
364
+ export { W_NS };
@@ -0,0 +1,34 @@
1
+ import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import { Font } from './Font.mjs';
3
+ import type { Paragraph } from './Paragraph.mjs';
4
+ import type { Element } from './tree.mjs';
5
+ import { type SearchOptions } from './search.mjs';
6
+ /**
7
+ * A subset of Office JS `Word.Range`: a span of text within one paragraph, [start, end) in the
8
+ * paragraph's text. Positions are recomputed from the tree on every use, so a range stays
9
+ * valid across edits made through it; edits made elsewhere may shift it.
10
+ */
11
+ export declare class Range {
12
+ readonly paragraph: Paragraph;
13
+ start: number;
14
+ end: number;
15
+ constructor(paragraph: Paragraph, start: number, end: number);
16
+ get paragraphs(): Paragraph[];
17
+ get text(): string;
18
+ set text(value: string);
19
+ /** The paragraph's style (Office JS reports the range's paragraph style). */
20
+ get style(): string;
21
+ set style(id: string);
22
+ /** Direct formatting of exactly this span: runs are split at the boundaries so that a write touches only the span. */
23
+ get font(): Font;
24
+ /** The runs the span covers (a run partly inside counts). */
25
+ get runs(): Element<wml.R>[];
26
+ insertText(text: string, location: 'Before' | 'After' | 'Start' | 'End' | 'Replace'): Range;
27
+ insertParagraph(text: string, location: 'Before' | 'After'): Paragraph;
28
+ /** Removes the span's text. */
29
+ delete(): void;
30
+ search(text: string, options?: SearchOptions): Range[];
31
+ getRange(location?: 'Whole' | 'Start' | 'End' | 'Content'): Range;
32
+ /** The span's text as it stands (extension; Office JS getOoxml wraps it in a package). */
33
+ toString(): string;
34
+ }
@@ -0,0 +1,94 @@
1
+ import { Font } from './Font.mjs';
2
+ import { searchPattern, findAll } from './search.mjs';
3
+ /**
4
+ * A subset of Office JS `Word.Range`: a span of text within one paragraph, [start, end) in the
5
+ * paragraph's text. Positions are recomputed from the tree on every use, so a range stays
6
+ * valid across edits made through it; edits made elsewhere may shift it.
7
+ */
8
+ export class Range {
9
+ constructor(paragraph, start, end) {
10
+ this.paragraph = paragraph;
11
+ this.start = start;
12
+ this.end = end;
13
+ }
14
+ get paragraphs() {
15
+ return [this.paragraph];
16
+ }
17
+ get text() {
18
+ return this.paragraph.text.substring(this.start, this.end);
19
+ }
20
+ set text(value) {
21
+ this.insertText(value, 'Replace');
22
+ }
23
+ /** The paragraph's style (Office JS reports the range's paragraph style). */
24
+ get style() {
25
+ return this.paragraph.style;
26
+ }
27
+ set style(id) {
28
+ this.paragraph.style = id;
29
+ }
30
+ /** Direct formatting of exactly this span: runs are split at the boundaries so that a write touches only the span. */
31
+ get font() {
32
+ return new Font(() => {
33
+ if (this.start === this.end)
34
+ return [];
35
+ this.paragraph.splitAt(this.start);
36
+ this.paragraph.splitAt(this.end);
37
+ return this.runs.map((r) => r.value);
38
+ });
39
+ }
40
+ /** The runs the span covers (a run partly inside counts). */
41
+ get runs() {
42
+ const out = [];
43
+ const seen = new Set();
44
+ for (const seg of this.paragraph.segments()) {
45
+ if (seg.end <= this.start || seg.start >= this.end)
46
+ continue;
47
+ if (seen.has(seg.run))
48
+ continue;
49
+ seen.add(seg.run);
50
+ out.push(seg.runOwner[seg.runIndex]);
51
+ }
52
+ return out;
53
+ }
54
+ insertText(text, location) {
55
+ let r;
56
+ if (location === 'Replace')
57
+ r = this.paragraph.splice(this.start, this.end, text);
58
+ else if (location === 'Before' || location === 'Start')
59
+ r = this.paragraph.splice(this.start, this.start, text);
60
+ else
61
+ r = this.paragraph.splice(this.end, this.end, text);
62
+ // this range keeps covering the same text: a replacement resizes it, text before it shifts it, text after it leaves it
63
+ if (location === 'Replace')
64
+ this.end = this.start + text.length;
65
+ else if (location === 'Before' || location === 'Start') {
66
+ this.start += text.length;
67
+ this.end += text.length;
68
+ }
69
+ return r;
70
+ }
71
+ insertParagraph(text, location) {
72
+ return this.paragraph.insertParagraph(text, location);
73
+ }
74
+ /** Removes the span's text. */
75
+ delete() {
76
+ this.paragraph.splice(this.start, this.end, '');
77
+ this.end = this.start;
78
+ }
79
+ search(text, options) {
80
+ const base = this.start;
81
+ return findAll(this.text, searchPattern(text, options)).map(([s, e]) => new Range(this.paragraph, base + s, base + e));
82
+ }
83
+ getRange(location = 'Whole') {
84
+ if (location === 'Start')
85
+ return new Range(this.paragraph, this.start, this.start);
86
+ if (location === 'End')
87
+ return new Range(this.paragraph, this.end, this.end);
88
+ return new Range(this.paragraph, this.start, this.end);
89
+ }
90
+ /** The span's text as it stands (extension; Office JS getOoxml wraps it in a package). */
91
+ toString() {
92
+ return this.text;
93
+ }
94
+ }
@@ -0,0 +1,7 @@
1
+ export { Body, type Address, type BlockElement, type Outline, type OutlineParagraph, type OutlineTable } from './Body.mjs';
2
+ export { Paragraph, type Alignment } from './Paragraph.mjs';
3
+ export { Range } from './Range.mjs';
4
+ export { Font, type UnderlineType, type RPrHolder } from './Font.mjs';
5
+ export { type SearchOptions, searchPattern } from './search.mjs';
6
+ export { segmentsOf, runsOf, runItemsOf, childrenOf, BLOCK_LEVEL_TYPES, runOf, paragraphOf, type TextSegment } from './tree.mjs';
7
+ export { wml, wmlOne, p, r, t, br, tab, tbl, textOf, walk, find, linkParents, applyRunOptions, readRunOptions, UNDERLINE_TO_WML, isElement, typeNameOf, W_NS, type Element, type Wrapper, type WmlOptions, type Raw, type Interpolated, type RunOptions, type RunFormatting, type ParagraphOptions, type TableOptions } from '@docx4j/generated-objects-ts/builders/wml';
@@ -0,0 +1,9 @@
1
+ // The content API (CR-002 phases B and D): Office JS shapes over the docx4j tree.
2
+ export { Body } from './Body.mjs';
3
+ export { Paragraph } from './Paragraph.mjs';
4
+ export { Range } from './Range.mjs';
5
+ export { Font } from './Font.mjs';
6
+ export { searchPattern } from './search.mjs';
7
+ export { segmentsOf, runsOf, runItemsOf, childrenOf, BLOCK_LEVEL_TYPES, runOf, paragraphOf } from './tree.mjs';
8
+ // The objects package's builders, re-exported so that one import serves the content API and the tree it works on.
9
+ export { wml, wmlOne, p, r, t, br, tab, tbl, textOf, walk, find, linkParents, applyRunOptions, readRunOptions, UNDERLINE_TO_WML, isElement, typeNameOf, W_NS } from '@docx4j/generated-objects-ts/builders/wml';
@@ -0,0 +1,12 @@
1
+ /** Office JS Word.SearchOptions, the subset honoured here. */
2
+ export interface SearchOptions {
3
+ matchCase?: boolean;
4
+ matchWholeWord?: boolean;
5
+ /** Word wildcards: `?` one character, `*` any run, `[a-z]`, `[!x]`, `<` / `>` word start and end, `@` one or more of the previous. */
6
+ matchWildcards?: boolean;
7
+ }
8
+ /** A regular expression for a search text under the options; global so it can iterate matches. */
9
+ export declare function searchPattern(text: string, options?: SearchOptions): RegExp;
10
+ export declare function escapeRegExp(s: string): string;
11
+ /** All [start, end) matches of a pattern in a text. */
12
+ export declare function findAll(text: string, pattern: RegExp): [number, number][];
@@ -0,0 +1,68 @@
1
+ /** A regular expression for a search text under the options; global so it can iterate matches. */
2
+ export function searchPattern(text, options = {}) {
3
+ let source;
4
+ if (options.matchWildcards) {
5
+ source = '';
6
+ for (let i = 0; i < text.length; i++) {
7
+ const c = text[i];
8
+ if (c === '?')
9
+ source += '.';
10
+ else if (c === '*')
11
+ source += '.*?';
12
+ else if (c === '<')
13
+ source += '\\b(?=\\w)';
14
+ else if (c === '>')
15
+ source += '\\b(?<=\\w)';
16
+ else if (c === '@')
17
+ source += '+';
18
+ else if (c === '[') {
19
+ const close = text.indexOf(']', i);
20
+ if (close < 0) {
21
+ source += '\\[';
22
+ continue;
23
+ }
24
+ let cls = text.substring(i + 1, close);
25
+ if (cls.startsWith('!'))
26
+ cls = '^' + cls.substring(1);
27
+ source += `[${cls.replace(/\\/g, '\\\\')}]`;
28
+ i = close;
29
+ }
30
+ else if (c === '{') {
31
+ const close = text.indexOf('}', i);
32
+ if (close < 0) {
33
+ source += '\\{';
34
+ continue;
35
+ }
36
+ source += `{${text.substring(i + 1, close)}}`;
37
+ i = close;
38
+ }
39
+ else if (c === '\\' && i + 1 < text.length) {
40
+ source += '\\' + text[++i];
41
+ }
42
+ else
43
+ source += escapeRegExp(c);
44
+ }
45
+ }
46
+ else {
47
+ source = escapeRegExp(text);
48
+ }
49
+ if (options.matchWholeWord)
50
+ source = `(?<![\\w])${source}(?![\\w])`;
51
+ return new RegExp(source, options.matchCase ? 'g' : 'gi');
52
+ }
53
+ export function escapeRegExp(s) {
54
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
55
+ }
56
+ /** All [start, end) matches of a pattern in a text. */
57
+ export function findAll(text, pattern) {
58
+ const out = [];
59
+ pattern.lastIndex = 0;
60
+ for (let m = pattern.exec(text); m !== null; m = pattern.exec(text)) {
61
+ if (m[0].length === 0) {
62
+ pattern.lastIndex++;
63
+ continue;
64
+ }
65
+ out.push([m.index, m.index + m[0].length]);
66
+ }
67
+ return out;
68
+ }
@@ -0,0 +1,40 @@
1
+ import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import { isElement, type Element } from '@docx4j/generated-objects-ts/builders/wml';
3
+ export { isElement, typeNameOf, walk, find, linkParents, textOf, W_NS, type Element } from '@docx4j/generated-objects-ts/builders/wml';
4
+ /** One text-bearing item of a run, with its position in the paragraph's text. */
5
+ export interface TextSegment {
6
+ /** The item: w:t, w:tab, w:br, ... */
7
+ item: Element;
8
+ /** The run's content array holding the item, and the item's index in it. */
9
+ owner: Element[];
10
+ index: number;
11
+ /** The run. */
12
+ run: wml.R;
13
+ /** The array holding the run and its index there. */
14
+ runOwner: Element[];
15
+ runIndex: number;
16
+ text: string;
17
+ start: number;
18
+ end: number;
19
+ /** True for w:t: text that can be edited in place. */
20
+ editable: boolean;
21
+ }
22
+ /**
23
+ * The run-level items a holder keeps, by the model's own property names: `content` for most,
24
+ * `customXmlOrSmartTagOrSdt` for w:ins, w:del, w:moveFrom and w:moveTo (docx4j's names for
25
+ * the choice groups), `sdtContent.content` for a run-level content control.
26
+ */
27
+ export declare function runItemsOf(value: object): Element[] | undefined;
28
+ /** The text segments of a paragraph (or any run holder) in order. Deleted text (w:del) is skipped. */
29
+ export declare function segmentsOf(container: object): TextSegment[];
30
+ /** The runs of a paragraph, direct and nested in hyperlinks, content controls and insertions, in order. */
31
+ export declare function runsOf(container: object): Element<wml.R>[];
32
+ /** The block-level children of a container: body, header, footer, cell, sdt content, table (rows), row (cells). */
33
+ export declare function childrenOf(value: object): Element[] | undefined;
34
+ /** True for what a body or cell may hold directly (the Body content union), by TYPE_NAME. */
35
+ export declare const BLOCK_LEVEL_TYPES: Set<string>;
36
+ /** A run element with the given items and, optionally, run properties (the builders' r() takes text; this takes items). */
37
+ export declare function runOf(items: Element[], rPr?: wml.RPr): Element<wml.R>;
38
+ /** A paragraph element with the given runs and, optionally, paragraph properties. */
39
+ export declare function paragraphOf(runs: Element[], pPr?: wml.PPr): Element<wml.P>;
40
+ export { isElement as isTypedElement };