@docx4j/core-ts 0.4.0 → 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 +5 -2
- package/dist/html/convert.mjs +38 -14
- package/dist/model/content/Body.d.mts +13 -1
- package/dist/model/content/Body.mjs +90 -19
- package/dist/model/content/Comment.mjs +7 -0
- package/dist/model/content/InlinePicture.d.mts +71 -7
- package/dist/model/content/InlinePicture.mjs +201 -28
- 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/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 +2 -2
- package/dist/model/content/index.mjs +1 -1
- package/dist/model/content/relationships.d.mts +30 -0
- package/dist/model/content/relationships.mjs +133 -0
- package/dist/model/content/tree.d.mts +52 -2
- package/dist/model/content/tree.mjs +48 -3
- package/dist/model/customxml/pictures.d.mts +5 -2
- package/dist/model/customxml/pictures.mjs +13 -7
- 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 +98 -2
- package/dist/parts/XmlPart.d.mts +26 -5
- package/dist/parts/XmlPart.mjs +67 -18
- package/package.json +2 -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;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
|
|
2
|
+
import type * as math from '@docx4j/generated-objects-ts/modules/org_docx4j_math';
|
|
2
3
|
import { type Element, type Located, type RevisionKind } from './tree.mjs';
|
|
3
4
|
import { Range } from './Range.mjs';
|
|
4
5
|
import type { Paragraph } from './Paragraph.mjs';
|
|
6
|
+
import type { Body } from './Body.mjs';
|
|
7
|
+
import type { Part } from '../../parts/Part.mjs';
|
|
8
|
+
import { type RelationshipRemovals } from './relationships.mjs';
|
|
5
9
|
/** Office JS `Word.ChangeTrackingState` (`Unknown` is not produced here). */
|
|
6
10
|
export type TrackedChangeType = 'Added' | 'Deleted' | 'Formatted' | 'None' | 'Unknown';
|
|
7
11
|
/** Which piece of revision markup a `TrackedChange` is over (an extension: Office JS has no such member). */
|
|
@@ -112,6 +116,51 @@ export type TrackedChangeTarget =
|
|
|
112
116
|
outer: [Element, Element];
|
|
113
117
|
inner: [Element, Element];
|
|
114
118
|
value: wml.CTTrackChange;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* A field Word inserted under tracking, in its own form: a `w:ins` around the field's begin and
|
|
122
|
+
* instruction runs and another around its end, the separator and the result between them plain
|
|
123
|
+
* (check 30, `field-inserted`). Office JS lists it as one `Added` change whose text is the result's,
|
|
124
|
+
* and Word's Reject All took the whole field away, the result with it (CR-002 section 44). `span` is
|
|
125
|
+
* the items of `owner` from the first `w:ins` to the last.
|
|
126
|
+
*/
|
|
127
|
+
| {
|
|
128
|
+
kind: 'field';
|
|
129
|
+
span: Element[];
|
|
130
|
+
owner: Element[];
|
|
131
|
+
value: wml.CTTrackChange;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A `w:ins` or `w:del` inside an `m:r`, which is where Word writes an equation's text inserted or
|
|
135
|
+
* deleted (`m:r` > `w:ins` > `m:rPr`, `w:rPr`, `m:t`; docx4j's `tracked-changes-equations.docx`).
|
|
136
|
+
* `item` is the `w:ins` or `w:del` in the run's content, `run` the `m:r` and `runOwner` its list.
|
|
137
|
+
* Kept, its content takes its place in the run; not kept, it goes, and a run left with no text with it
|
|
138
|
+
* (CR-002 section 51). `equation` is the `m:oMath` or `m:oMathPara` in the paragraph that holds it.
|
|
139
|
+
*/
|
|
140
|
+
| {
|
|
141
|
+
kind: 'mathRun';
|
|
142
|
+
revision: 'ins' | 'del';
|
|
143
|
+
item: Element;
|
|
144
|
+
run: Element<math.CTR>;
|
|
145
|
+
runOwner: Element[];
|
|
146
|
+
value: wml.CTMathRunTrackChange;
|
|
147
|
+
equation?: Element;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A `w:ins` or `w:del` in an equation structure's `m:ctrlPr`, Word's mark of an `m:sSup`, an `m:acc`,
|
|
151
|
+
* ... inserted or deleted as a whole. `structure` is that element and `owner` its list, two up from
|
|
152
|
+
* the `m:ctrlPr`; undefined for an argument's own `m:ctrlPr` (an `m:e`'s), which marks no structure.
|
|
153
|
+
* Kept, the marker goes and the run properties it recorded become the control's; not kept, the
|
|
154
|
+
* structure goes whole, as Word's Accept All and Reject All did (CR-002 section 51).
|
|
155
|
+
*/
|
|
156
|
+
| {
|
|
157
|
+
kind: 'mathStructure';
|
|
158
|
+
revision: 'ins' | 'del';
|
|
159
|
+
ctrlPr: math.CTCtrlPr;
|
|
160
|
+
structure?: Element;
|
|
161
|
+
owner?: Element[];
|
|
162
|
+
value: wml.CTRPrChange;
|
|
163
|
+
equation?: Element;
|
|
115
164
|
};
|
|
116
165
|
/**
|
|
117
166
|
* A subset of Office JS `Word.TrackedChange`: `type`, `author`, `date`, `text`, `accept()`,
|
|
@@ -122,9 +171,13 @@ export declare class TrackedChange {
|
|
|
122
171
|
readonly target: TrackedChangeTarget;
|
|
123
172
|
/** The paragraph the change is in; undefined for a row revision. */
|
|
124
173
|
readonly paragraph: Paragraph | undefined;
|
|
174
|
+
/** The body a change without a paragraph (a row, a cell) is in, for the notes it may take away. */
|
|
175
|
+
readonly body?: Body | undefined;
|
|
125
176
|
constructor(target: TrackedChangeTarget,
|
|
126
177
|
/** The paragraph the change is in; undefined for a row revision. */
|
|
127
|
-
paragraph: Paragraph | undefined
|
|
178
|
+
paragraph: Paragraph | undefined,
|
|
179
|
+
/** The body a change without a paragraph (a row, a cell) is in, for the notes it may take away. */
|
|
180
|
+
body?: Body | undefined);
|
|
128
181
|
/** Office JS `Word.ChangeTrackingState`. */
|
|
129
182
|
get type(): TrackedChangeType;
|
|
130
183
|
get author(): string;
|
|
@@ -147,13 +200,76 @@ export declare class TrackedChange {
|
|
|
147
200
|
* Either half of a move keeps the whole move, as Word's Review tab does (CR-002 section 29).
|
|
148
201
|
*/
|
|
149
202
|
accept(): void;
|
|
203
|
+
private acceptHere;
|
|
150
204
|
/**
|
|
151
205
|
* Puts back what was there: a `w:ins` is removed, a `w:del` restored, a `w:rPrChange` re-applied.
|
|
152
206
|
* Either half of a move rejects the whole move, as Word's Review tab does (CR-002 section 29).
|
|
153
207
|
*/
|
|
154
208
|
reject(): void;
|
|
209
|
+
private rejectHere;
|
|
210
|
+
/** The body the change is in. */
|
|
211
|
+
private get body_();
|
|
212
|
+
/** The package the change is in, which keeps the notes whose references it took away. */
|
|
213
|
+
private get package_();
|
|
155
214
|
private requireParagraph;
|
|
156
215
|
}
|
|
216
|
+
/** The ids of the footnotes and endnotes whose references removed content took with it. */
|
|
217
|
+
export interface NoteRemovals {
|
|
218
|
+
footnote: Set<number>;
|
|
219
|
+
endnote: Set<number>;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Runs `resolve`, then takes away what went with the content it removed:
|
|
223
|
+
* - **Notes**: Word took a footnote from the notes part when its reference went with the content
|
|
224
|
+
* around it, accepting a deletion and rejecting an insertion alike (the editor's `review-for-word`
|
|
225
|
+
* saves, Word 15 and Word 2010); the entries go when the package is saved
|
|
226
|
+
* (`WordprocessingMLPackage.pendingNoteRemovals`).
|
|
227
|
+
* - **Bookmarks**: Word removed a bookmark whole when its end went with a deletion it accepted, and
|
|
228
|
+
* when the insertion it was put around went with a reject (docx4j CR-043's probe, C11 and C11b, Word
|
|
229
|
+
* 16.0.20326). docx4j's rule from the two (its CR-043 section 3.2) is followed here: a bookmark with a
|
|
230
|
+
* marker in removed content goes whole, its other marker too, even with content left beside it; so
|
|
231
|
+
* does one with content between its markers that is left with none, a deletion it was around accepted
|
|
232
|
+
* among them (U15); and one that was empty before, as `_GoBack` is, stays. Those three are not measured.
|
|
233
|
+
* - **Relationships and parts**: Word's saves kept no part the resolved content alone referenced. A
|
|
234
|
+
* picture deleted and accepted, or inserted and rejected, took its image part (docx4j CR-043's probe,
|
|
235
|
+
* C05 and C04), and a section break deleted and accepted took the headers and footers of the section
|
|
236
|
+
* it ended (C06: six of each became three). docx4j's rule (its CR-043 section 3.2, D7) is followed: a
|
|
237
|
+
* relationship of the change's part that the removed content referenced, through any `r:` attribute,
|
|
238
|
+
* goes when nothing left in the part references it; its part goes when no relationship in the package
|
|
239
|
+
* targets it any more, and what that part alone related to goes with it. The kinds beyond pictures,
|
|
240
|
+
* headers and footers (charts, OLE objects, `v:imagedata`) are docx4j's reading, not measured.
|
|
241
|
+
* A change resolved inside another's (a row's cells, a group's pieces, `acceptAll`'s changes) shares the
|
|
242
|
+
* outer resolution, so the bookmarks are read twice for the lot, and not at all when nothing is removed.
|
|
243
|
+
*/
|
|
244
|
+
export declare function resolving(pkg: unknown, body: Body | undefined, resolve: () => void): void;
|
|
245
|
+
/** What `flushPendingNoteRemovals` needs of the main document part. */
|
|
246
|
+
interface NotesHost {
|
|
247
|
+
getContents(): Promise<unknown>;
|
|
248
|
+
footnotesPart?: Part & {
|
|
249
|
+
getContents(): Promise<wml.CTFootnotes>;
|
|
250
|
+
};
|
|
251
|
+
endnotesPart?: Part & {
|
|
252
|
+
getContents(): Promise<wml.CTEndnotes>;
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/** Where a removed note's own content goes on to: the package's queues (`WordprocessingMLPackage`). */
|
|
256
|
+
interface NoteRemovalHost {
|
|
257
|
+
pendingCommentRemovals?: Set<number>;
|
|
258
|
+
pendingRelationshipRemovals?: RelationshipRemovals;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* The notes `pending` holds taken out of the notes parts, each unless a reference to it still stands in
|
|
262
|
+
* the main document (an accepted move keeps the reference its source had, say); then `pending` is
|
|
263
|
+
* empty. A note's entry goes, Word's separators never: their ids are never referenced. The notes parts
|
|
264
|
+
* are read only when something is pending, so a save with none leaves them untouched. Word's own save
|
|
265
|
+
* also renumbers the notes left (the editor's `review-for-word` saves); their ids stay here.
|
|
266
|
+
*
|
|
267
|
+
* What a removed note held goes on with it, as docx4j's `Removals.flush` takes it (CR-002 section 50,
|
|
268
|
+
* docx4j's reading, not measured): the relationships its content referenced (a picture's image) are
|
|
269
|
+
* queued on `host` for the notes part, to go when nothing else uses them, and a comment whose reference
|
|
270
|
+
* was in it is queued for removal, its anchor being gone.
|
|
271
|
+
*/
|
|
272
|
+
export declare function flushPendingNoteRemovals(main: NotesHost, pending: NoteRemovals, host?: NoteRemovalHost): Promise<void>;
|
|
157
273
|
/**
|
|
158
274
|
* The insertion of a content control or custom XML element, when `items[i]` is a
|
|
159
275
|
* `w:customXmlInsRangeStart` and what follows is Word's form for one (check 30): the element, whose
|
|
@@ -161,6 +277,15 @@ export declare class TrackedChange {
|
|
|
161
277
|
* one's end right after the element. Anything else is not one (a marker on its own is left alone).
|
|
162
278
|
*/
|
|
163
279
|
export declare function holderInsertionAt(items: Element[], i: number, paragraph: Paragraph | undefined): TrackedChange | undefined;
|
|
280
|
+
/**
|
|
281
|
+
* Word's form for a field inserted under tracking, when `items[i]` starts it: a `w:ins` that opens a
|
|
282
|
+
* field (a `w:fldChar` begin it does not end), then plain runs - the separator and the result - and
|
|
283
|
+
* markers, then a `w:ins` by the same author in which that field ends (check 30, `field-inserted`:
|
|
284
|
+
* begin and instruction in one, the end run in the other). Another revision, a run whose formatting
|
|
285
|
+
* changed, a run holder, or a field that ends in a plain run is not this form, and is listed as before.
|
|
286
|
+
* A field wholly inside one `w:ins` needs nothing of this: that `w:ins` already takes it whole.
|
|
287
|
+
*/
|
|
288
|
+
export declare function insertedFieldAt(items: Element[], i: number, paragraph: Paragraph | undefined): TrackedChange | undefined;
|
|
164
289
|
/**
|
|
165
290
|
* docx4j AcceptTrackedChanges: the paragraph takes the next one's content and properties, so the
|
|
166
291
|
* mark that survives is the next one's. Called when a deleted mark is accepted and when an
|
|
@@ -200,11 +325,11 @@ export declare function groupTouching(tokens: TrackedChangeToken[]): TrackedChan
|
|
|
200
325
|
* The `w:trPr/w:ins` and `w:trPr/w:del` revisions of a table row. `inner` is the revision markup
|
|
201
326
|
* of the row's own cells, which the row's change owns rather than reporting separately.
|
|
202
327
|
*/
|
|
203
|
-
export declare function trackedChangesOfRow(tr: Element<wml.Tr>, owner: Element[], inner?: TrackedChange[], table?: Located<wml.Tbl
|
|
328
|
+
export declare function trackedChangesOfRow(tr: Element<wml.Tr>, owner: Element[], inner?: TrackedChange[], table?: Located<wml.Tbl>, body?: Body): TrackedChange[];
|
|
204
329
|
/** The change a table's property records make, one per table, or undefined when they make none. */
|
|
205
330
|
export declare function tablePropertiesChangeOf(tbl: Element<wml.Tbl>): TrackedChange | undefined;
|
|
206
331
|
/** A cell's own revision, `w:cellIns` or `w:cellDel`, or undefined. */
|
|
207
|
-
export declare function cellChangeOf(tc: Element<wml.Tc>, owner: Element[], tr: wml.Tr): TrackedChange | undefined;
|
|
332
|
+
export declare function cellChangeOf(tc: Element<wml.Tc>, owner: Element[], tr: wml.Tr, body?: Body): TrackedChange | undefined;
|
|
208
333
|
/** The change to the body's last section (`w:body/w:sectPr/w:sectPrChange`), its text the section's paragraphs'. */
|
|
209
334
|
export declare function bodySectionChangeOf(container: {
|
|
210
335
|
content?: Element[];
|
|
@@ -219,3 +344,4 @@ export declare function bodySectionChangeOf(container: {
|
|
|
219
344
|
* the form `holderInsertionAt` lists marks nothing that can be found, and goes either way.
|
|
220
345
|
*/
|
|
221
346
|
export declare function resolveUnlisted(items: Element[], what: 'accept' | 'reject'): void;
|
|
347
|
+
export {};
|