@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.
- package/README.md +8 -3
- package/dist/html/convert.d.mts +116 -0
- package/dist/html/convert.mjs +565 -0
- package/dist/html/elements.d.mts +19 -0
- package/dist/html/elements.mjs +182 -0
- package/dist/html/index.d.mts +2 -0
- package/dist/html/index.mjs +4 -0
- package/dist/model/content/Body.d.mts +13 -1
- package/dist/model/content/Body.mjs +97 -19
- package/dist/model/content/Comment.mjs +7 -0
- package/dist/model/content/InlinePicture.d.mts +89 -10
- package/dist/model/content/InlinePicture.mjs +245 -35
- package/dist/model/content/List.mjs +10 -1
- package/dist/model/content/Paragraph.d.mts +28 -5
- package/dist/model/content/Paragraph.mjs +77 -37
- package/dist/model/content/Range.d.mts +18 -2
- package/dist/model/content/Range.mjs +194 -64
- package/dist/model/content/Table.d.mts +38 -0
- package/dist/model/content/Table.mjs +80 -2
- package/dist/model/content/TrackedChange.d.mts +129 -3
- package/dist/model/content/TrackedChange.mjs +577 -12
- package/dist/model/content/binder.d.mts +2 -1
- package/dist/model/content/binder.mjs +48 -1
- package/dist/model/content/index.d.mts +4 -3
- package/dist/model/content/index.mjs +3 -2
- package/dist/model/content/relationships.d.mts +30 -0
- package/dist/model/content/relationships.mjs +133 -0
- package/dist/model/content/stylesFacade.d.mts +4 -2
- package/dist/model/content/stylesFacade.mjs +20 -6
- package/dist/model/content/tableColumns.d.mts +48 -0
- package/dist/model/content/tableColumns.mjs +484 -0
- package/dist/model/content/tree.d.mts +52 -2
- package/dist/model/content/tree.mjs +48 -3
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
- package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
- package/dist/model/customxml/bindings.d.mts +5 -2
- package/dist/model/customxml/bindings.mjs +21 -6
- package/dist/model/customxml/index.d.mts +3 -0
- package/dist/model/customxml/index.mjs +3 -0
- package/dist/model/customxml/opendope.d.mts +32 -0
- package/dist/model/customxml/opendope.mjs +56 -0
- package/dist/model/customxml/pictures.d.mts +57 -0
- package/dist/model/customxml/pictures.mjs +192 -0
- package/dist/model/customxml/xhtml.d.mts +42 -0
- package/dist/model/customxml/xhtml.mjs +162 -0
- package/dist/model/fonts/families.generated.mjs +2 -2
- package/dist/model/fonts/registry.mjs +7 -1
- package/dist/model/fonts/substitutions.generated.mjs +2 -2
- package/dist/model/fonts/themes.generated.mjs +1 -1
- package/dist/model/listnumbering/definitions.mjs +18 -0
- package/dist/model/properties/PropertyResolver.d.mts +27 -6
- package/dist/model/properties/PropertyResolver.mjs +83 -16
- package/dist/model/properties/catalogue.d.mts +15 -1
- package/dist/model/properties/catalogue.mjs +80 -27
- package/dist/model/properties/table.d.mts +16 -2
- package/dist/model/properties/table.mjs +13 -3
- package/dist/opc/Load.d.mts +9 -2
- package/dist/opc/index.d.mts +2 -2
- package/dist/opc/index.mjs +2 -2
- package/dist/opc/mce/preprocessor.d.mts +62 -6
- package/dist/opc/mce/preprocessor.mjs +142 -35
- package/dist/opc/mce/understood.d.mts +10 -0
- package/dist/opc/mce/understood.mjs +25 -3
- package/dist/packages/OpcPackage.d.mts +7 -0
- package/dist/packages/OpcPackage.mjs +15 -3
- package/dist/packages/WordprocessingMLPackage.d.mts +35 -2
- package/dist/packages/WordprocessingMLPackage.mjs +99 -2
- package/dist/parts/XmlPart.d.mts +26 -5
- package/dist/parts/XmlPart.mjs +67 -18
- package/dist/parts/wml/knownStyles.d.mts +1 -0
- package/dist/parts/wml/knownStyles.mjs +2233 -0
- package/package.json +7 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Font } from './Font.mjs';
|
|
2
2
|
import { runFontSelectorOf } from '../fonts/lookup.mjs';
|
|
3
|
-
import { typeNameOf, linkParents, runItemsOf, runsOf } from './tree.mjs';
|
|
3
|
+
import { typeNameOf, linkParents, runItemsOf, runsOf, revisionKindOf, WHOLE } from './tree.mjs';
|
|
4
4
|
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
5
5
|
import { ContentControl } from './ContentControl.mjs';
|
|
6
6
|
import { sdt as sdtOf, nextSdtId, applyRunOptions, t as textElement } from '@docx4j/generated-objects-ts/builders/wml';
|
|
@@ -15,12 +15,23 @@ import { commentApi } from './comments.mjs';
|
|
|
15
15
|
* A subset of Office JS `Word.Range`: a span of text within one paragraph, [start, end) in the
|
|
16
16
|
* paragraph's text. Positions are recomputed from the tree on every use, so a range stays
|
|
17
17
|
* valid across edits made through it; edits made elsewhere may shift it.
|
|
18
|
+
*
|
|
19
|
+
* A picture has a place in the text and no text of its own, so a picture at the range's start or
|
|
20
|
+
* end offset may be inside it or outside it. `whole` says: a range from `Paragraph.getRange('Whole')`
|
|
21
|
+
* or `'Content'` holds the paragraph's whole content, the pictures at its edges too, and deleting or
|
|
22
|
+
* replacing it deletes them; any other range holds the pictures strictly inside it, so that a search
|
|
23
|
+
* hit replaced does not take the picture beside it (CR-002 section 48).
|
|
18
24
|
*/
|
|
19
25
|
export class Range {
|
|
20
|
-
constructor(paragraph, start, end) {
|
|
26
|
+
constructor(paragraph, start, end, whole = false) {
|
|
21
27
|
this.paragraph = paragraph;
|
|
22
28
|
this.start = start;
|
|
23
29
|
this.end = end;
|
|
30
|
+
this.whole = whole;
|
|
31
|
+
}
|
|
32
|
+
/** The edges `Paragraph.splice` is given: both for a whole range. */
|
|
33
|
+
get edges() {
|
|
34
|
+
return this.whole ? WHOLE : undefined;
|
|
24
35
|
}
|
|
25
36
|
get paragraphs() {
|
|
26
37
|
return [this.paragraph];
|
|
@@ -93,7 +104,7 @@ export class Range {
|
|
|
93
104
|
insertText(text, location) {
|
|
94
105
|
let r;
|
|
95
106
|
if (location === 'Replace')
|
|
96
|
-
r = this.paragraph.splice(this.start, this.end, text);
|
|
107
|
+
r = this.paragraph.splice(this.start, this.end, text, 'before', this.edges);
|
|
97
108
|
// text put before the range joins the run the range starts in - inside the link the range is on,
|
|
98
109
|
// as Office JS (check 32: insertText('Before') on a link's text went into the link, as 'After' did)
|
|
99
110
|
else if (location === 'Before' || location === 'Start')
|
|
@@ -145,9 +156,9 @@ export class Range {
|
|
|
145
156
|
: { element: el, container: this.paragraph.container };
|
|
146
157
|
});
|
|
147
158
|
}
|
|
148
|
-
/** Removes the span's text. */
|
|
159
|
+
/** Removes the span's text, and the pictures in it (CR-002 section 48). */
|
|
149
160
|
delete() {
|
|
150
|
-
this.paragraph.splice(this.start, this.end, '');
|
|
161
|
+
this.paragraph.splice(this.start, this.end, '', 'before', this.edges);
|
|
151
162
|
this.end = this.start;
|
|
152
163
|
}
|
|
153
164
|
search(text, options) {
|
|
@@ -183,7 +194,7 @@ export class Range {
|
|
|
183
194
|
return new Range(this.paragraph, this.start, this.start);
|
|
184
195
|
if (location === 'End')
|
|
185
196
|
return new Range(this.paragraph, this.end, this.end);
|
|
186
|
-
return new Range(this.paragraph, this.start, this.end);
|
|
197
|
+
return new Range(this.paragraph, this.start, this.end, this.whole);
|
|
187
198
|
}
|
|
188
199
|
/** The span's text as it stands (extension; Office JS getOoxml wraps it in a package). */
|
|
189
200
|
toString() {
|
|
@@ -209,7 +220,7 @@ export class Range {
|
|
|
209
220
|
* span gets an empty control at its position. Returns the control.
|
|
210
221
|
*/
|
|
211
222
|
insertContentControl(kind) {
|
|
212
|
-
var _a
|
|
223
|
+
var _a;
|
|
213
224
|
const paragraph = this.paragraph;
|
|
214
225
|
const body = paragraph.parentBody;
|
|
215
226
|
// The control is built empty first: `sdt` refuses a kind that cannot be run-level (a repeating
|
|
@@ -220,62 +231,44 @@ export class Range {
|
|
|
220
231
|
paragraph.insertItemsAt(this.start, [sdt]);
|
|
221
232
|
return new ContentControl(sdt, containerOf(sdt, paragraph), body);
|
|
222
233
|
}
|
|
223
|
-
|
|
224
|
-
paragraph.
|
|
225
|
-
|
|
226
|
-
if (segments.length === 0)
|
|
234
|
+
// Every refusal before anything changes (CR-002 section 43): until then the runs were split first.
|
|
235
|
+
const covered = coveredSegments(paragraph, this.start, this.end);
|
|
236
|
+
if (covered.length === 0)
|
|
227
237
|
throw new Docx4JException('This range covers no run');
|
|
228
|
-
const
|
|
229
|
-
if (
|
|
238
|
+
const coveredOwner = covered[0].runOwner;
|
|
239
|
+
if (covered.some((s) => s.runOwner !== coveredOwner)) {
|
|
230
240
|
throw new Docx4JException('This range spans more than one run holder (a hyperlink or a tracked change); wrap a narrower span');
|
|
231
241
|
}
|
|
232
|
-
|
|
233
|
-
for (const segment of segments) {
|
|
234
|
-
const element = owner[segment.runIndex];
|
|
235
|
-
if (!items.includes(element))
|
|
236
|
-
items.push(element);
|
|
237
|
-
}
|
|
238
|
-
const at = owner.indexOf(items[0]);
|
|
242
|
+
refuseTrackedDeletion(coveredOwner, covered);
|
|
239
243
|
const tracker = paragraph.changeTracker;
|
|
240
|
-
const
|
|
244
|
+
const coveredRevision = revisionHolding(covered[0], coveredOwner);
|
|
245
|
+
if (tracker && coveredRevision && itemsKeyOf(coveredRevision) === undefined) {
|
|
246
|
+
throw new Docx4JException('This range is in a revision that cannot be split around a content control');
|
|
247
|
+
}
|
|
248
|
+
paragraph.splitAt(this.start);
|
|
249
|
+
paragraph.splitAt(this.end);
|
|
250
|
+
const segments = paragraph.segments().filter((s) => s.start >= this.start && s.end <= this.end);
|
|
251
|
+
const owner = segments[0].runOwner;
|
|
252
|
+
const { at, items } = wrappedItems(owner, segments);
|
|
253
|
+
const revision = revisionHolding(segments[0], owner);
|
|
241
254
|
if (tracker && revision) {
|
|
242
255
|
// The runs are in a revision: the control goes between its halves, not in it, and the runs go
|
|
243
256
|
// into the control in a revision of the same kind and attributes with a new id - Word's form for
|
|
244
257
|
// inserted text wrapped in a new control (check 30, inserted-text-wrapped: the w:ins inside the
|
|
245
258
|
// control, the control's own markers outside it).
|
|
246
|
-
|
|
247
|
-
revision
|
|
248
|
-
const value = revision.value;
|
|
249
|
-
const itemsKey = Object.keys(value).find((key) => value[key] === revision.items);
|
|
250
|
-
const inner = {};
|
|
251
|
-
for (const [key, v] of Object.entries(value))
|
|
252
|
-
if (key !== itemsKey && key !== 'PARENT')
|
|
253
|
-
inner[key] = v;
|
|
254
|
-
inner.id = tracker.nextId();
|
|
255
|
-
inner[itemsKey] = items;
|
|
256
|
-
const wrapped = { name: revision.element.name, value: inner };
|
|
257
|
-
linkParents(items, inner);
|
|
259
|
+
revision.items.splice(at, items.length);
|
|
260
|
+
const wrapped = revisionAround(revision, items, tracker.nextId());
|
|
258
261
|
sdt.value.sdtContent.content = [wrapped];
|
|
259
262
|
linkParents(wrapped, sdt.value.sdtContent);
|
|
260
|
-
|
|
261
|
-
const parent = (_a = revision.value.PARENT) !== null && _a !== void 0 ? _a : paragraph.p;
|
|
262
|
-
if (revision.items.length === 0) {
|
|
263
|
-
holderOwner.splice(holderOwner.indexOf(revision.element), 1, sdt); // the whole revision wrapped
|
|
264
|
-
linkParents(sdt, parent);
|
|
265
|
-
}
|
|
266
|
-
else if (index === 0) {
|
|
267
|
-
holderOwner.splice(holderOwner.indexOf(revision.element), 0, sdt); // its start wrapped
|
|
268
|
-
linkParents(sdt, parent);
|
|
269
|
-
}
|
|
270
|
-
else if (!paragraph.splitHolderAround(revision, index, sdt, () => tracker.nextId())) {
|
|
263
|
+
if (!placeInRevision(paragraph, revision, at, sdt, () => tracker.nextId())) {
|
|
271
264
|
throw new Docx4JException('This range is in a revision that cannot be split around a content control');
|
|
272
265
|
}
|
|
273
|
-
markHolderInserted(tracker, sdt,
|
|
274
|
-
return new ContentControl(sdt,
|
|
266
|
+
markHolderInserted(tracker, sdt, revision.owner, () => { });
|
|
267
|
+
return new ContentControl(sdt, revision.owner, body);
|
|
275
268
|
}
|
|
276
269
|
sdt.value.sdtContent.content = items;
|
|
277
270
|
owner.splice(at, items.length, sdt);
|
|
278
|
-
linkParents(sdt, (
|
|
271
|
+
linkParents(sdt, (_a = segments[0].run.PARENT) !== null && _a !== void 0 ? _a : paragraph.p);
|
|
279
272
|
// under tracking the control itself is the insertion, the runs in it what they were (check 30)
|
|
280
273
|
if (tracker)
|
|
281
274
|
markHolderInserted(tracker, sdt, owner, () => { });
|
|
@@ -340,12 +333,18 @@ export class Range {
|
|
|
340
333
|
start = Math.min(start, span[0]);
|
|
341
334
|
end = Math.max(end, span[1]);
|
|
342
335
|
}
|
|
343
|
-
this.removeHyperlinks();
|
|
344
|
-
if (value === '')
|
|
345
|
-
return;
|
|
346
336
|
const hash = value.indexOf('#');
|
|
347
337
|
const address = hash === -1 ? value : value.slice(0, hash);
|
|
348
338
|
const location = hash === -1 ? '' : value.slice(hash + 1);
|
|
339
|
+
// Every refusal comes before anything changes, judged on what the range would hold once the links
|
|
340
|
+
// it touches were unwrapped (CR-002 section 43). Until then the setter unwrapped them, added the
|
|
341
|
+
// relationship and split the runs before it refused, so a refused call left the links gone, an
|
|
342
|
+
// orphan relationship and split runs (found by the docx4j-python session).
|
|
343
|
+
if (value !== '')
|
|
344
|
+
this.refuseUnlinkable(start, end, address);
|
|
345
|
+
this.removeHyperlinks();
|
|
346
|
+
if (value === '')
|
|
347
|
+
return;
|
|
349
348
|
// w:history="1" is what Word writes on every hyperlink it makes, and what docx4j writes when
|
|
350
349
|
// it builds one (MainDocumentPart.addHyperlink): "add the target to the viewed hyperlinks"
|
|
351
350
|
// (ECMA-376 17.16.22). Readers do not depend on it, but a document holding one holder from
|
|
@@ -359,26 +358,79 @@ export class Range {
|
|
|
359
358
|
paragraph.splitAt(start);
|
|
360
359
|
paragraph.splitAt(end);
|
|
361
360
|
const segments = paragraph.segments().filter((seg) => seg.start >= start && seg.end <= end);
|
|
362
|
-
if (segments.length === 0)
|
|
363
|
-
throw new Docx4JException('This range covers no run');
|
|
364
361
|
const owner = segments[0].runOwner;
|
|
365
|
-
|
|
366
|
-
|
|
362
|
+
const { at, items } = wrappedItems(owner, segments);
|
|
363
|
+
const revision = revisionHolding(segments[0], owner);
|
|
364
|
+
let runsParent = holder.value;
|
|
365
|
+
if (revision) {
|
|
366
|
+
// A w:hyperlink cannot be in a w:ins or w:moveTo (CT_RunTrackChange holds EG_ContentRunContent
|
|
367
|
+
// only), so over runs in one, tracked or not, the link goes between the revision's halves and the
|
|
368
|
+
// runs into it in a revision of the same kind and attributes with a new id, as a content control
|
|
369
|
+
// over inserted text does (CR-002 section 43; the docx4j-python session's form). Until then the
|
|
370
|
+
// link went into the revision, which marshalled with an xsi:type. Word's own form for a link set
|
|
371
|
+
// on inserted text is a HYPERLINK field in the one w:ins (check 30, link-on-inserted-text), which
|
|
372
|
+
// this setter's getter would not read back.
|
|
373
|
+
revision.items.splice(at, items.length);
|
|
374
|
+
const inner = revisionAround(revision, items, paragraph.nextAnnotationId());
|
|
375
|
+
holder.value.content = [inner];
|
|
376
|
+
linkParents(inner, holder.value);
|
|
377
|
+
if (!placeInRevision(paragraph, revision, at, holder, () => paragraph.nextAnnotationId())) {
|
|
378
|
+
throw new Docx4JException('This range is in a revision that cannot be split around a hyperlink');
|
|
379
|
+
}
|
|
380
|
+
runsParent = inner.value;
|
|
367
381
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
items.push(element);
|
|
382
|
+
else {
|
|
383
|
+
holder.value.content = items;
|
|
384
|
+
owner.splice(at, items.length, holder);
|
|
385
|
+
linkParents(holder, (_a = segments[0].run.PARENT) !== null && _a !== void 0 ? _a : paragraph.p);
|
|
373
386
|
}
|
|
374
|
-
const at = owner.indexOf(items[0]);
|
|
375
|
-
holder.value.content = items;
|
|
376
|
-
owner.splice(at, items.length, holder);
|
|
377
|
-
linkParents(holder, (_a = segments[0].run.PARENT) !== null && _a !== void 0 ? _a : paragraph.p);
|
|
378
387
|
this.restyle(holder.value, true);
|
|
379
|
-
mergeTextRuns(
|
|
388
|
+
mergeTextRuns(items, runsParent);
|
|
380
389
|
(_c = (_b = paragraph.parentBody.package_) === null || _b === void 0 ? void 0 : _b.requireStyles) === null || _c === void 0 ? void 0 : _c.call(_b, [HYPERLINK_STYLE]);
|
|
381
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Refuses, before anything changes, a link the setter could not make over [start, end): one with an
|
|
393
|
+
* address on a range with no part, over no run, over runs in more than one holder, or around a
|
|
394
|
+
* tracked deletion - each judged as the range would stand once the links it touches were unwrapped,
|
|
395
|
+
* their runs then in the list holding them.
|
|
396
|
+
*/
|
|
397
|
+
refuseUnlinkable(start, end, address) {
|
|
398
|
+
const paragraph = this.paragraph;
|
|
399
|
+
if (address !== '' && !paragraph.parentBody.part) {
|
|
400
|
+
throw new Docx4JException('This range has no part, so a hyperlink cannot be related to it');
|
|
401
|
+
}
|
|
402
|
+
const touched = hyperlinkHolders(paragraph).filter((h) => this.touches(h));
|
|
403
|
+
const unwrapsInto = new Map();
|
|
404
|
+
for (const holder of touched) {
|
|
405
|
+
const inner = runItemsOf(holder.value);
|
|
406
|
+
if (inner)
|
|
407
|
+
unwrapsInto.set(inner, containerOf(holder, paragraph));
|
|
408
|
+
}
|
|
409
|
+
const ownerOnceUnwrapped = (owner) => {
|
|
410
|
+
let o = owner;
|
|
411
|
+
for (let guard = 0; guard < 100 && unwrapsInto.has(o); guard++)
|
|
412
|
+
o = unwrapsInto.get(o);
|
|
413
|
+
return o;
|
|
414
|
+
};
|
|
415
|
+
const covered = coveredSegments(paragraph, start, end);
|
|
416
|
+
if (covered.length === 0)
|
|
417
|
+
throw new Docx4JException('This range covers no run');
|
|
418
|
+
const owner = ownerOnceUnwrapped(covered[0].runOwner);
|
|
419
|
+
if (covered.some((seg) => ownerOnceUnwrapped(seg.runOwner) !== owner)) {
|
|
420
|
+
throw new Docx4JException('This range spans more than one run holder (a hyperlink or a tracked change); wrap a narrower span');
|
|
421
|
+
}
|
|
422
|
+
const unwrapped = new Set(touched);
|
|
423
|
+
const flatten = (items) => items.flatMap((item) => {
|
|
424
|
+
var _a;
|
|
425
|
+
return (unwrapped.has(item)
|
|
426
|
+
? flatten(((_a = runItemsOf(item.value)) !== null && _a !== void 0 ? _a : [])) : [item]);
|
|
427
|
+
});
|
|
428
|
+
refuseTrackedDeletion(flatten(owner), covered);
|
|
429
|
+
const revision = revisionHolding(covered[0], owner);
|
|
430
|
+
if (revision && itemsKeyOf(revision) === undefined) {
|
|
431
|
+
throw new Docx4JException('This range is in a revision that cannot be split around a hyperlink');
|
|
432
|
+
}
|
|
433
|
+
}
|
|
382
434
|
/** The first `w:hyperlink` this range touches, if any. */
|
|
383
435
|
hyperlinkHolder() {
|
|
384
436
|
return hyperlinkHolders(this.paragraph).find((holder) => this.touches(holder));
|
|
@@ -541,6 +593,84 @@ function hyperlinkHolders(paragraph) {
|
|
|
541
593
|
return out;
|
|
542
594
|
}
|
|
543
595
|
/** The run-level array holding an element, after `insertItemsAt` put it somewhere in the paragraph. */
|
|
596
|
+
/**
|
|
597
|
+
* The segments a range covers, read before its runs are split at its edges: those inside it, and those
|
|
598
|
+
* a split would cut, whose inner part then is. What `segments()` gives inside the range once it is
|
|
599
|
+
* split, so that a wrapper's refusals can come before anything changes.
|
|
600
|
+
*/
|
|
601
|
+
function coveredSegments(paragraph, start, end) {
|
|
602
|
+
return paragraph.segments().filter((s) => (s.start >= start && s.end <= end) || (s.start < end && s.end > start));
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* The items a range's runs are wrapped in place of: everything from the first run it covers to the
|
|
606
|
+
* last, in the one list holding them, so that what lies between - a bookmark, a comment's range, a
|
|
607
|
+
* proofing mark - goes into the wrapper with them. Until CR-002 section 43 as many items were taken as
|
|
608
|
+
* there were runs, from the first, which dropped what lay between and left a run in two places.
|
|
609
|
+
*/
|
|
610
|
+
function wrappedItems(owner, segments) {
|
|
611
|
+
const indexes = segments.map((s) => owner.indexOf(s.runOwner[s.runIndex])).filter((i) => i >= 0);
|
|
612
|
+
const at = Math.min(...indexes);
|
|
613
|
+
return { at, items: owner.slice(at, Math.max(...indexes) + 1) };
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Refuses a range with a tracked deletion (or text moved away) between its runs: having no text in
|
|
617
|
+
* the range's, it would be wrapped unseen, and a range spanning a tracked change is refused (CR-002
|
|
618
|
+
* section 43).
|
|
619
|
+
*/
|
|
620
|
+
function refuseTrackedDeletion(items, segments) {
|
|
621
|
+
const indexes = segments.map((s) => items.indexOf(s.runOwner[s.runIndex])).filter((i) => i >= 0);
|
|
622
|
+
if (indexes.length === 0)
|
|
623
|
+
return;
|
|
624
|
+
for (const item of items.slice(Math.min(...indexes), Math.max(...indexes) + 1)) {
|
|
625
|
+
const kind = revisionKindOf(item);
|
|
626
|
+
if (kind === 'del' || kind === 'moveFrom') {
|
|
627
|
+
throw new Docx4JException('This range spans a tracked deletion; wrap the text on either side of it');
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/** The `w:ins` or `w:moveTo` a segment's run is directly in, when `owner` is its own item list. */
|
|
632
|
+
function revisionHolding(segment, owner) {
|
|
633
|
+
return segment.revision !== undefined && segment.revision.items === owner ? segment.revision : undefined;
|
|
634
|
+
}
|
|
635
|
+
/** The key under which a revision's value holds its items, or undefined where none does. */
|
|
636
|
+
function itemsKeyOf(revision) {
|
|
637
|
+
const value = revision.value;
|
|
638
|
+
return Object.keys(value).find((key) => value[key] === revision.items);
|
|
639
|
+
}
|
|
640
|
+
/** A revision of the same kind and attributes as `revision`, with a new id, holding `items`. */
|
|
641
|
+
function revisionAround(revision, items, id) {
|
|
642
|
+
const value = revision.value;
|
|
643
|
+
const itemsKey = itemsKeyOf(revision);
|
|
644
|
+
const inner = {};
|
|
645
|
+
for (const [key, v] of Object.entries(value))
|
|
646
|
+
if (key !== itemsKey && key !== 'PARENT')
|
|
647
|
+
inner[key] = v;
|
|
648
|
+
inner.id = id;
|
|
649
|
+
inner[itemsKey] = items;
|
|
650
|
+
linkParents(items, inner);
|
|
651
|
+
return { name: revision.element.name, value: inner };
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Puts `item` where items from `index` on were taken out of `revision`: in its place where it holds
|
|
655
|
+
* nothing now, before it where they were its first, else between its halves. False where the revision
|
|
656
|
+
* cannot be split.
|
|
657
|
+
*/
|
|
658
|
+
function placeInRevision(paragraph, revision, index, item, nextId) {
|
|
659
|
+
var _a;
|
|
660
|
+
const owner = revision.owner;
|
|
661
|
+
const parent = (_a = revision.value.PARENT) !== null && _a !== void 0 ? _a : paragraph.p;
|
|
662
|
+
if (revision.items.length === 0) {
|
|
663
|
+
owner.splice(owner.indexOf(revision.element), 1, item); // the whole revision wrapped
|
|
664
|
+
linkParents(item, parent);
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
if (index === 0) {
|
|
668
|
+
owner.splice(owner.indexOf(revision.element), 0, item); // its start wrapped
|
|
669
|
+
linkParents(item, parent);
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
return paragraph.splitHolderAround(revision, index, item, nextId);
|
|
673
|
+
}
|
|
544
674
|
function containerOf(element, paragraph) {
|
|
545
675
|
var _a, _b;
|
|
546
676
|
var _c;
|
|
@@ -4,6 +4,7 @@ import type { Body } from './Body.mjs';
|
|
|
4
4
|
import type { Paragraph } from './Paragraph.mjs';
|
|
5
5
|
import type { Range } from './Range.mjs';
|
|
6
6
|
import { type ChangeTracker } from './tracking.mjs';
|
|
7
|
+
import { type TableLayoutMode } from './tableColumns.mjs';
|
|
7
8
|
/**
|
|
8
9
|
* A subset of Office JS `Word.Table` over a `w:tbl`. Rows are read through row-level content
|
|
9
10
|
* controls (an OpenDoPE repeat wraps its `w:tr` in a `w:sdt`), so `rows` is what Word shows.
|
|
@@ -51,6 +52,30 @@ export declare class Table {
|
|
|
51
52
|
* tracks changes the rows stay and take a `w:trPr/w:del` instead (CR-002 phase F).
|
|
52
53
|
*/
|
|
53
54
|
deleteRows(rowIndex: number, rowCount?: number): void;
|
|
55
|
+
/**
|
|
56
|
+
* Adds columns at the start or the end (Office JS `Table.addColumns`), the new cells taking the
|
|
57
|
+
* properties and width of the column beside them - the one to the right of the boundary when
|
|
58
|
+
* there is one, else the left - in the table's layout mode as Word 365 writes it (CR-002 section
|
|
59
|
+
* 42, checks 39 and 40): fixed copies the width and grows the table past the margin, AutoFit to
|
|
60
|
+
* window rescales the grid and writes the cells' percentages again, AutoFit to contents stays
|
|
61
|
+
* automatic. `values` is by row, then by new column. Returns the new cells per row, an extension
|
|
62
|
+
* (Office JS returns nothing); a row whose skipped columns grew instead has none. Tracked, the
|
|
63
|
+
* table change is recorded as Office JS records one and the new cells' text is an insertion.
|
|
64
|
+
*/
|
|
65
|
+
addColumns(location: 'Start' | 'End', columnCount: number, values?: string[][]): TableCell[][];
|
|
66
|
+
/**
|
|
67
|
+
* Removes `columnCount` grid columns from `columnIndex` (one by default; Office JS
|
|
68
|
+
* `Table.deleteColumns`): a cell covering only those columns goes, one spanning further shrinks.
|
|
69
|
+
* Tracked, the cells still go and the table change is recorded, as Office JS's deletion leaves
|
|
70
|
+
* it (section 42.3; check 40c; Word's own is not tracked at all, known issues entry 2).
|
|
71
|
+
*/
|
|
72
|
+
deleteColumns(columnIndex: number, columnCount?: number): void;
|
|
73
|
+
/** Which of Word's three layout modes the table is in: fixed, AutoFit to window, or AutoFit to contents (extension). */
|
|
74
|
+
get layoutMode(): TableLayoutMode;
|
|
75
|
+
/** The section's text width in twips, for the window mode and a percentage width. */
|
|
76
|
+
textWidth(): number;
|
|
77
|
+
/** Views of new cells, row by row. */
|
|
78
|
+
cellViewsOf(cells: Element<wml.Tc>[][]): TableCell[][];
|
|
54
79
|
/** Removes the table from its container; tracked, every row is marked deleted instead. */
|
|
55
80
|
delete(): void;
|
|
56
81
|
/** The package's change tracker while `changeTrackingMode` is on (CR-002 phase F). */
|
|
@@ -118,7 +143,20 @@ export declare class TableCell {
|
|
|
118
143
|
insertText(text: string, location: 'Start' | 'End' | 'Replace'): Range;
|
|
119
144
|
/** The cell width in points (w:tcW in twips), as Office JS reports it; 0 when it is not a fixed width. */
|
|
120
145
|
get width(): number;
|
|
146
|
+
/**
|
|
147
|
+
* Written in the form the cell states, else its table, else twips (CR-002 section 42.2 item 4): a
|
|
148
|
+
* cell in percent takes the points as a share of the section's text width; `auto` becomes twips,
|
|
149
|
+
* a stated width being what was asked for.
|
|
150
|
+
*/
|
|
121
151
|
set width(points: number);
|
|
152
|
+
/**
|
|
153
|
+
* New columns before or after this cell (Office JS `TableCell.insertColumns`), the new cells
|
|
154
|
+
* taking this cell's properties and width, as `Table.addColumns` does. Returns the new cells per
|
|
155
|
+
* row (extension).
|
|
156
|
+
*/
|
|
157
|
+
insertColumns(location: 'Before' | 'After', columnCount: number, values?: string[][]): TableCell[][];
|
|
158
|
+
/** Removes the grid columns this cell covers (Office JS `TableCell.deleteColumn`); tracked as `Table.deleteColumns` is. */
|
|
159
|
+
deleteColumn(): void;
|
|
122
160
|
/** Office JS TableCell.columnWidth, the same value. */
|
|
123
161
|
get columnWidth(): number;
|
|
124
162
|
set columnWidth(points: number);
|
|
@@ -4,6 +4,7 @@ import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
|
4
4
|
import { typeNameOf, childrenOf, linkParents, textOf, rowsOf, cellsOf } from './tree.mjs';
|
|
5
5
|
import { builtInOf, idOfBuiltIn, styleNameOf, styleIdOf } from './styles.mjs';
|
|
6
6
|
import { trackInsertedBlocks } from './tracking.mjs';
|
|
7
|
+
import { insertGridColumns, deleteGridColumns, gridRangeOf, textWidthTwips, widthTypeOf, layoutModeOf } from './tableColumns.mjs';
|
|
7
8
|
/** The default table width in twips (A4 with 2.54 cm margins), as the objects package's `tbl` builder uses. */
|
|
8
9
|
const DEFAULT_WIDTH = 9026;
|
|
9
10
|
const TWIPS_PER_POINT = 20;
|
|
@@ -128,6 +129,51 @@ export class Table {
|
|
|
128
129
|
row.container.splice(i, 1);
|
|
129
130
|
}
|
|
130
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Adds columns at the start or the end (Office JS `Table.addColumns`), the new cells taking the
|
|
134
|
+
* properties and width of the column beside them - the one to the right of the boundary when
|
|
135
|
+
* there is one, else the left - in the table's layout mode as Word 365 writes it (CR-002 section
|
|
136
|
+
* 42, checks 39 and 40): fixed copies the width and grows the table past the margin, AutoFit to
|
|
137
|
+
* window rescales the grid and writes the cells' percentages again, AutoFit to contents stays
|
|
138
|
+
* automatic. `values` is by row, then by new column. Returns the new cells per row, an extension
|
|
139
|
+
* (Office JS returns nothing); a row whose skipped columns grew instead has none. Tracked, the
|
|
140
|
+
* table change is recorded as Office JS records one and the new cells' text is an insertion.
|
|
141
|
+
*/
|
|
142
|
+
addColumns(location, columnCount, values) {
|
|
143
|
+
const at = location === 'Start' ? 0 : this.columnWidths().length;
|
|
144
|
+
const inserted = insertGridColumns(this.tbl, this.textWidth(), at, columnCount, values, this.changeTracker);
|
|
145
|
+
return this.cellViewsOf(inserted.cells);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Removes `columnCount` grid columns from `columnIndex` (one by default; Office JS
|
|
149
|
+
* `Table.deleteColumns`): a cell covering only those columns goes, one spanning further shrinks.
|
|
150
|
+
* Tracked, the cells still go and the table change is recorded, as Office JS's deletion leaves
|
|
151
|
+
* it (section 42.3; check 40c; Word's own is not tracked at all, known issues entry 2).
|
|
152
|
+
*/
|
|
153
|
+
deleteColumns(columnIndex, columnCount = 1) {
|
|
154
|
+
var _a, _b, _c;
|
|
155
|
+
deleteGridColumns(this.tbl, this.textWidth(), columnIndex, columnCount, this.changeTracker);
|
|
156
|
+
if (((_c = (_b = (_a = this.tbl.tblGrid) === null || _a === void 0 ? void 0 : _a.gridCol) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) === 0 || rowsOf(this.tbl).every((row) => cellsOf(row.element.value).length === 0))
|
|
157
|
+
this.delete();
|
|
158
|
+
}
|
|
159
|
+
/** Which of Word's three layout modes the table is in: fixed, AutoFit to window, or AutoFit to contents (extension). */
|
|
160
|
+
get layoutMode() {
|
|
161
|
+
return layoutModeOf(this.tbl);
|
|
162
|
+
}
|
|
163
|
+
/** The section's text width in twips, for the window mode and a percentage width. */
|
|
164
|
+
textWidth() {
|
|
165
|
+
return textWidthTwips(this.parentBody.container);
|
|
166
|
+
}
|
|
167
|
+
/** Views of new cells, row by row. */
|
|
168
|
+
cellViewsOf(cells) {
|
|
169
|
+
const rows = this.rows;
|
|
170
|
+
return cells.map((rowCells, i) => {
|
|
171
|
+
const row = rows[i];
|
|
172
|
+
if (!row || rowCells.length === 0)
|
|
173
|
+
return [];
|
|
174
|
+
return row.cells.filter((cell) => rowCells.includes(cell.element));
|
|
175
|
+
});
|
|
176
|
+
}
|
|
131
177
|
/** Removes the table from its container; tracked, every row is marked deleted instead. */
|
|
132
178
|
delete() {
|
|
133
179
|
const tracker = this.changeTracker;
|
|
@@ -311,12 +357,44 @@ export class TableCell {
|
|
|
311
357
|
const w = (_a = this.tc.tcPr) === null || _a === void 0 ? void 0 : _a.tcW;
|
|
312
358
|
return (w === null || w === void 0 ? void 0 : w.type) === 'dxa' ? ((_b = w.w) !== null && _b !== void 0 ? _b : 0) / TWIPS_PER_POINT : 0;
|
|
313
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Written in the form the cell states, else its table, else twips (CR-002 section 42.2 item 4): a
|
|
362
|
+
* cell in percent takes the points as a share of the section's text width; `auto` becomes twips,
|
|
363
|
+
* a stated width being what was asked for.
|
|
364
|
+
*/
|
|
314
365
|
set width(points) {
|
|
315
366
|
var _a, _b;
|
|
316
367
|
var _c, _d;
|
|
368
|
+
const twips = Math.round(points * TWIPS_PER_POINT);
|
|
317
369
|
const tcW = ((_b = (_d = ((_a = (_c = this.tc).tcPr) !== null && _a !== void 0 ? _a : (_c.tcPr = {}))).tcW) !== null && _b !== void 0 ? _b : (_d.tcW = {}));
|
|
318
|
-
|
|
319
|
-
|
|
370
|
+
if (widthTypeOf(this.tc, this.parentTable.tbl) === 'pct') {
|
|
371
|
+
tcW.w = Math.round((twips * 5000) / this.parentTable.textWidth());
|
|
372
|
+
tcW.type = 'pct';
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
tcW.w = twips;
|
|
376
|
+
tcW.type = 'dxa';
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* New columns before or after this cell (Office JS `TableCell.insertColumns`), the new cells
|
|
381
|
+
* taking this cell's properties and width, as `Table.addColumns` does. Returns the new cells per
|
|
382
|
+
* row (extension).
|
|
383
|
+
*/
|
|
384
|
+
insertColumns(location, columnCount, values) {
|
|
385
|
+
const range = gridRangeOf(this.parentRow.tr, this.tc);
|
|
386
|
+
if (!range)
|
|
387
|
+
throw new Docx4JException('This cell is not in its row');
|
|
388
|
+
const table = this.parentTable;
|
|
389
|
+
const inserted = insertGridColumns(table.tbl, table.textWidth(), location === 'Before' ? range.start : range.end, columnCount, values, table.changeTracker);
|
|
390
|
+
return table.cellViewsOf(inserted.cells);
|
|
391
|
+
}
|
|
392
|
+
/** Removes the grid columns this cell covers (Office JS `TableCell.deleteColumn`); tracked as `Table.deleteColumns` is. */
|
|
393
|
+
deleteColumn() {
|
|
394
|
+
const range = gridRangeOf(this.parentRow.tr, this.tc);
|
|
395
|
+
if (!range)
|
|
396
|
+
throw new Docx4JException('This cell is not in its row');
|
|
397
|
+
this.parentTable.deleteColumns(range.start, range.end - range.start);
|
|
320
398
|
}
|
|
321
399
|
/** Office JS TableCell.columnWidth, the same value. */
|
|
322
400
|
get columnWidth() {
|