@docx4j/core-ts 0.3.0 → 0.3.2
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/dist/model/content/Body.mjs +40 -19
- package/dist/model/content/Comment.d.mts +6 -0
- package/dist/model/content/Comment.mjs +76 -22
- package/dist/model/content/ContentControl.d.mts +11 -1
- package/dist/model/content/ContentControl.mjs +69 -10
- package/dist/model/content/Paragraph.d.mts +37 -2
- package/dist/model/content/Paragraph.mjs +140 -38
- package/dist/model/content/Range.mjs +49 -5
- package/dist/model/content/Table.mjs +6 -8
- package/dist/model/content/TrackedChange.d.mts +32 -4
- package/dist/model/content/TrackedChange.mjs +271 -34
- package/dist/model/content/index.d.mts +1 -1
- package/dist/model/content/index.mjs +1 -1
- package/dist/model/content/stylesFacade.mjs +9 -3
- package/dist/model/content/tracking.d.mts +65 -5
- package/dist/model/content/tracking.mjs +170 -43
- package/dist/model/customxml/XmlMapping.d.mts +20 -4
- package/dist/model/customxml/XmlMapping.mjs +44 -6
- package/dist/model/customxml/bindings.d.mts +9 -2
- package/dist/model/customxml/bindings.mjs +18 -2
- package/dist/model/customxml/index.d.mts +1 -1
- package/dist/model/customxml/index.mjs +1 -1
- package/dist/model/customxml/insert.d.mts +10 -0
- package/dist/model/customxml/insert.mjs +13 -0
- package/dist/model/fonts/RunFontSelector.d.mts +8 -1
- package/dist/model/fonts/RunFontSelector.mjs +6 -5
- package/dist/model/properties/PropertyResolver.d.mts +110 -10
- package/dist/model/properties/PropertyResolver.mjs +300 -31
- package/dist/model/properties/index.d.mts +3 -0
- package/dist/model/properties/index.mjs +2 -0
- package/dist/model/properties/table.d.mts +155 -0
- package/dist/model/properties/table.mjs +315 -0
- package/dist/model/properties/tableStyleConditions.d.mts +101 -0
- package/dist/model/properties/tableStyleConditions.mjs +296 -0
- package/dist/packages/WordprocessingMLPackage.d.mts +6 -0
- package/dist/packages/WordprocessingMLPackage.mjs +11 -0
- package/dist/parts/wml/commentStyles.d.mts +3 -0
- package/dist/parts/wml/commentStyles.mjs +25 -0
- package/dist/parts/wml/comments.mjs +4 -22
- package/dist/parts/wml/placeholderStyle.d.mts +3 -0
- package/dist/parts/wml/placeholderStyle.mjs +13 -0
- package/package.json +2 -2
|
@@ -15,11 +15,11 @@ import { builtInOf, idOfBuiltIn, styleNameOf, styleIdOf } from './styles.mjs';
|
|
|
15
15
|
import { cellOf } from './Table.mjs';
|
|
16
16
|
import { ContentControl, collectRunControls } from './ContentControl.mjs';
|
|
17
17
|
import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/wml';
|
|
18
|
-
import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
|
|
18
|
+
import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
|
|
19
19
|
import { InlinePicture, addImage, writableWidthEmu } from './InlinePicture.mjs';
|
|
20
20
|
import { contentOf } from './ooxml.mjs';
|
|
21
21
|
import { commentApi } from './comments.mjs';
|
|
22
|
-
import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr } from './tracking.mjs';
|
|
22
|
+
import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr, wrapInsertedRuns, markHolderInserted, pruneParagraphProperties } from './tracking.mjs';
|
|
23
23
|
import { trackedChangesOfParagraph } from './TrackedChange.mjs';
|
|
24
24
|
import { isListItem, listOf, listOrNullObjectOf, listItemOf, listItemOrNullObjectOf, startNewList, attachToList, detachFromList, } from './List.mjs';
|
|
25
25
|
const TWIPS_PER_POINT = 20;
|
|
@@ -125,11 +125,23 @@ export class Paragraph {
|
|
|
125
125
|
* changing it. Needs the package's resolver; see `Body.propertyResolver`.
|
|
126
126
|
*/
|
|
127
127
|
get effectivePPr() {
|
|
128
|
-
return this.parentBody.propertyResolver.getEffectivePPr(this.p.pPr);
|
|
128
|
+
return this.parentBody.propertyResolver.getEffectivePPr(this.p.pPr, this.cellContext);
|
|
129
129
|
}
|
|
130
130
|
/** The paragraph mark's effective run properties (docx4j `getEffectiveParagraphMarkRPr`). */
|
|
131
131
|
get effectiveParagraphMarkRPr() {
|
|
132
|
-
return this.parentBody.propertyResolver.getEffectiveParagraphMarkRPr(this.p.pPr);
|
|
132
|
+
return this.parentBody.propertyResolver.getEffectiveParagraphMarkRPr(this.p.pPr, this.cellContext);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Where this paragraph sits in a table, as far as its formatting goes (docx4j
|
|
136
|
+
* `PropertyResolver.cellContextOf`; CR-007): the table style its table resolves to and the
|
|
137
|
+
* conditional formats it is under. Undefined outside a table, and in a text box, note or comment
|
|
138
|
+
* reached from a cell. Every effective read of the paragraph and of its runs passes it, so a run
|
|
139
|
+
* in a header row the table style makes bold reads bold. Each read walks the table's rows; code
|
|
140
|
+
* resolving every paragraph of a large table should walk with a `TableContextTracker` instead.
|
|
141
|
+
* Needs the package's resolver, as the effective reads do.
|
|
142
|
+
*/
|
|
143
|
+
get cellContext() {
|
|
144
|
+
return this.parentBody.propertyResolver.cellContextOf(this.p);
|
|
133
145
|
}
|
|
134
146
|
/** The `w:pPr` a formatting read works from: the resolved one, or this paragraph's own. */
|
|
135
147
|
readPPr(options) {
|
|
@@ -152,9 +164,29 @@ export class Paragraph {
|
|
|
152
164
|
const val = v === 'Left' ? 'left' : v === 'Centered' ? 'center' : v === 'Right' ? 'right' : v === 'Justified' ? 'both' : undefined;
|
|
153
165
|
if (val === undefined)
|
|
154
166
|
delete pPr.jc;
|
|
167
|
+
else if (this.inheritedAlignment() === v)
|
|
168
|
+
delete pPr.jc;
|
|
155
169
|
else
|
|
156
170
|
pPr.jc = { val };
|
|
157
171
|
this.settleFormatting();
|
|
172
|
+
pruneParagraphProperties(this.p); // no empty w:pPr left (the editor's finding on 0.3.1)
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The alignment the paragraph resolves to with no `w:jc` of its own: its style's, else the
|
|
176
|
+
* defaults'. Word's Ctrl+L on a centred paragraph whose style is left-aligned removes the `w:jc`
|
|
177
|
+
* rather than writing `w:jc="left"`, with tracking on (check 26) and off (the editor's
|
|
178
|
+
* `measure-word2010.docx` and `measure-word15.docx`, 2026-09-30; CR-002 section 38), so a value the
|
|
179
|
+
* paragraph already inherits is not written. Measured for left over a left-aligned style; taken to
|
|
180
|
+
* hold for any value. Undefined without a resolver, where the value is written as asked.
|
|
181
|
+
*/
|
|
182
|
+
inheritedAlignment() {
|
|
183
|
+
var _a;
|
|
184
|
+
const resolver = (_a = this.parentBody.package_) === null || _a === void 0 ? void 0 : _a.propertyResolverOrUndefined;
|
|
185
|
+
if (!resolver)
|
|
186
|
+
return undefined;
|
|
187
|
+
const { jc: _jc, pPrChange: _change, PARENT: _parent, ...rest } = this.p.pPr;
|
|
188
|
+
// in a table cell what is inherited includes the table style's (CR-007)
|
|
189
|
+
return alignmentOf(resolver.getEffectivePPr(rest, resolver.cellContextOf(this.p)), true);
|
|
158
190
|
}
|
|
159
191
|
/** Indents and spacing in points, as Office JS; the effective values (CR-001 Phase B step 2). */
|
|
160
192
|
get leftIndent() { var _a, _b; return ((_b = (_a = this.effectivePPr.ind) === null || _a === void 0 ? void 0 : _a.left) !== null && _b !== void 0 ? _b : 0) / TWIPS_PER_POINT; }
|
|
@@ -204,7 +236,7 @@ export class Paragraph {
|
|
|
204
236
|
const holders = () => runsOf(this.p).map((r) => r.value);
|
|
205
237
|
if ((options === null || options === void 0 ? void 0 : options.direct) === true)
|
|
206
238
|
return new Font(holders, () => this.fontTracking());
|
|
207
|
-
return new Font(holders, () => this.fontTracking(), (rPr) => this.parentBody.propertyResolver.getEffectiveRPr(rPr, this.p.pPr), (rPr) => { var _a; return (_a = runFontSelectorOf(this.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
|
|
239
|
+
return new Font(holders, () => this.fontTracking(), (rPr) => this.parentBody.propertyResolver.getEffectiveRPr(rPr, this.p.pPr, this.cellContext), (rPr) => { var _a; return (_a = runFontSelectorOf(this.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
|
|
208
240
|
}
|
|
209
241
|
/** w14:paraId, the stable address Word gives paragraphs. */
|
|
210
242
|
get paraId() {
|
|
@@ -408,6 +440,13 @@ export class Paragraph {
|
|
|
408
440
|
return;
|
|
409
441
|
}
|
|
410
442
|
tracker.markParagraphDeleted(this.p);
|
|
443
|
+
// Word, deleting a paragraph mark ALONE under tracking, gives the next paragraph this one's
|
|
444
|
+
// properties as a w:pPrChange (CR-002 section 38 item 2, the editor's measure-word15.docx), so
|
|
445
|
+
// that the joined line reads in them. This method deletes the whole paragraph, text and mark - the
|
|
446
|
+
// engine's only mark deletion, a Range being within one paragraph - and what Word writes for that
|
|
447
|
+
// is not measured (test/README.md check 33). 0.3.1 carried the properties here too, which turned
|
|
448
|
+
// an agent's new heading, put in after a paragraph it then deleted, into body text once accepted
|
|
449
|
+
// (the editor's finding on 0.3.1); until the check, nothing is carried.
|
|
411
450
|
}
|
|
412
451
|
/**
|
|
413
452
|
* The paragraph's content and its mark marked deleted, the paragraph element itself kept:
|
|
@@ -479,8 +518,9 @@ export class Paragraph {
|
|
|
479
518
|
if (!same) {
|
|
480
519
|
const rest = (props) => canonical(Object.fromEntries(Object.entries(props).filter(([key]) => !FORMATTING_MEMBERS.has(key))));
|
|
481
520
|
const resolver = (_b = this.parentBody.package_) === null || _b === void 0 ? void 0 : _b.propertyResolverOrUndefined;
|
|
521
|
+
const cell = resolver === null || resolver === void 0 ? void 0 : resolver.cellContextOf(this.p);
|
|
482
522
|
same = resolver !== undefined && rest(current) === rest(before)
|
|
483
|
-
&& canonical(formattingOf(resolver.getEffectivePPr(current), true)) === canonical(formattingOf(resolver.getEffectivePPr(before), true));
|
|
523
|
+
&& canonical(formattingOf(resolver.getEffectivePPr(current, cell), true)) === canonical(formattingOf(resolver.getEffectivePPr(before, cell), true));
|
|
484
524
|
}
|
|
485
525
|
if (same)
|
|
486
526
|
restorePPr(this.p, recorded);
|
|
@@ -533,7 +573,7 @@ export class Paragraph {
|
|
|
533
573
|
* is removed, a run left empty is removed. Text is inserted into the w:t at `start`,
|
|
534
574
|
* else into a new run with the formatting of the nearest run.
|
|
535
575
|
*/
|
|
536
|
-
splice(start, end, text) {
|
|
576
|
+
splice(start, end, text, prefer = 'before') {
|
|
537
577
|
var _a, _b, _c, _d, _e, _f;
|
|
538
578
|
var _g;
|
|
539
579
|
const length = this.text.length;
|
|
@@ -544,7 +584,7 @@ export class Paragraph {
|
|
|
544
584
|
// a replacement is the w:del first and the w:ins after it, as Word writes one
|
|
545
585
|
const deletions = end > start ? this.deleteText(tracker, start, end) : [];
|
|
546
586
|
if (text.length > 0)
|
|
547
|
-
this.insertTracked(tracker, start, text, deletions[deletions.length - 1]);
|
|
587
|
+
this.insertTracked(tracker, start, text, deletions[deletions.length - 1], prefer);
|
|
548
588
|
return new Range(this, start, start + text.length);
|
|
549
589
|
}
|
|
550
590
|
let segs = this.segments();
|
|
@@ -602,8 +642,12 @@ export class Paragraph {
|
|
|
602
642
|
}
|
|
603
643
|
const ending = segs.find((s) => s.editable && s.end === start);
|
|
604
644
|
const starting = segs.find((s) => s.editable && s.start === start);
|
|
605
|
-
|
|
606
|
-
|
|
645
|
+
// `prefer` says which run takes text put exactly between two: the one ending there (typed text,
|
|
646
|
+
// `Paragraph.insertText`) or the one starting there (`Range.insertText('Before')`, which Office
|
|
647
|
+
// JS puts inside the link the range is on: check 32)
|
|
648
|
+
const [near, far] = prefer === 'after' ? [starting, ending] : [ending, starting];
|
|
649
|
+
const target = (_d = (_c = (_b = (containing && !containing.revision ? containing : undefined)) !== null && _b !== void 0 ? _b : (near && !near.revision ? near : undefined)) !== null && _c !== void 0 ? _c : (far && !far.revision ? far : undefined)) !== null && _d !== void 0 ? _d : containing; // a revision that could not be split: extended, as before
|
|
650
|
+
const edge = target ? undefined : near !== null && near !== void 0 ? near : far;
|
|
607
651
|
if (edge === null || edge === void 0 ? void 0 : edge.revision) {
|
|
608
652
|
const run = runOf([textItem(text)], copyRPr(edge.run.rPr));
|
|
609
653
|
this.insertBesideRevision(edge.revision, run, edge === ending ? 'after' : 'before');
|
|
@@ -618,11 +662,11 @@ export class Paragraph {
|
|
|
618
662
|
// no w:t to extend: a new run next to the segment at `start`, with that run's formatting
|
|
619
663
|
const before = [...segs].reverse().find((s) => s.end <= start);
|
|
620
664
|
const after = segs.find((s) => s.start >= start);
|
|
621
|
-
const neighbour = before !== null && before !== void 0 ? before : after;
|
|
665
|
+
const neighbour = prefer === 'after' ? after !== null && after !== void 0 ? after : before : before !== null && before !== void 0 ? before : after;
|
|
622
666
|
const firstRun = (_e = runsOf(this.p)[0]) === null || _e === void 0 ? void 0 : _e.value.rPr;
|
|
623
667
|
const rPr = (neighbour === null || neighbour === void 0 ? void 0 : neighbour.run.rPr) ? deepCopy(neighbour.run.rPr) : firstRun ? deepCopy(firstRun) : undefined;
|
|
624
668
|
const run = runOf([textItem(text)], rPr);
|
|
625
|
-
if (before)
|
|
669
|
+
if (neighbour === before && before)
|
|
626
670
|
before.runOwner.splice(before.runIndex + 1, 0, run);
|
|
627
671
|
else if (after)
|
|
628
672
|
after.runOwner.splice(after.runIndex, 0, run);
|
|
@@ -643,19 +687,33 @@ export class Paragraph {
|
|
|
643
687
|
* run there when the offset falls inside one. PARENT is linked on what is inserted.
|
|
644
688
|
*/
|
|
645
689
|
insertItemsAt(offset, items) {
|
|
646
|
-
var _a, _b, _c;
|
|
647
|
-
var
|
|
690
|
+
var _a, _b, _c, _d;
|
|
691
|
+
var _e;
|
|
648
692
|
if (items.length === 0)
|
|
649
693
|
return;
|
|
694
|
+
const tracker = this.changeTracker;
|
|
695
|
+
if (tracker) {
|
|
696
|
+
// With tracking on, what goes in is an insertion: its runs in w:ins, in a link, a control or a
|
|
697
|
+
// field too, placed as typed text is - beside another's revision, or between its halves, never
|
|
698
|
+
// in it. Before, `insertOoxml` into a paragraph went in untracked (CR-002 section 37).
|
|
699
|
+
wrapInsertedRuns(tracker, items, this.p);
|
|
700
|
+
this.splitAt(offset);
|
|
701
|
+
const segs = this.segments();
|
|
702
|
+
const before = [...segs].reverse().find((s) => s.end <= offset);
|
|
703
|
+
const after = segs.find((s) => s.start >= offset);
|
|
704
|
+
tracker.assertEditable((_a = (before !== null && before !== void 0 ? before : after)) === null || _a === void 0 ? void 0 : _a.revision);
|
|
705
|
+
this.placeInsertion(offset, items, before, after, () => tracker.nextId());
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
650
708
|
this.splitAt(offset);
|
|
651
709
|
const segs = this.segments();
|
|
652
710
|
const after = segs.find((s) => s.start >= offset);
|
|
653
711
|
const before = [...segs].reverse().find((s) => s.end <= offset);
|
|
654
712
|
const neighbour = after !== null && after !== void 0 ? after : before;
|
|
655
|
-
const list = neighbour ? neighbour.runOwner : ((
|
|
713
|
+
const list = neighbour ? neighbour.runOwner : ((_b = (_e = this.p).content) !== null && _b !== void 0 ? _b : (_e.content = []));
|
|
656
714
|
const at = neighbour ? (after ? neighbour.runIndex : neighbour.runIndex + 1) : (offset === 0 ? 0 : list.length);
|
|
657
715
|
list.splice(at, 0, ...items);
|
|
658
|
-
const owner = (
|
|
716
|
+
const owner = (_d = (_c = neighbour === null || neighbour === void 0 ? void 0 : neighbour.run) === null || _c === void 0 ? void 0 : _c.PARENT) !== null && _d !== void 0 ? _d : this.p;
|
|
659
717
|
for (const item of items)
|
|
660
718
|
linkParents(item, owner);
|
|
661
719
|
}
|
|
@@ -754,6 +812,11 @@ export class Paragraph {
|
|
|
754
812
|
const sdt = sdtOf([this.element], { kind: sdtKindFor(kind), id: nextSdtId(controlIdScope(this.parentBody)), form: 'block' });
|
|
755
813
|
this.container.splice(at, 1, sdt);
|
|
756
814
|
linkParents(sdt, (_a = this.p.PARENT) !== null && _a !== void 0 ? _a : this.parentBody.container);
|
|
815
|
+
declareSdtExtensions(this.parentBody, sdt);
|
|
816
|
+
// under tracking the control itself is the insertion, the paragraph what it was (check 30)
|
|
817
|
+
const tracker = this.changeTracker;
|
|
818
|
+
if (tracker)
|
|
819
|
+
markHolderInserted(tracker, sdt, this.container, () => { });
|
|
757
820
|
return new ContentControl(sdt, this.container, this.parentBody);
|
|
758
821
|
}
|
|
759
822
|
removeEmptyRuns() {
|
|
@@ -787,7 +850,7 @@ export class Paragraph {
|
|
|
787
850
|
* Returns the `w:del` elements it made, in document order.
|
|
788
851
|
*/
|
|
789
852
|
deleteText(tracker, start, end) {
|
|
790
|
-
var _a, _b, _c, _d, _e
|
|
853
|
+
var _a, _b, _c, _d, _e;
|
|
791
854
|
const targets = this.isolate(start, end);
|
|
792
855
|
for (const seg of targets)
|
|
793
856
|
tracker.assertEditable(seg.revision);
|
|
@@ -833,7 +896,11 @@ export class Paragraph {
|
|
|
833
896
|
const del = tracker.del(runs);
|
|
834
897
|
owner.splice(at, 0, del);
|
|
835
898
|
linkParents(del, ((_c = (_b = (_a = group[0].revision) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : group[0].run.value.PARENT) !== null && _c !== void 0 ? _c : this.p));
|
|
836
|
-
|
|
899
|
+
const revision = group[0].revision;
|
|
900
|
+
const anchor = { owner, element: del, parent: ((_e = (_d = revision === null || revision === void 0 ? void 0 : revision.value) !== null && _d !== void 0 ? _d : del.value.PARENT) !== null && _e !== void 0 ? _e : this.p) };
|
|
901
|
+
if (revision && revision.items === owner)
|
|
902
|
+
anchor.revision = revision;
|
|
903
|
+
made.unshift(anchor);
|
|
837
904
|
}
|
|
838
905
|
return made;
|
|
839
906
|
}
|
|
@@ -846,17 +913,35 @@ export class Paragraph {
|
|
|
846
913
|
* then does what it did before.
|
|
847
914
|
*/
|
|
848
915
|
insertBetweenHalves(at, item, nextId) {
|
|
849
|
-
var _a;
|
|
850
916
|
this.splitAt(at);
|
|
851
917
|
const after = this.segments().find((s) => s.start >= at && s.revision);
|
|
852
918
|
const holder = after === null || after === void 0 ? void 0 : after.revision;
|
|
853
919
|
const index = holder && after ? holder.items.indexOf(after.runOwner[after.runIndex]) : -1;
|
|
854
920
|
if (!holder || index <= 0)
|
|
855
921
|
return false;
|
|
922
|
+
return this.splitHolderAround(holder, index, item, nextId);
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Moves a revision's items from `index` on into a copy of it - its attributes, a new `w:id` - and
|
|
926
|
+
* puts `item` between the two; with nothing from `index` on, `item` just goes after the revision.
|
|
927
|
+
* False, with nothing changed, where the revision's items cannot be told apart from its attributes.
|
|
928
|
+
* Internal: `Range.insertContentControl` uses it to put a new control between the halves of the
|
|
929
|
+
* insertion it wraps part of (check 30).
|
|
930
|
+
*/
|
|
931
|
+
splitHolderAround(holder, index, item, nextId) {
|
|
932
|
+
var _a;
|
|
856
933
|
const value = holder.value;
|
|
857
934
|
const itemsKey = Object.keys(value).find((key) => value[key] === holder.items);
|
|
858
935
|
if (!itemsKey)
|
|
859
936
|
return false;
|
|
937
|
+
const parent = (_a = holder.value.PARENT) !== null && _a !== void 0 ? _a : this.p;
|
|
938
|
+
const at = holder.owner.indexOf(holder.element) + 1;
|
|
939
|
+
const items = Array.isArray(item) ? item : [item];
|
|
940
|
+
if (index >= holder.items.length) {
|
|
941
|
+
holder.owner.splice(at, 0, ...items);
|
|
942
|
+
linkParents(items, parent);
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
860
945
|
const copy = {};
|
|
861
946
|
for (const [key, v] of Object.entries(value))
|
|
862
947
|
if (key !== itemsKey)
|
|
@@ -864,11 +949,9 @@ export class Paragraph {
|
|
|
864
949
|
copy.id = nextId();
|
|
865
950
|
copy[itemsKey] = holder.items.splice(index);
|
|
866
951
|
const second = { name: holder.element.name, value: copy };
|
|
867
|
-
|
|
868
|
-
const at2 = holder.owner.indexOf(holder.element) + 1;
|
|
869
|
-
holder.owner.splice(at2, 0, item, second);
|
|
952
|
+
holder.owner.splice(at, 0, ...items, second);
|
|
870
953
|
linkParents(copy[itemsKey], copy);
|
|
871
|
-
linkParents([
|
|
954
|
+
linkParents([...items, second], parent);
|
|
872
955
|
return true;
|
|
873
956
|
}
|
|
874
957
|
/**
|
|
@@ -884,14 +967,16 @@ export class Paragraph {
|
|
|
884
967
|
while (index > 0 && ((_b = (_a = holder.owner[index - 1]) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.localPart) === marker)
|
|
885
968
|
index--;
|
|
886
969
|
}
|
|
887
|
-
|
|
888
|
-
|
|
970
|
+
const items = Array.isArray(item) ? item : [item];
|
|
971
|
+
holder.owner.splice(index, 0, ...items);
|
|
972
|
+
linkParents(items, (_c = holder.value.PARENT) !== null && _c !== void 0 ? _c : this.p);
|
|
889
973
|
}
|
|
890
974
|
/**
|
|
891
975
|
* A fresh annotation id for markup this paragraph writes itself: the package's counter when there
|
|
892
976
|
* is a package that keeps one, since a document has one id space; else one above every id in the
|
|
893
977
|
* story.
|
|
894
978
|
*/
|
|
979
|
+
/** @internal `Comment` uses it to split an insertion at a comment's end. */
|
|
895
980
|
nextAnnotationId() {
|
|
896
981
|
const pkg = this.parentBody.package_;
|
|
897
982
|
if (pkg && typeof pkg.markupRoots === 'function' && 'annotationIdFloor' in pkg) {
|
|
@@ -907,12 +992,18 @@ export class Paragraph {
|
|
|
907
992
|
* `w:ins` is extended rather than nested in another one, as Word does. `anchor` is the `w:del`
|
|
908
993
|
* of a replacement, which the insertion must follow.
|
|
909
994
|
*/
|
|
910
|
-
insertTracked(tracker, at, text, anchor) {
|
|
911
|
-
var _a, _b, _c
|
|
912
|
-
var _f;
|
|
995
|
+
insertTracked(tracker, at, text, anchor, prefer = 'before') {
|
|
996
|
+
var _a, _b, _c;
|
|
913
997
|
if (anchor) {
|
|
914
998
|
const rPr = copyRPr((_a = runsOf(anchor.element.value, { view: 'original' })[0]) === null || _a === void 0 ? void 0 : _a.value.rPr);
|
|
915
999
|
const ins = tracker.ins([runOf([textItem(text)], rPr)]);
|
|
1000
|
+
// A replacement inside another's revision - an insertion, moved text - leaves its w:del in
|
|
1001
|
+
// that revision; the new text is not put in it too, but the revision split after the deletion
|
|
1002
|
+
// and the text between the halves, as for anything typed in it (check 21; the editor's finding
|
|
1003
|
+
// on 0.3.0, CR-002 section 37).
|
|
1004
|
+
const holder = anchor.revision;
|
|
1005
|
+
if (holder && this.splitHolderAround(holder, holder.items.indexOf(anchor.element) + 1, ins, () => tracker.nextId()))
|
|
1006
|
+
return;
|
|
916
1007
|
anchor.owner.splice(anchor.owner.indexOf(anchor.element) + 1, 0, ins);
|
|
917
1008
|
linkParents(ins, anchor.parent);
|
|
918
1009
|
return;
|
|
@@ -921,7 +1012,8 @@ export class Paragraph {
|
|
|
921
1012
|
const segs = this.segments();
|
|
922
1013
|
const before = [...segs].reverse().find((s) => s.end <= at);
|
|
923
1014
|
const after = segs.find((s) => s.start >= at);
|
|
924
|
-
|
|
1015
|
+
const sides = [[before, 'after'], [after, 'before']];
|
|
1016
|
+
for (const [seg, side] of prefer === 'after' ? [...sides].reverse() : sides) {
|
|
925
1017
|
if (!seg || !tracker.ownInsertion(seg.revision))
|
|
926
1018
|
continue;
|
|
927
1019
|
if (seg.editable) {
|
|
@@ -935,36 +1027,46 @@ export class Paragraph {
|
|
|
935
1027
|
}
|
|
936
1028
|
return;
|
|
937
1029
|
}
|
|
938
|
-
const neighbour = before !== null && before !== void 0 ? before : after;
|
|
1030
|
+
const neighbour = prefer === 'after' ? after !== null && after !== void 0 ? after : before : before !== null && before !== void 0 ? before : after;
|
|
939
1031
|
tracker.assertEditable(neighbour === null || neighbour === void 0 ? void 0 : neighbour.revision);
|
|
940
1032
|
const rPr = copyRPr((_b = neighbour === null || neighbour === void 0 ? void 0 : neighbour.run.rPr) !== null && _b !== void 0 ? _b : (_c = runsOf(this.p)[0]) === null || _c === void 0 ? void 0 : _c.value.rPr);
|
|
941
1033
|
const ins = tracker.ins([runOf([textItem(text)], rPr)]);
|
|
1034
|
+
this.placeInsertion(at, [ins], before, after, () => tracker.nextId(), prefer);
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* An insertion put at `at`, `before` and `after` being the segments either side of it once the runs
|
|
1038
|
+
* are split there: never inside another's revision.
|
|
1039
|
+
*/
|
|
1040
|
+
placeInsertion(at, items, before, after, nextId, prefer = 'before') {
|
|
1041
|
+
var _a, _b;
|
|
1042
|
+
var _c;
|
|
942
1043
|
// Typed inside another's revision - a w:ins, a w:moveTo - the revision is split and the typist's
|
|
943
1044
|
// w:ins goes between its halves, as Word writes it (test/README.md check 20 case 01, check 21's
|
|
944
1045
|
// first run); before, it went to the revision's edge, putting the text in the wrong place.
|
|
945
1046
|
if ((before === null || before === void 0 ? void 0 : before.revision) && (after === null || after === void 0 ? void 0 : after.revision) && before.revision.element === after.revision.element
|
|
946
|
-
&& this.insertBetweenHalves(at,
|
|
1047
|
+
&& this.insertBetweenHalves(at, items, nextId)) {
|
|
947
1048
|
return;
|
|
948
1049
|
}
|
|
949
1050
|
// at the start of a revision, beside it - and outside a move's range that opens there (case 03)
|
|
950
1051
|
if (!before && (after === null || after === void 0 ? void 0 : after.revision)) {
|
|
951
|
-
this.insertBesideRevision(after.revision,
|
|
1052
|
+
this.insertBesideRevision(after.revision, items, 'before');
|
|
952
1053
|
return;
|
|
953
1054
|
}
|
|
1055
|
+
const neighbour = prefer === 'after' ? after !== null && after !== void 0 ? after : before : before !== null && before !== void 0 ? before : after;
|
|
954
1056
|
if (neighbour) {
|
|
955
1057
|
const owner = neighbour.revision ? neighbour.revision.owner : neighbour.runOwner;
|
|
956
1058
|
const item = neighbour.revision ? neighbour.revision.element : neighbour.runOwner[neighbour.runIndex];
|
|
957
1059
|
const parent = (neighbour.revision ? neighbour.revision.value : neighbour.run);
|
|
958
|
-
owner.splice(owner.indexOf(item) + (neighbour === before ? 1 : 0), 0,
|
|
959
|
-
linkParents(
|
|
1060
|
+
owner.splice(owner.indexOf(item) + (neighbour === before ? 1 : 0), 0, ...items);
|
|
1061
|
+
linkParents(items, (_a = parent.PARENT) !== null && _a !== void 0 ? _a : this.p);
|
|
960
1062
|
}
|
|
961
1063
|
else {
|
|
962
|
-
const content = ((
|
|
1064
|
+
const content = ((_b = (_c = this.p).content) !== null && _b !== void 0 ? _b : (_c.content = []));
|
|
963
1065
|
if (at === 0)
|
|
964
|
-
content.unshift(
|
|
1066
|
+
content.unshift(...items);
|
|
965
1067
|
else
|
|
966
|
-
content.push(
|
|
967
|
-
linkParents(
|
|
1068
|
+
content.push(...items);
|
|
1069
|
+
linkParents(items, this.p);
|
|
968
1070
|
}
|
|
969
1071
|
}
|
|
970
1072
|
/**
|
|
@@ -6,8 +6,9 @@ import { ContentControl } from './ContentControl.mjs';
|
|
|
6
6
|
import { sdt as sdtOf, nextSdtId, applyRunOptions, t as textElement } from '@docx4j/generated-objects-ts/builders/wml';
|
|
7
7
|
import { hyperlink as hyperlinkOf } from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
|
|
8
8
|
import { Namespaces } from '../../parts/Namespaces.mjs';
|
|
9
|
-
import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
|
|
9
|
+
import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
|
|
10
10
|
import { contentOf } from './ooxml.mjs';
|
|
11
|
+
import { markHolderInserted } from './tracking.mjs';
|
|
11
12
|
import { searchPattern, findAll, matchesOf, expandReplacement } from './search.mjs';
|
|
12
13
|
import { commentApi } from './comments.mjs';
|
|
13
14
|
/**
|
|
@@ -73,7 +74,7 @@ export class Range {
|
|
|
73
74
|
};
|
|
74
75
|
if ((options === null || options === void 0 ? void 0 : options.direct) === true)
|
|
75
76
|
return new Font(holders, () => this.paragraph.fontTracking());
|
|
76
|
-
return new Font(holders, () => this.paragraph.fontTracking(), (rPr) => this.paragraph.parentBody.propertyResolver.getEffectiveRPr(rPr, this.paragraph.p.pPr), (rPr) => { var _a; return (_a = runFontSelectorOf(this.paragraph.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
|
|
77
|
+
return new Font(holders, () => this.paragraph.fontTracking(), (rPr) => this.paragraph.parentBody.propertyResolver.getEffectiveRPr(rPr, this.paragraph.p.pPr, this.paragraph.cellContext), (rPr) => { var _a; return (_a = runFontSelectorOf(this.paragraph.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
|
|
77
78
|
}
|
|
78
79
|
/** The runs the span covers (a run partly inside counts). */
|
|
79
80
|
get runs() {
|
|
@@ -93,8 +94,10 @@ export class Range {
|
|
|
93
94
|
let r;
|
|
94
95
|
if (location === 'Replace')
|
|
95
96
|
r = this.paragraph.splice(this.start, this.end, text);
|
|
97
|
+
// text put before the range joins the run the range starts in - inside the link the range is on,
|
|
98
|
+
// as Office JS (check 32: insertText('Before') on a link's text went into the link, as 'After' did)
|
|
96
99
|
else if (location === 'Before' || location === 'Start')
|
|
97
|
-
r = this.paragraph.splice(this.start, this.start, text);
|
|
100
|
+
r = this.paragraph.splice(this.start, this.start, text, 'after');
|
|
98
101
|
else
|
|
99
102
|
r = this.paragraph.splice(this.end, this.end, text);
|
|
100
103
|
// this range keeps covering the same text: a replacement resizes it, text before it shifts it, text after it leaves it
|
|
@@ -206,12 +209,13 @@ export class Range {
|
|
|
206
209
|
* span gets an empty control at its position. Returns the control.
|
|
207
210
|
*/
|
|
208
211
|
insertContentControl(kind) {
|
|
209
|
-
var _a;
|
|
212
|
+
var _a, _b;
|
|
210
213
|
const paragraph = this.paragraph;
|
|
211
214
|
const body = paragraph.parentBody;
|
|
212
215
|
// The control is built empty first: `sdt` refuses a kind that cannot be run-level (a repeating
|
|
213
216
|
// section), and that refusal must come before any run is split.
|
|
214
217
|
const sdt = sdtOf([], { kind: sdtKindFor(kind), id: nextSdtId(controlIdScope(body)), form: 'run' });
|
|
218
|
+
declareSdtExtensions(body, sdt);
|
|
215
219
|
if (this.start === this.end) {
|
|
216
220
|
paragraph.insertItemsAt(this.start, [sdt]);
|
|
217
221
|
return new ContentControl(sdt, containerOf(sdt, paragraph), body);
|
|
@@ -232,9 +236,49 @@ export class Range {
|
|
|
232
236
|
items.push(element);
|
|
233
237
|
}
|
|
234
238
|
const at = owner.indexOf(items[0]);
|
|
239
|
+
const tracker = paragraph.changeTracker;
|
|
240
|
+
const revision = segments[0].revision;
|
|
241
|
+
if (tracker && revision) {
|
|
242
|
+
// The runs are in a revision: the control goes between its halves, not in it, and the runs go
|
|
243
|
+
// into the control in a revision of the same kind and attributes with a new id - Word's form for
|
|
244
|
+
// inserted text wrapped in a new control (check 30, inserted-text-wrapped: the w:ins inside the
|
|
245
|
+
// control, the control's own markers outside it).
|
|
246
|
+
const index = revision.items.indexOf(items[0]);
|
|
247
|
+
revision.items.splice(index, items.length);
|
|
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);
|
|
258
|
+
sdt.value.sdtContent.content = [wrapped];
|
|
259
|
+
linkParents(wrapped, sdt.value.sdtContent);
|
|
260
|
+
const holderOwner = revision.owner;
|
|
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())) {
|
|
271
|
+
throw new Docx4JException('This range is in a revision that cannot be split around a content control');
|
|
272
|
+
}
|
|
273
|
+
markHolderInserted(tracker, sdt, holderOwner, () => { });
|
|
274
|
+
return new ContentControl(sdt, holderOwner, body);
|
|
275
|
+
}
|
|
235
276
|
sdt.value.sdtContent.content = items;
|
|
236
277
|
owner.splice(at, items.length, sdt);
|
|
237
|
-
linkParents(sdt, (
|
|
278
|
+
linkParents(sdt, (_b = segments[0].run.PARENT) !== null && _b !== void 0 ? _b : paragraph.p);
|
|
279
|
+
// under tracking the control itself is the insertion, the runs in it what they were (check 30)
|
|
280
|
+
if (tracker)
|
|
281
|
+
markHolderInserted(tracker, sdt, owner, () => { });
|
|
238
282
|
return new ContentControl(sdt, owner, body);
|
|
239
283
|
}
|
|
240
284
|
/**
|
|
@@ -3,7 +3,7 @@ import { tr as rowOf } from '@docx4j/generated-objects-ts/builders/wml';
|
|
|
3
3
|
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
|
-
import {
|
|
6
|
+
import { trackInsertedBlocks } from './tracking.mjs';
|
|
7
7
|
/** The default table width in twips (A4 with 2.54 cm margins), as the objects package's `tbl` builder uses. */
|
|
8
8
|
const DEFAULT_WIDTH = 9026;
|
|
9
9
|
const TWIPS_PER_POINT = 20;
|
|
@@ -418,12 +418,10 @@ function markRowsInserted(tracker, rows) {
|
|
|
418
418
|
if (!tracker)
|
|
419
419
|
return;
|
|
420
420
|
for (const row of rows) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
}
|
|
421
|
+
if (!((_a = row.value.trPr) === null || _a === void 0 ? void 0 : _a.ins))
|
|
422
|
+
tracker.markRowInserted(row.value);
|
|
423
|
+
// nested tables and block controls in a new row's cells too (CR-002 section 37)
|
|
424
|
+
for (const cell of cellsOf(row.value))
|
|
425
|
+
trackInsertedBlocks(tracker, childrenOf(cell.element.value));
|
|
428
426
|
}
|
|
429
427
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
|
|
2
|
-
import { type Element, type RevisionKind } from './tree.mjs';
|
|
2
|
+
import { type Element, type Located, type RevisionKind } from './tree.mjs';
|
|
3
3
|
import { Range } from './Range.mjs';
|
|
4
4
|
import type { Paragraph } from './Paragraph.mjs';
|
|
5
5
|
/** Office JS `Word.ChangeTrackingState` (`Unknown` is not produced here). */
|
|
@@ -35,7 +35,8 @@ export type TrackedChangeTarget =
|
|
|
35
35
|
* `w:trPr/w:ins` or `w:trPr/w:del`: a table row. `inner` is the revision markup the row's own
|
|
36
36
|
* cells carry (the `w:ins` or `w:del` around their runs and on their paragraph marks), which
|
|
37
37
|
* `Body.getTrackedChanges` reports through the row rather than separately, as Office JS does:
|
|
38
|
-
* one change per row. Accepting or rejecting the row applies them too.
|
|
38
|
+
* one change per row. Accepting or rejecting the row applies them too. `table` is the table the
|
|
39
|
+
* row is in, which goes with its last row.
|
|
39
40
|
*/
|
|
40
41
|
| {
|
|
41
42
|
kind: 'row';
|
|
@@ -44,6 +45,7 @@ export type TrackedChangeTarget =
|
|
|
44
45
|
owner: Element[];
|
|
45
46
|
value: wml.CTTrackChange;
|
|
46
47
|
inner: TrackedChange[];
|
|
48
|
+
table?: Located<wml.Tbl>;
|
|
47
49
|
}
|
|
48
50
|
/**
|
|
49
51
|
* Touching insertions, deletions or formatting changes by one author - `w:ins` or `w:del` around
|
|
@@ -92,6 +94,24 @@ export type TrackedChangeTarget =
|
|
|
92
94
|
sectPr: wml.SectPr;
|
|
93
95
|
value: wml.CTSectPrChange;
|
|
94
96
|
text: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A content control or custom XML element that is itself an insertion: `w:customXmlInsRangeStart`
|
|
100
|
+
* and `w:customXmlInsRangeEnd` around it, the end first inside its content, and a second pair the
|
|
101
|
+
* other way round at its end - Word's form for a control put in under tracking (check 30, CR-002
|
|
102
|
+
* section 37 item 7), which groups with the insertions around and inside it. `holder` is the
|
|
103
|
+
* `w:sdt` or `w:customXml`, `owner` the list holding it, `outer` the pair in that list and `inner`
|
|
104
|
+
* the pair in its content. Rejecting takes the element away and leaves its content where it
|
|
105
|
+
* stood; accepting takes the markers away. Its text is its content's, which the pieces inside
|
|
106
|
+
* report.
|
|
107
|
+
*/
|
|
108
|
+
| {
|
|
109
|
+
kind: 'holder';
|
|
110
|
+
holder: Element;
|
|
111
|
+
owner: Element[];
|
|
112
|
+
outer: [Element, Element];
|
|
113
|
+
inner: [Element, Element];
|
|
114
|
+
value: wml.CTTrackChange;
|
|
95
115
|
};
|
|
96
116
|
/**
|
|
97
117
|
* A subset of Office JS `Word.TrackedChange`: `type`, `author`, `date`, `text`, `accept()`,
|
|
@@ -134,6 +154,13 @@ export declare class TrackedChange {
|
|
|
134
154
|
reject(): void;
|
|
135
155
|
private requireParagraph;
|
|
136
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* The insertion of a content control or custom XML element, when `items[i]` is a
|
|
159
|
+
* `w:customXmlInsRangeStart` and what follows is Word's form for one (check 30): the element, whose
|
|
160
|
+
* content starts with that marker's `w:customXmlInsRangeEnd` and ends with a second start, and that
|
|
161
|
+
* one's end right after the element. Anything else is not one (a marker on its own is left alone).
|
|
162
|
+
*/
|
|
163
|
+
export declare function holderInsertionAt(items: Element[], i: number, paragraph: Paragraph | undefined): TrackedChange | undefined;
|
|
137
164
|
/**
|
|
138
165
|
* docx4j AcceptTrackedChanges: the paragraph takes the next one's content and properties, so the
|
|
139
166
|
* mark that survives is the next one's. Called when a deleted mark is accepted and when an
|
|
@@ -173,7 +200,7 @@ export declare function groupTouching(tokens: TrackedChangeToken[]): TrackedChan
|
|
|
173
200
|
* The `w:trPr/w:ins` and `w:trPr/w:del` revisions of a table row. `inner` is the revision markup
|
|
174
201
|
* of the row's own cells, which the row's change owns rather than reporting separately.
|
|
175
202
|
*/
|
|
176
|
-
export declare function trackedChangesOfRow(tr: Element<wml.Tr>, owner: Element[], inner?: TrackedChange[]): TrackedChange[];
|
|
203
|
+
export declare function trackedChangesOfRow(tr: Element<wml.Tr>, owner: Element[], inner?: TrackedChange[], table?: Located<wml.Tbl>): TrackedChange[];
|
|
177
204
|
/** The change a table's property records make, one per table, or undefined when they make none. */
|
|
178
205
|
export declare function tablePropertiesChangeOf(tbl: Element<wml.Tbl>): TrackedChange | undefined;
|
|
179
206
|
/** A cell's own revision, `w:cellIns` or `w:cellDel`, or undefined. */
|
|
@@ -188,6 +215,7 @@ export declare function bodySectionChangeOf(container: {
|
|
|
188
215
|
* cannot list (check 27: `getTrackedChanges` throws); a `w:numberingChange`, which Word no longer
|
|
189
216
|
* writes and drops when one is put in, and whose record is the old number's text, so that a reject
|
|
190
217
|
* too can only drop it; and a table's records that change nothing. A merge rejected gets the merge it
|
|
191
|
-
* recorded back (docx4j's reading; unmeasured).
|
|
218
|
+
* recorded back (docx4j's reading; unmeasured). A `w:customXmlInsRangeStart` or `End` left without
|
|
219
|
+
* the form `holderInsertionAt` lists marks nothing that can be found, and goes either way.
|
|
192
220
|
*/
|
|
193
221
|
export declare function resolveUnlisted(items: Element[], what: 'accept' | 'reject'): void;
|