@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.
Files changed (49) hide show
  1. package/README.md +5 -2
  2. package/dist/html/convert.mjs +38 -14
  3. package/dist/model/content/Body.d.mts +13 -1
  4. package/dist/model/content/Body.mjs +90 -19
  5. package/dist/model/content/Comment.mjs +7 -0
  6. package/dist/model/content/InlinePicture.d.mts +71 -7
  7. package/dist/model/content/InlinePicture.mjs +201 -28
  8. package/dist/model/content/List.mjs +10 -1
  9. package/dist/model/content/Paragraph.d.mts +28 -5
  10. package/dist/model/content/Paragraph.mjs +77 -37
  11. package/dist/model/content/Range.d.mts +18 -2
  12. package/dist/model/content/Range.mjs +194 -64
  13. package/dist/model/content/TrackedChange.d.mts +129 -3
  14. package/dist/model/content/TrackedChange.mjs +577 -12
  15. package/dist/model/content/binder.d.mts +2 -1
  16. package/dist/model/content/binder.mjs +48 -1
  17. package/dist/model/content/index.d.mts +2 -2
  18. package/dist/model/content/index.mjs +1 -1
  19. package/dist/model/content/relationships.d.mts +30 -0
  20. package/dist/model/content/relationships.mjs +133 -0
  21. package/dist/model/content/tree.d.mts +52 -2
  22. package/dist/model/content/tree.mjs +48 -3
  23. package/dist/model/customxml/pictures.d.mts +5 -2
  24. package/dist/model/customxml/pictures.mjs +13 -7
  25. package/dist/model/fonts/families.generated.mjs +2 -2
  26. package/dist/model/fonts/registry.mjs +7 -1
  27. package/dist/model/fonts/substitutions.generated.mjs +2 -2
  28. package/dist/model/fonts/themes.generated.mjs +1 -1
  29. package/dist/model/listnumbering/definitions.mjs +18 -0
  30. package/dist/model/properties/PropertyResolver.d.mts +27 -6
  31. package/dist/model/properties/PropertyResolver.mjs +83 -16
  32. package/dist/model/properties/catalogue.d.mts +15 -1
  33. package/dist/model/properties/catalogue.mjs +80 -27
  34. package/dist/model/properties/table.d.mts +16 -2
  35. package/dist/model/properties/table.mjs +13 -3
  36. package/dist/opc/Load.d.mts +9 -2
  37. package/dist/opc/index.d.mts +2 -2
  38. package/dist/opc/index.mjs +2 -2
  39. package/dist/opc/mce/preprocessor.d.mts +62 -6
  40. package/dist/opc/mce/preprocessor.mjs +142 -35
  41. package/dist/opc/mce/understood.d.mts +10 -0
  42. package/dist/opc/mce/understood.mjs +25 -3
  43. package/dist/packages/OpcPackage.d.mts +7 -0
  44. package/dist/packages/OpcPackage.mjs +15 -3
  45. package/dist/packages/WordprocessingMLPackage.d.mts +35 -2
  46. package/dist/packages/WordprocessingMLPackage.mjs +98 -2
  47. package/dist/parts/XmlPart.d.mts +26 -5
  48. package/dist/parts/XmlPart.mjs +67 -18
  49. package/package.json +2 -2
@@ -1,7 +1,8 @@
1
1
  import { deepCopy, marshalString } from '@docx4j/generated-objects-ts';
2
2
  import { Docx4JException } from '../../opc/exceptions.mjs';
3
3
  import { r as textRun, t as textItem, br as breakItem } from '@docx4j/generated-objects-ts/builders/wml';
4
- import { segmentsOf, runsOf, runItemsOf, linkParents, textOf, textOfView, typeNameOf, W_NS, runOf, paragraphOf, revisionKindOf } from './tree.mjs';
4
+ import { segmentsOf, runsOf, runItemsOf, linkParents, textOf, textOfView, typeNameOf, W_NS, runOf, paragraphOf, revisionKindOf, objectInSpan, WHOLE } from './tree.mjs';
5
+ import { relationshipIdsIn, queueRelationshipRemovals } from './relationships.mjs';
5
6
  function withRPr(run, rPr) {
6
7
  if (rPr)
7
8
  run.value.rPr = rPr;
@@ -16,7 +17,7 @@ import { cellOf } from './Table.mjs';
16
17
  import { ContentControl, collectRunControls } from './ContentControl.mjs';
17
18
  import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/wml';
18
19
  import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
19
- import { InlinePicture, addImage, writableWidthEmu } from './InlinePicture.mjs';
20
+ import { InlinePicture, picturesIn, addImage, writableWidthEmu } from './InlinePicture.mjs';
20
21
  import { contentOf } from './ooxml.mjs';
21
22
  import { commentApi } from './comments.mjs';
22
23
  import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr, wrapInsertedRuns, markHolderInserted, pruneParagraphProperties } from './tracking.mjs';
@@ -72,10 +73,11 @@ export class Paragraph {
72
73
  set text(value) {
73
74
  var _a;
74
75
  if (this.changeTracker) {
75
- this.splice(0, this.text.length, value);
76
+ this.splice(0, this.text.length, value, 'before', WHOLE);
76
77
  return;
77
78
  }
78
79
  const rPr = (_a = runsOf(this.p)[0]) === null || _a === void 0 ? void 0 : _a.value.rPr;
80
+ this.releaseRelationships(this.p.content);
79
81
  this.p.content = [withRPr(textRun(value), rPr ? deepCopy(rPr) : undefined)];
80
82
  linkParents(this.p.content, this.p);
81
83
  }
@@ -262,18 +264,19 @@ export class Paragraph {
262
264
  collectRunControls(this.p, this.parentBody, out);
263
265
  return out;
264
266
  }
265
- /** The inline pictures in this paragraph, in order (Office JS Paragraph.inlinePictures). */
267
+ /**
268
+ * The inline pictures in this paragraph, in order (Office JS Paragraph.inlinePictures): each
269
+ * `w:drawing` whose `wp:inline` holds a `pic:pic`, in a run or in the selected branch of a kept
270
+ * `mc:AlternateContent` (`picturesIn`). An inline chart, text box or group is not one (CR-010
271
+ * section 12.6).
272
+ */
266
273
  get inlinePictures() {
267
- var _a, _b;
274
+ var _a;
268
275
  const out = [];
269
276
  for (const run of runsOf(this.p)) {
270
277
  for (const item of ((_a = run.value.content) !== null && _a !== void 0 ? _a : [])) {
271
- if (item.name.localPart !== 'drawing')
272
- continue;
273
- const inline = (_b = item.value.anchorOrInline) === null || _b === void 0 ? void 0 : _b[0];
274
- if (inline && inline.TYPE_NAME === 'org_docx4j_dml_wordprocessingDrawing.Inline') {
275
- out.push(new InlinePicture(item, run, this));
276
- }
278
+ for (const drawing of picturesIn(item))
279
+ out.push(new InlinePicture(drawing, run, this, drawing === item ? undefined : item));
277
280
  }
278
281
  }
279
282
  return out;
@@ -285,7 +288,7 @@ export class Paragraph {
285
288
  return this.splice(0, 0, text);
286
289
  if (location === 'End')
287
290
  return this.splice(length, length, text);
288
- return this.splice(0, length, text);
291
+ return this.splice(0, length, text, 'before', WHOLE);
289
292
  }
290
293
  /** A new paragraph of the text before or after this one, with this paragraph's properties (as Word). */
291
294
  insertParagraph(text, location) {
@@ -332,14 +335,21 @@ export class Paragraph {
332
335
  if (!part)
333
336
  throw new Docx4JException('This body has no part, so an image cannot be related to it');
334
337
  const { run, drawing } = addImage(part, base64, this.parentBody.container, writableWidthEmu(this.parentBody.container), options);
338
+ const tracker = this.changeTracker;
339
+ // under tracking the picture is an insertion, and what it replaces is deleted as insertText's is
340
+ if (location === 'Replace' && tracker)
341
+ this.splice(0, this.text.length, '', 'before', WHOLE);
335
342
  const content = ((_a = (_b = this.p).content) !== null && _a !== void 0 ? _a : (_b.content = []));
336
- if (location === 'Replace')
343
+ if (location === 'Replace' && !tracker) {
344
+ this.releaseRelationships(content);
337
345
  content.length = 0;
346
+ }
347
+ const item = tracker ? tracker.ins([run]) : run;
338
348
  if (location === 'Start')
339
- content.unshift(run);
349
+ content.unshift(item);
340
350
  else
341
- content.push(run);
342
- linkParents(run, this.p);
351
+ content.push(item);
352
+ linkParents(item, this.p);
343
353
  return new InlinePicture(drawing, run, this);
344
354
  }
345
355
  /**
@@ -401,13 +411,17 @@ export class Paragraph {
401
411
  getTrackedChanges() {
402
412
  return trackedChangesOfParagraph(this);
403
413
  }
414
+ /**
415
+ * The paragraph's range: 'Whole' and 'Content' hold its whole content, the pictures at its start and
416
+ * end among them, so deleting it deletes them (CR-002 section 48); 'Start' and 'End' are collapsed.
417
+ */
404
418
  getRange(location = 'Whole') {
405
419
  const length = this.text.length;
406
420
  if (location === 'Start')
407
421
  return new Range(this, 0, 0);
408
422
  if (location === 'End')
409
423
  return new Range(this, length, length);
410
- return new Range(this, 0, length);
424
+ return new Range(this, 0, length, true);
411
425
  }
412
426
  /**
413
427
  * Removes the paragraph from its container. While the package tracks changes, the content
@@ -421,16 +435,18 @@ export class Paragraph {
421
435
  return;
422
436
  }
423
437
  const i = this.index;
424
- if (i >= 0)
425
- this.container.splice(i, 1);
438
+ if (i < 0)
439
+ return;
440
+ this.container.splice(i, 1);
441
+ this.releaseRelationships(this.element);
426
442
  }
427
443
  trackedDelete(tracker) {
428
444
  var _a, _b;
429
445
  if (markDeleted(this.p))
430
446
  throw new Docx4JException('This paragraph is already marked deleted');
431
- const length = this.text.length;
432
- if (length > 0)
433
- this.deleteText(tracker, 0, length);
447
+ // the whole content, its pictures too: before, a picture's run, having no text, stayed out of the
448
+ // w:del, and accepting joined it into the next paragraph (CR-002 section 48)
449
+ this.deleteText(tracker, 0, this.text.length, WHOLE);
434
450
  const rPr = (_a = this.p.pPr) === null || _a === void 0 ? void 0 : _a.rPr;
435
451
  const ownMark = (rPr === null || rPr === void 0 ? void 0 : rPr.ins) !== undefined && rPr.ins.author === tracker.author;
436
452
  if (ownMark && ((_b = this.p.content) !== null && _b !== void 0 ? _b : []).length === 0) {
@@ -458,11 +474,22 @@ export class Paragraph {
458
474
  markDeletedInPlace(tracker) {
459
475
  if (markDeleted(this.p))
460
476
  return;
461
- const length = this.text.length;
462
- if (length > 0)
463
- this.deleteText(tracker, 0, length);
477
+ this.deleteText(tracker, 0, this.text.length, WHOLE);
464
478
  tracker.markParagraphDeleted(this.p);
465
479
  }
480
+ /**
481
+ * Queues the relationships that content removed outright referenced (a picture's image, a link's
482
+ * target), to be settled when the package is saved: each goes then if nothing references it, and
483
+ * its part with it when nothing else targets that (CR-002 sections 47 and 48).
484
+ */
485
+ releaseRelationships(removed) {
486
+ const ids = new Set();
487
+ relationshipIdsIn(removed, ids);
488
+ if (ids.size === 0)
489
+ return;
490
+ const pkg = this.parentBody.package_;
491
+ queueRelationshipRemovals(pkg === null || pkg === void 0 ? void 0 : pkg.pendingRelationshipRemovals, this.parentBody.part, ids);
492
+ }
466
493
  /** The paragraph as XML (extension; Office JS getOoxml wraps it in a package). */
467
494
  getXml() {
468
495
  return marshalString(this.element);
@@ -563,7 +590,7 @@ export class Paragraph {
563
590
  ownInsertions.add(seg.run);
564
591
  return { tracker, ownInsertions };
565
592
  }
566
- /** The text segments (w:t, w:tab, ...) with their offsets. */
593
+ /** The text segments (w:t, w:tab, ...) with their offsets; `{ objects: true }` adds the pictures. */
567
594
  segments(options) {
568
595
  return segmentsOf(this.p, options);
569
596
  }
@@ -572,8 +599,12 @@ export class Paragraph {
572
599
  * Runs are edited in place: a w:t loses or gains characters, a tab or break inside the range
573
600
  * is removed, a run left empty is removed. Text is inserted into the w:t at `start`,
574
601
  * else into a new run with the formatting of the nearest run.
602
+ *
603
+ * The pictures in the span go with its text: those strictly inside it, and those at an edge `edges`
604
+ * says the span holds (`WHOLE` for a paragraph's whole content), so a span of no text can still
605
+ * delete a picture (CR-002 section 48).
575
606
  */
576
- splice(start, end, text, prefer = 'before') {
607
+ splice(start, end, text, prefer = 'before', edges) {
577
608
  var _a, _b, _c, _d, _e, _f;
578
609
  var _g;
579
610
  const length = this.text.length;
@@ -582,21 +613,22 @@ export class Paragraph {
582
613
  const tracker = this.changeTracker;
583
614
  if (tracker) {
584
615
  // a replacement is the w:del first and the w:ins after it, as Word writes one
585
- const deletions = end > start ? this.deleteText(tracker, start, end) : [];
616
+ const deletions = end > start || edges ? this.deleteText(tracker, start, end, edges) : [];
586
617
  if (text.length > 0)
587
618
  this.insertTracked(tracker, start, text, deletions[deletions.length - 1], prefer);
588
619
  return new Range(this, start, start + text.length);
589
620
  }
590
621
  let segs = this.segments();
591
622
  let inserted = false;
623
+ const objects = this.segments({ objects: true }).filter((s) => s.object && objectInSpan(s.start, start, end, edges));
592
624
  // A replacement that begins inside a revision is a deletion and then the new text typed there, so
593
625
  // the new text is plain and splits the revision, as typed text is (test/README.md check 21), rather
594
626
  // than joining the revision it replaced part of.
595
627
  if (end > start && text.length > 0 && ((_a = segs.find((s) => s.start <= start && start < s.end)) === null || _a === void 0 ? void 0 : _a.revision)) {
596
- this.splice(start, end, '');
628
+ this.splice(start, end, '', prefer, edges);
597
629
  return this.splice(start, start, text);
598
630
  }
599
- if (end > start) {
631
+ if (end > start || objects.length > 0) {
600
632
  // Replace: the new text takes the place (and formatting) of the first replaced character, as Word does.
601
633
  const first = segs.find((s) => s.start <= start && start < s.end);
602
634
  const toRemove = [];
@@ -619,11 +651,12 @@ export class Paragraph {
619
651
  else
620
652
  toRemove.push(seg);
621
653
  }
622
- for (const seg of toRemove.reverse()) {
654
+ for (const seg of [...toRemove.reverse(), ...objects]) {
623
655
  const i = seg.owner.indexOf(seg.item);
624
656
  if (i >= 0)
625
657
  seg.owner.splice(i, 1);
626
658
  }
659
+ this.releaseRelationships(objects.map((seg) => seg.item));
627
660
  if (inserted && text.length > 0) {
628
661
  this.removeEmptyRuns();
629
662
  return new Range(this, start, start + text.length);
@@ -847,11 +880,13 @@ export class Paragraph {
847
880
  * The text in [start, end) becomes a deletion: the runs holding it are isolated, their `w:t`
848
881
  * turned into `w:delText`, and each run of consecutive ones moved into one `w:del`. Text this
849
882
  * author had inserted is simply removed, as Word does, rather than nested in a `w:del`.
883
+ * The pictures in the span (`splice`'s `edges`) are deleted with the text, each in a run of its own
884
+ * where its run held more, as `InlinePicture.delete` gives it one (CR-002 section 48).
850
885
  * Returns the `w:del` elements it made, in document order.
851
886
  */
852
- deleteText(tracker, start, end) {
887
+ deleteText(tracker, start, end, edges) {
853
888
  var _a, _b, _c, _d, _e;
854
- const targets = this.isolate(start, end);
889
+ const targets = this.isolate(start, end, edges);
855
890
  for (const seg of targets)
856
891
  tracker.assertEditable(seg.revision);
857
892
  const list = [];
@@ -882,7 +917,9 @@ export class Paragraph {
882
917
  const at = owner.indexOf(group[0].run);
883
918
  const runs = owner.splice(at, group.length);
884
919
  if (group[0].own) {
885
- // taking back our own insertion: the runs go, and an emptied w:ins with them
920
+ // taking back our own insertion: the runs go, and an emptied w:ins with them; a picture's
921
+ // image waits for the save, as `InlinePicture.delete` leaves it (section 47)
922
+ this.releaseRelationships(runs);
886
923
  const revision = group[0].revision;
887
924
  if (revision && revision.items.length === 0) {
888
925
  const i = revision.owner.indexOf(revision.element);
@@ -1073,12 +1110,15 @@ export class Paragraph {
1073
1110
  * Splits runs so that every run holding text in [start, end) holds nothing else, and returns
1074
1111
  * the segments inside the span. `splitAt` does the `w:t` boundaries; this also moves the items
1075
1112
  * of a run that straddles a boundary (a tab, a break, a drawing) out into runs of their own.
1113
+ * The pictures in the span (`splice`'s `edges`) are among the segments returned, and a run
1114
+ * holding one holds nothing outside the span either.
1076
1115
  */
1077
- isolate(start, end) {
1116
+ isolate(start, end, edges) {
1078
1117
  this.splitAt(start);
1079
1118
  this.splitAt(end);
1119
+ const inSpan = (s) => s.object ? objectInSpan(s.start, start, end, edges) : s.start >= start && s.end <= end && s.text.length > 0;
1080
1120
  for (let guard = 0; guard < 10000; guard++) {
1081
- const inside = this.segments().filter((s) => s.start >= start && s.end <= end && s.text.length > 0);
1121
+ const inside = this.segments({ objects: true }).filter(inSpan);
1082
1122
  let split = false;
1083
1123
  const seen = new Set();
1084
1124
  for (const seg of inside) {
@@ -1102,7 +1142,7 @@ export class Paragraph {
1102
1142
  if (!split)
1103
1143
  return inside;
1104
1144
  }
1105
- return this.segments().filter((s) => s.start >= start && s.end <= end && s.text.length > 0);
1145
+ return this.segments({ objects: true }).filter(inSpan);
1106
1146
  }
1107
1147
  /** Moves the items before `seg` into a run of their own, in front of `seg`'s run. */
1108
1148
  splitRunBefore(seg) {
@@ -12,12 +12,21 @@ import type { TrackedChange } from './TrackedChange.mjs';
12
12
  * A subset of Office JS `Word.Range`: a span of text within one paragraph, [start, end) in the
13
13
  * paragraph's text. Positions are recomputed from the tree on every use, so a range stays
14
14
  * valid across edits made through it; edits made elsewhere may shift it.
15
+ *
16
+ * A picture has a place in the text and no text of its own, so a picture at the range's start or
17
+ * end offset may be inside it or outside it. `whole` says: a range from `Paragraph.getRange('Whole')`
18
+ * or `'Content'` holds the paragraph's whole content, the pictures at its edges too, and deleting or
19
+ * replacing it deletes them; any other range holds the pictures strictly inside it, so that a search
20
+ * hit replaced does not take the picture beside it (CR-002 section 48).
15
21
  */
16
22
  export declare class Range {
17
23
  readonly paragraph: Paragraph;
18
24
  start: number;
19
25
  end: number;
20
- constructor(paragraph: Paragraph, start: number, end: number);
26
+ readonly whole: boolean;
27
+ constructor(paragraph: Paragraph, start: number, end: number, whole?: boolean);
28
+ /** The edges `Paragraph.splice` is given: both for a whole range. */
29
+ private get edges();
21
30
  get paragraphs(): Paragraph[];
22
31
  get text(): string;
23
32
  set text(value: string);
@@ -52,7 +61,7 @@ export declare class Range {
52
61
  * `insertXml` does.
53
62
  */
54
63
  insertOoxml(ooxml: string, location: 'Before' | 'After' | 'Replace'): Promise<(Paragraph | BlockElement)[]>;
55
- /** Removes the span's text. */
64
+ /** Removes the span's text, and the pictures in it (CR-002 section 48). */
56
65
  delete(): void;
57
66
  search(text: string, options?: SearchOptions): Range[];
58
67
  /**
@@ -106,6 +115,13 @@ export declare class Range {
106
115
  */
107
116
  get hyperlink(): string;
108
117
  set hyperlink(value: string);
118
+ /**
119
+ * Refuses, before anything changes, a link the setter could not make over [start, end): one with an
120
+ * address on a range with no part, over no run, over runs in more than one holder, or around a
121
+ * tracked deletion - each judged as the range would stand once the links it touches were unwrapped,
122
+ * their runs then in the list holding them.
123
+ */
124
+ private refuseUnlinkable;
109
125
  /** The first `w:hyperlink` this range touches, if any. */
110
126
  private hyperlinkHolder;
111
127
  /**