@docx4j/core-ts 0.2.2 → 0.3.1

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.
@@ -34,6 +34,18 @@ export interface CommentOptions {
34
34
  /** Checked unused across the comments part and the story's markers; a clash throws. */
35
35
  id?: number | undefined;
36
36
  }
37
+ /**
38
+ * A comment's entry for markers the caller placed (`addCommentEntry`, CR-002 section 36): the caller's
39
+ * `w:id`, which its markers already name, the content, and the author, initials and date as in
40
+ * `CommentOptions`.
41
+ */
42
+ export interface CommentEntry extends Omit<CommentOptions, 'id'> {
43
+ /** The `w:id` the caller's markers name; refused when the comments part already has a comment by it. */
44
+ id: number;
45
+ content: CommentContent;
46
+ /** The comment this one answers: its `w15:commentEx` then names the parent's (`w15:paraIdParent`). */
47
+ parent?: Comment | undefined;
48
+ }
37
49
  export interface Author {
38
50
  name: string;
39
51
  /** Word's `w:initials`; derived from the name when not given. */
@@ -67,6 +79,13 @@ export interface CommentParts {
67
79
  commentsIds: w16cid.CTCommentsIds | undefined;
68
80
  /** `w16cex:commentsExtensible`, when the document has one. */
69
81
  extensible: CommentsExtensible | undefined;
82
+ /**
83
+ * Each comment's `w16cex:dateUtc` by the `w14:paraId` the w16cid part names it by (upper case),
84
+ * read from the two parts' XML when the comments are loaded - reading does not unmarshal them, so
85
+ * a document whose comments are only read keeps those parts byte for byte - and kept in step as
86
+ * comments are added and removed (CR-002 section 29: a date as Word means it).
87
+ */
88
+ readonly datesUtc: Map<string, Date>;
70
89
  /** The package's author identity. */
71
90
  readonly author: Author;
72
91
  /** The w15 extended part, created (with its relationship and content type) when absent. */
@@ -132,9 +151,7 @@ export declare function paraIdTaken(parts: CommentParts, container: object, id:
132
151
  export declare function commentExOf(parts: CommentParts, paraId: string | undefined): w15.CTCommentEx | undefined;
133
152
  /** Word writes `w15:done="0"` and `"1"`; `w:ST_OnOff` also allows true/false/on/off. */
134
153
  export declare function isDone(value: string | undefined): boolean;
135
- /** A Jsonix calendar (what `w:date` unmarshals to) as a Date; undefined when there is none. */
136
- export declare function calendarToDate(calendar: XmlCalendar | undefined): Date | undefined;
137
- /** A Date as the UTC calendar Word writes (`2026-09-15T09:00:00Z`). */
154
+ /** A Date as a UTC calendar (`2026-09-15T09:00:00Z`): what Word writes in `w16cex:dateUtc`. */
138
155
  export declare function dateToCalendar(date: Date): XmlCalendar;
139
156
  /** `2026-09-15T09:00:00Z`, the form Word writes in w16cex. */
140
157
  export declare function toUtcString(date: Date): string;
@@ -226,15 +226,7 @@ export function isDone(value) {
226
226
  return value === '1' || value === 'true' || value === 'on';
227
227
  }
228
228
  // --- dates ---
229
- /** A Jsonix calendar (what `w:date` unmarshals to) as a Date; undefined when there is none. */
230
- export function calendarToDate(calendar) {
231
- if (!calendar || calendar.year === undefined || Number.isNaN(calendar.year))
232
- return undefined;
233
- const num = (v, fallback = 0) => (v === undefined || Number.isNaN(v) ? fallback : v);
234
- const ms = Date.UTC(calendar.year, num(calendar.month, 1) - 1, num(calendar.day, 1), num(calendar.hour), num(calendar.minute), num(calendar.second), Math.round(num(calendar.fractionalSecond) * 1000));
235
- return new Date(ms - num(calendar.timezone) * 60000);
236
- }
237
- /** A Date as the UTC calendar Word writes (`2026-09-15T09:00:00Z`). */
229
+ /** A Date as a UTC calendar (`2026-09-15T09:00:00Z`): what Word writes in `w16cex:dateUtc`. */
238
230
  export function dateToCalendar(date) {
239
231
  return {
240
232
  year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate(),
@@ -2,9 +2,9 @@ export { Body, type Address, type BlockElement, type Outline, type OutlineParagr
2
2
  export { Paragraph, type Alignment } from './Paragraph.mjs';
3
3
  export { Range } from './Range.mjs';
4
4
  export { BUILT_IN_STYLES, builtInOf, idOfBuiltIn, displayNameOf, styleNameOf, styleIdOf, type BuiltInStyle } from './styles.mjs';
5
- export { Comment } from './Comment.mjs';
5
+ export { Comment, addCommentEntry } from './Comment.mjs';
6
6
  export { List, ListItem, ListItemNotFoundError, NumberingFacade, listsOf, listLabelsOf, numberingEmulatorOf, levelOf, type ListLevelType, type ListNumbering, type ListBullet, type StartListOptions, type ListLabel, } from './List.mjs';
7
- export { type Author, type CommentContent, type CommentOptions, type CommentParts, type CommentPartsAccess, type CommentsExtensible, type CommentMarker, COMMENT_TEXT_STYLE, COMMENT_REFERENCE_STYLE, markersOf, markersOfParagraph, commentIdsOf } from './comments.mjs';
7
+ export { type Author, type CommentContent, type CommentOptions, type CommentEntry, type CommentParts, type CommentPartsAccess, type CommentsExtensible, type CommentMarker, COMMENT_TEXT_STYLE, COMMENT_REFERENCE_STYLE, markersOf, markersOfParagraph, commentIdsOf } from './comments.mjs';
8
8
  export { Font, type UnderlineType, type RPrHolder, type FontTracking } from './Font.mjs';
9
9
  export { Table, TableRow, TableCell, cellOf } from './Table.mjs';
10
10
  export { InlinePicture, imageInfoOf, naturalSizeEmu, addImage, writableWidthEmu, type ImageFormat, type ImageInfo, type InlinePictureOptions, type NewPicture } from './InlinePicture.mjs';
@@ -13,7 +13,7 @@ export { contentOf, isFlatOpc, rewriteRelationshipIds, type OoxmlOptions } from
13
13
  export { type SearchOptions, searchPattern, findAll, matchesOf, expandReplacement } from './search.mjs';
14
14
  export { segmentsOf, runsOf, runItemsOf, childrenOf, rowsOf, cellsOf, BLOCK_LEVEL_TYPES, SDT_TYPES, runOf, paragraphOf, textOfView, revisionKindOf, type TextSegment, type Located, type TextView, type TextViewOptions, type RevisionKind, type RevisionHolder } from './tree.mjs';
15
15
  export { TrackedChange, trackedChangesOfParagraph, trackedChangesOfRow, joinWithNext, type TrackedChangeType, type TrackedChangeTarget } from './TrackedChange.mjs';
16
- export { ChangeTracker, trackerOf, calendarOf, dateOf, copyRPr, markDeleted, markInserted, highestAnnotationId, MARKUP_TYPES, toDeletedText, toRestoredText, restoreRPr, restorePPr, trackInsertedParagraph, trackInsertedTable, wrapNewRuns, paraRPrOf, rowPrOf, pruneParagraphProperties, type ChangeTrackingMode, type TrackingHost, type TrackingScope, } from './tracking.mjs';
16
+ export { ChangeTracker, trackerOf, calendarOf, dateOf, wordCalendarOf, revisionDateOf, copyRPr, markDeleted, markInserted, highestAnnotationId, MARKUP_TYPES, toDeletedText, toRestoredText, restoreRPr, restorePPr, trackInsertedParagraph, trackInsertedTable, trackInsertedBlocks, wrapNewRuns, wrapInsertedRuns, paraRPrOf, rowPrOf, pruneParagraphProperties, type ChangeTrackingMode, type TrackingHost, type TrackingScope, type TrackedPart, } from './tracking.mjs';
17
17
  export { wml, wmlOne, p, r, t, br, tab, tbl, tr, tc, sdt, sdtPr, nextSdtId, sdtProperty, sdtKindOf, inlinePicture, rPrToElements, rPrFromElements, textOf, walk, walkAll, find, linkParents, applyRunOptions, readRunOptions, UNDERLINE_TO_WML, isElement, typeNameOf, W_NS, type Element, type Wrapper, type WmlOptions, type Raw, type Interpolated, type RunOptions, type RunFormatting, type ParagraphOptions, type TableOptions, type CellOptions, type SdtKind, type SdtForm, type SdtOptions } from '@docx4j/generated-objects-ts/builders/wml';
18
18
  export { StylesFacade, type StylePackageLike } from './stylesFacade.mjs';
19
19
  export { bindSnapshot, boundsOf, pathOf, type Bound, type BoundSnapshot } from './binder.mjs';
@@ -3,7 +3,7 @@ export { Body } from './Body.mjs';
3
3
  export { Paragraph } from './Paragraph.mjs';
4
4
  export { Range } from './Range.mjs';
5
5
  export { BUILT_IN_STYLES, builtInOf, idOfBuiltIn, displayNameOf, styleNameOf, styleIdOf } from './styles.mjs';
6
- export { Comment } from './Comment.mjs';
6
+ export { Comment, addCommentEntry } from './Comment.mjs';
7
7
  // lists (CR-002 phase H)
8
8
  export { List, ListItem, ListItemNotFoundError, NumberingFacade, listsOf, listLabelsOf, numberingEmulatorOf, levelOf, } from './List.mjs';
9
9
  export { COMMENT_TEXT_STYLE, COMMENT_REFERENCE_STYLE, markersOf, markersOfParagraph, commentIdsOf } from './comments.mjs';
@@ -16,7 +16,7 @@ export { searchPattern, findAll, matchesOf, expandReplacement } from './search.m
16
16
  export { segmentsOf, runsOf, runItemsOf, childrenOf, rowsOf, cellsOf, BLOCK_LEVEL_TYPES, SDT_TYPES, runOf, paragraphOf, textOfView, revisionKindOf } from './tree.mjs';
17
17
  // change tracking (CR-002 phase F)
18
18
  export { TrackedChange, trackedChangesOfParagraph, trackedChangesOfRow, joinWithNext } from './TrackedChange.mjs';
19
- export { ChangeTracker, trackerOf, calendarOf, dateOf, copyRPr, markDeleted, markInserted, highestAnnotationId, MARKUP_TYPES, toDeletedText, toRestoredText, restoreRPr, restorePPr, trackInsertedParagraph, trackInsertedTable, wrapNewRuns, paraRPrOf, rowPrOf, pruneParagraphProperties, } from './tracking.mjs';
19
+ export { ChangeTracker, trackerOf, calendarOf, dateOf, wordCalendarOf, revisionDateOf, copyRPr, markDeleted, markInserted, highestAnnotationId, MARKUP_TYPES, toDeletedText, toRestoredText, restoreRPr, restorePPr, trackInsertedParagraph, trackInsertedTable, trackInsertedBlocks, wrapNewRuns, wrapInsertedRuns, paraRPrOf, rowPrOf, pruneParagraphProperties, } from './tracking.mjs';
20
20
  // The objects package's builders, re-exported so that one import serves the content API and the tree it works on.
21
21
  export { wml, wmlOne, p, r, t, br, tab, tbl, tr, tc, sdt, sdtPr, nextSdtId, sdtProperty, sdtKindOf, inlinePicture, rPrToElements, rPrFromElements, textOf, walk, walkAll, find, linkParents, applyRunOptions, readRunOptions, UNDERLINE_TO_WML, isElement, typeNameOf, W_NS } from '@docx4j/generated-objects-ts/builders/wml';
22
22
  export { StylesFacade } from './stylesFacade.mjs';
@@ -3,6 +3,7 @@ import { linkParents } from './tree.mjs';
3
3
  import { parseXml } from '../../xml/dom.mjs';
4
4
  import { DEFAULT_STYLES_XML } from '../../parts/wml/defaultStyles.mjs';
5
5
  import { SPLICEABLE_STYLES_XML } from '../../parts/wml/spliceableStyles.mjs';
6
+ import { commentStyles } from '../../parts/wml/commentStyles.mjs';
6
7
  import { Docx4JException } from '../../opc/exceptions.mjs';
7
8
  /**
8
9
  * Where a definition is looked up, unmarshalled once and never handed out (a copy is spliced in):
@@ -15,7 +16,9 @@ function styleSources() {
15
16
  return (sources !== null && sources !== void 0 ? sources : (sources = (async () => {
16
17
  const read = async (xml) => { var _a; return (_a = (await unmarshalNode(parseXml(xml))).value.style) !== null && _a !== void 0 ? _a : []; };
17
18
  const defaults = await read(DEFAULT_STYLES_XML);
18
- return { defaults, all: [...defaults, ...(await read(SPLICEABLE_STYLES_XML))] };
19
+ // and Word's two comment styles, which the comment parts add when they are made, for a caller
20
+ // that makes the comment itself (CR-002 section 37)
21
+ return { defaults, all: [...defaults, ...(await read(SPLICEABLE_STYLES_XML)), ...commentStyles()] };
19
22
  })()));
20
23
  }
21
24
  /**
@@ -99,7 +102,7 @@ function closureOf(wanted, present, source) {
99
102
  seen.add(id);
100
103
  const template = source.find((s) => s.styleId === id);
101
104
  if (template === undefined) {
102
- throw new Docx4JException(`No definition to splice for the style ${id}: neither docx4j's default styles nor the nine it comments out carry one. Define it yourself on the styles part.`);
105
+ throw new Docx4JException(`No definition to splice for the style ${id}: neither docx4j's default styles, the nine it comments out nor the two comment styles carry one. Define it yourself on the styles part.`);
103
106
  }
104
107
  // what it is based on and linked to must be there too, and before it reads better in the part
105
108
  for (const dependency of [(_a = template.basedOn) === null || _a === void 0 ? void 0 : _a.val, (_b = template.link) === null || _b === void 0 ? void 0 : _b.val]) {
@@ -0,0 +1,30 @@
1
+ import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ /** A table's property records, as the listing needs them; undefined when it has none that change anything. */
3
+ export interface TableRecords {
4
+ /** The record whose author and date the change reports: the table's, else the first row's or cell's. */
5
+ value: wml.CTTrackChange;
6
+ }
7
+ export declare function tableRecordsOf(tbl: wml.Tbl): TableRecords | undefined;
8
+ /** True when the table has any property record at all, changing anything or not. */
9
+ export declare function hasTableRecords(tbl: wml.Tbl): boolean;
10
+ /** A row's text as Office JS reports it: its cells with a tab between them, then `\r\n` (checks 27 and 28). */
11
+ export declare function rowText(tr: wml.Tr): string;
12
+ /** The text of a table's change: the table's where its own properties or grid changed, else the changed rows'. */
13
+ export declare function tableChangeText(tbl: wml.Tbl): string;
14
+ /** Accepting keeps the properties as they are and drops every record; a row's `w:trPr` left empty goes. */
15
+ export declare function acceptTable(tbl: wml.Tbl): void;
16
+ /**
17
+ * Rejecting puts the recorded table back: its properties, its grid, and each row and cell in the
18
+ * change as recorded - a row in the change without a `w:trPrChange` cleared, having had no properties
19
+ * (check 28, from a saved file) - then collapses a merged grid (check 27: a width change records the
20
+ * old cells over a grid of the old and new column edges together). Rows outside the change are left.
21
+ */
22
+ export declare function rejectTable(tbl: wml.Tbl): void;
23
+ /** A row's `w:trPr` with nothing left in it goes, as Word writes none. */
24
+ export declare function pruneRow(tr: wml.Tr): void;
25
+ /**
26
+ * Grid columns that no row keeps apart become one: the edges any row's cells (or its `w:gridBefore`
27
+ * and `w:gridAfter`) fall on are kept, the others merged, and each cell's `w:gridSpan` and each row's
28
+ * skipped columns counted again over the kept columns.
29
+ */
30
+ export declare function collapseGrid(tbl: wml.Tbl): void;
@@ -0,0 +1,207 @@
1
+ import { deepCopy } from '@docx4j/generated-objects-ts';
2
+ import { typeNameOf, rowsOf, cellsOf, childrenOf, linkParents, textOfView } from './tree.mjs';
3
+ import { canonical } from './tracking.mjs';
4
+ /** The marker members of a `w:tcPr` that are revisions of their own, kept whatever a reject restores. */
5
+ const CELL_MARKERS = ['cellIns', 'cellDel', 'cellMerge'];
6
+ /** The members of a `w:trPr` that are the row's own revisions. */
7
+ const ROW_MARKERS = ['ins', 'del'];
8
+ /** A value without some of its members, for comparing what a record holds with what stands. */
9
+ function without(value, keys) {
10
+ const out = {};
11
+ for (const [key, v] of Object.entries(value !== null && value !== void 0 ? value : {}))
12
+ if (!keys.includes(key))
13
+ out[key] = v;
14
+ return out;
15
+ }
16
+ function differs(current, recorded) {
17
+ return canonical(current !== null && current !== void 0 ? current : {}) !== canonical(recorded !== null && recorded !== void 0 ? recorded : {});
18
+ }
19
+ /** The rows a change takes in: a row with a `w:trPrChange` or a `w:tblPrExChange`, or a cell with a `w:tcPrChange`. */
20
+ function inChange(tr) {
21
+ var _a, _b;
22
+ return ((_a = tr.trPr) === null || _a === void 0 ? void 0 : _a.trPrChange) !== undefined || ((_b = tr.tblPrEx) === null || _b === void 0 ? void 0 : _b.tblPrExChange) !== undefined
23
+ || cellsOf(tr).some((cell) => { var _a; return ((_a = cell.element.value.tcPr) === null || _a === void 0 ? void 0 : _a.tcPrChange) !== undefined; });
24
+ }
25
+ /**
26
+ * True when a row in the change is not as its records have it: its properties (a row without a
27
+ * `w:trPrChange` had none before, check 28), a cell's, or its `w:tblPrEx`.
28
+ */
29
+ function rowDiffers(tr) {
30
+ var _a, _b, _c;
31
+ if (differs(without(tr.trPr, [...ROW_MARKERS, 'trPrChange']), (_b = (_a = tr.trPr) === null || _a === void 0 ? void 0 : _a.trPrChange) === null || _b === void 0 ? void 0 : _b.trPr))
32
+ return true;
33
+ if (((_c = tr.tblPrEx) === null || _c === void 0 ? void 0 : _c.tblPrExChange) && differs(without(tr.tblPrEx, ['tblPrExChange']), tr.tblPrEx.tblPrExChange.tblPrEx))
34
+ return true;
35
+ return cellsOf(tr).some(({ element }) => {
36
+ const tcPr = element.value.tcPr;
37
+ return (tcPr === null || tcPr === void 0 ? void 0 : tcPr.tcPrChange) !== undefined && differs(without(tcPr, [...CELL_MARKERS, 'tcPrChange']), tcPr.tcPrChange.tcPr);
38
+ });
39
+ }
40
+ /** True when the table's own properties or its grid are not as recorded. */
41
+ function tableDiffers(tbl) {
42
+ var _a, _b;
43
+ const change = (_a = tbl.tblPr) === null || _a === void 0 ? void 0 : _a.tblPrChange;
44
+ if (change && differs(without(tbl.tblPr, ['tblPrChange']), change.tblPr))
45
+ return true;
46
+ const grid = (_b = tbl.tblGrid) === null || _b === void 0 ? void 0 : _b.tblGridChange;
47
+ return grid !== undefined && differs(without(tbl.tblGrid, ['tblGridChange']), grid.tblGrid);
48
+ }
49
+ export function tableRecordsOf(tbl) {
50
+ var _a, _b, _c, _d;
51
+ const rows = rowsOf(tbl).map((row) => row.element.value);
52
+ const changed = rows.filter(inChange);
53
+ if (!((_a = tbl.tblPr) === null || _a === void 0 ? void 0 : _a.tblPrChange) && !((_b = tbl.tblGrid) === null || _b === void 0 ? void 0 : _b.tblGridChange) && changed.length === 0)
54
+ return undefined;
55
+ // a set of records that holds no difference is no change (checks 24 and 26's rule)
56
+ if (!tableDiffers(tbl) && !changed.some(rowDiffers))
57
+ return undefined;
58
+ const value = (_d = (_c = tbl.tblPr) === null || _c === void 0 ? void 0 : _c.tblPrChange) !== null && _d !== void 0 ? _d : changed.map((tr) => { var _a, _b, _c, _d; return (_d = (_b = (_a = tr.trPr) === null || _a === void 0 ? void 0 : _a.trPrChange) !== null && _b !== void 0 ? _b : (_c = tr.tblPrEx) === null || _c === void 0 ? void 0 : _c.tblPrExChange) !== null && _d !== void 0 ? _d : cellsOf(tr).map((c) => { var _a; return (_a = c.element.value.tcPr) === null || _a === void 0 ? void 0 : _a.tcPrChange; }).find(Boolean); }).find(Boolean);
59
+ return value ? { value } : undefined;
60
+ }
61
+ /** True when the table has any property record at all, changing anything or not. */
62
+ export function hasTableRecords(tbl) {
63
+ var _a, _b;
64
+ return ((_a = tbl.tblPr) === null || _a === void 0 ? void 0 : _a.tblPrChange) !== undefined || ((_b = tbl.tblGrid) === null || _b === void 0 ? void 0 : _b.tblGridChange) !== undefined || rowsOf(tbl).some((row) => inChange(row.element.value));
65
+ }
66
+ /** A cell's text as Office JS reports it in a table's change: its paragraphs, a `\r` between them. */
67
+ function cellText(tc) {
68
+ var _a;
69
+ return ((_a = childrenOf(tc)) !== null && _a !== void 0 ? _a : []).filter((el) => typeNameOf(el) === 'org_docx4j_wml.P').map((el) => textOfView(el.value, { view: 'accepted' })).join('\r');
70
+ }
71
+ /** A row's text as Office JS reports it: its cells with a tab between them, then `\r\n` (checks 27 and 28). */
72
+ export function rowText(tr) {
73
+ return cellsOf(tr).map((cell) => cellText(cell.element.value)).join('\t') + '\r\n';
74
+ }
75
+ /** The text of a table's change: the table's where its own properties or grid changed, else the changed rows'. */
76
+ export function tableChangeText(tbl) {
77
+ const rows = rowsOf(tbl).map((row) => row.element.value);
78
+ const shown = tableDiffers(tbl) ? rows : rows.filter((tr) => inChange(tr) && rowDiffers(tr));
79
+ return shown.map(rowText).join('');
80
+ }
81
+ /** Accepting keeps the properties as they are and drops every record; a row's `w:trPr` left empty goes. */
82
+ export function acceptTable(tbl) {
83
+ var _a, _b, _c, _d, _e;
84
+ (_a = tbl.tblPr) === null || _a === void 0 ? true : delete _a.tblPrChange;
85
+ (_b = tbl.tblGrid) === null || _b === void 0 ? true : delete _b.tblGridChange;
86
+ for (const { element } of rowsOf(tbl)) {
87
+ const tr = element.value;
88
+ (_c = tr.trPr) === null || _c === void 0 ? true : delete _c.trPrChange;
89
+ (_d = tr.tblPrEx) === null || _d === void 0 ? true : delete _d.tblPrExChange;
90
+ pruneRow(tr);
91
+ for (const cell of cellsOf(tr))
92
+ (_e = cell.element.value.tcPr) === null || _e === void 0 ? true : delete _e.tcPrChange;
93
+ }
94
+ }
95
+ /**
96
+ * Rejecting puts the recorded table back: its properties, its grid, and each row and cell in the
97
+ * change as recorded - a row in the change without a `w:trPrChange` cleared, having had no properties
98
+ * (check 28, from a saved file) - then collapses a merged grid (check 27: a width change records the
99
+ * old cells over a grid of the old and new column edges together). Rows outside the change are left.
100
+ */
101
+ export function rejectTable(tbl) {
102
+ var _a, _b, _c, _d;
103
+ const tableChange = (_a = tbl.tblPr) === null || _a === void 0 ? void 0 : _a.tblPrChange;
104
+ if (tableChange && tbl.tblPr)
105
+ replaceProperties(tbl.tblPr, tableChange.tblPr, [], tbl);
106
+ const gridChange = (_b = tbl.tblGrid) === null || _b === void 0 ? void 0 : _b.tblGridChange;
107
+ if (gridChange && tbl.tblGrid)
108
+ replaceProperties(tbl.tblGrid, gridChange.tblGrid, [], tbl);
109
+ for (const { element } of rowsOf(tbl)) {
110
+ const tr = element.value;
111
+ if (!inChange(tr))
112
+ continue;
113
+ const exChange = (_c = tr.tblPrEx) === null || _c === void 0 ? void 0 : _c.tblPrExChange;
114
+ if (exChange && tr.tblPrEx)
115
+ replaceProperties(tr.tblPrEx, exChange.tblPrEx, [], tr);
116
+ if (tr.trPr)
117
+ replaceProperties(tr.trPr, (_d = tr.trPr.trPrChange) === null || _d === void 0 ? void 0 : _d.trPr, ROW_MARKERS, tr);
118
+ pruneRow(tr);
119
+ for (const cell of cellsOf(tr)) {
120
+ const tcPr = cell.element.value.tcPr;
121
+ if (tcPr === null || tcPr === void 0 ? void 0 : tcPr.tcPrChange)
122
+ replaceProperties(tcPr, tcPr.tcPrChange.tcPr, CELL_MARKERS, cell.element.value);
123
+ }
124
+ }
125
+ if (gridChange)
126
+ collapseGrid(tbl);
127
+ }
128
+ /**
129
+ * Replaces a properties object's members with a record's, keeping the object (and so its type, which
130
+ * the marshaller writes by) and the members named in `keep`.
131
+ */
132
+ function replaceProperties(target, recorded, keep, parent) {
133
+ const t = target;
134
+ for (const key of Object.keys(t))
135
+ if (key !== 'TYPE_NAME' && key !== 'PARENT' && !keep.includes(key))
136
+ delete t[key];
137
+ for (const [key, value] of Object.entries(recorded !== null && recorded !== void 0 ? recorded : {})) {
138
+ if (key === 'TYPE_NAME' || key === 'PARENT' || keep.includes(key))
139
+ continue;
140
+ t[key] = deepCopy(value);
141
+ }
142
+ linkParents(target, parent);
143
+ }
144
+ /** A row's `w:trPr` with nothing left in it goes, as Word writes none. */
145
+ export function pruneRow(tr) {
146
+ var _a;
147
+ const trPr = tr.trPr;
148
+ if (trPr && !trPr.ins && !trPr.del && !trPr.trPrChange && !((_a = trPr.cnfStyleOrDivIdOrGridBefore) === null || _a === void 0 ? void 0 : _a.length))
149
+ delete tr.trPr;
150
+ }
151
+ function rowMember(tr, name) {
152
+ var _a, _b;
153
+ return ((_b = (_a = tr.trPr) === null || _a === void 0 ? void 0 : _a.cnfStyleOrDivIdOrGridBefore) !== null && _b !== void 0 ? _b : []).find((el) => { var _a; return ((_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) === name; });
154
+ }
155
+ function span(tc) {
156
+ var _a, _b, _c;
157
+ return Number((_c = (_b = (_a = tc.tcPr) === null || _a === void 0 ? void 0 : _a.gridSpan) === null || _b === void 0 ? void 0 : _b.val) !== null && _c !== void 0 ? _c : 1) || 1;
158
+ }
159
+ /**
160
+ * Grid columns that no row keeps apart become one: the edges any row's cells (or its `w:gridBefore`
161
+ * and `w:gridAfter`) fall on are kept, the others merged, and each cell's `w:gridSpan` and each row's
162
+ * skipped columns counted again over the kept columns.
163
+ */
164
+ export function collapseGrid(tbl) {
165
+ var _a, _b, _c, _d, _e, _f;
166
+ const cols = (_b = (_a = tbl.tblGrid) === null || _a === void 0 ? void 0 : _a.gridCol) !== null && _b !== void 0 ? _b : [];
167
+ if (cols.length < 2)
168
+ return;
169
+ const rows = rowsOf(tbl).map((row) => row.element.value);
170
+ const edges = new Set([0, cols.length]);
171
+ for (const tr of rows) {
172
+ let at = Number((_d = (_c = rowMember(tr, 'gridBefore')) === null || _c === void 0 ? void 0 : _c.value.val) !== null && _d !== void 0 ? _d : 0);
173
+ edges.add(at);
174
+ for (const cell of cellsOf(tr)) {
175
+ at += span(cell.element.value);
176
+ edges.add(at);
177
+ }
178
+ }
179
+ const kept = [...edges].filter((edge) => edge >= 0 && edge <= cols.length).sort((a, b) => a - b);
180
+ if (kept.length - 1 === cols.length)
181
+ return;
182
+ const index = (edge) => kept.indexOf(edge);
183
+ const widths = kept.slice(1).map((edge, i) => cols.slice(kept[i], edge).reduce((sum, col) => { var _a; return sum + Number((_a = col.w) !== null && _a !== void 0 ? _a : 0); }, 0));
184
+ tbl.tblGrid.gridCol = widths.map((w, i) => ({ ...cols[Math.min(kept[i], cols.length - 1)], w }));
185
+ linkParents(tbl.tblGrid, tbl);
186
+ for (const tr of rows) {
187
+ const before = rowMember(tr, 'gridBefore');
188
+ let at = Number((_e = before === null || before === void 0 ? void 0 : before.value.val) !== null && _e !== void 0 ? _e : 0);
189
+ if (before)
190
+ before.value.val = index(at);
191
+ for (const cell of cellsOf(tr)) {
192
+ const tc = cell.element.value;
193
+ const next = at + span(tc);
194
+ const count = index(next) - index(at);
195
+ if (tc.tcPr) {
196
+ if (count > 1)
197
+ tc.tcPr.gridSpan = { ...((_f = tc.tcPr.gridSpan) !== null && _f !== void 0 ? _f : {}), val: count };
198
+ else
199
+ delete tc.tcPr.gridSpan;
200
+ }
201
+ at = next;
202
+ }
203
+ const after = rowMember(tr, 'gridAfter');
204
+ if (after)
205
+ after.value.val = kept.length - 1 - index(at);
206
+ }
207
+ }
@@ -59,16 +59,39 @@ export interface TrackingScope {
59
59
  /** The tracker of a package whose mode is not `Off`; `undefined` when it is (duck typed, no import cycle). */
60
60
  export declare function trackerOf(pkg: unknown): ChangeTracker | undefined;
61
61
  /**
62
- * Now, with the seconds and milliseconds dropped, which is what Word stamps a revision with.
63
- * Not cosmetic: Word groups adjacent revisions by author **and** timestamp, so a per-second stamp
64
- * makes one change of every keystroke run and an editor's typing unreviewable (CR-002 section 28.2).
65
- * A caller that sets `trackedChangeDate` gets exactly the date it asked for, truncated or not.
62
+ * Now, with the seconds and milliseconds dropped, which is what Word stamps a revision with
63
+ * (CR-002 section 28.2). A caller that sets `trackedChangeDate` gets exactly the date it asked for,
64
+ * truncated or not. (Section 28.2 gave a second reason, that Word groups adjacent revisions by their
65
+ * timestamp; check 23 found otherwise for Office JS's list, where an hour apart is still one change,
66
+ * CR-002 section 29.)
66
67
  */
67
68
  export declare function nowToTheMinute(): Date;
68
- /** An `XmlCalendar` in UTC, as Word writes `w:date` (an xsd:dateTime ending in `Z`). */
69
+ /** An `XmlCalendar` in UTC, ending in `Z`: `w16du:dateUtc`, or any xsd:dateTime meant as UTC. */
69
70
  export declare function calendarOf(date: Date): wml.XmlCalendar;
70
- /** The `Date` of a `w:date`, or undefined when there is none. */
71
+ /** The `Date` of an xsd:dateTime read as it says - a missing offset as UTC - or undefined when there is none. */
71
72
  export declare function dateOf(cal: wml.XmlCalendar | undefined): Date | undefined;
73
+ /**
74
+ * A revision's `w:date` as Word writes it: the local wall-clock time, with a `Z` that does not mean
75
+ * UTC (CR-002 section 29, check 23: Office JS read `01:00:00Z` as 15:00 UTC the day before, at
76
+ * UTC+10; Word's own files carry the true UTC beside it in `w16du:dateUtc`). "Local" is this
77
+ * process's time zone, as it is the add-in's for Office JS.
78
+ */
79
+ export declare function wordCalendarOf(date: Date): wml.XmlCalendar;
80
+ /**
81
+ * A revision's date as Word means it: `w16du:dateUtc` when there is one, which is UTC; else
82
+ * `w:date` read as local wall-clock time, its `Z` notwithstanding, as Office JS reads it (check 23).
83
+ * A `w:date` with a non-zero offset, which Word does not write, is read as it says.
84
+ */
85
+ export declare function revisionDateOf(value: {
86
+ date?: wml.XmlCalendar;
87
+ dateUtc?: wml.XmlCalendar;
88
+ }): Date | undefined;
89
+ /** Properties compared as values: keys sorted, `PARENT`, `TYPE_NAME` and absent values left out. */
90
+ export declare function canonical(value: unknown): string;
91
+ /** The part a tracker writes into, as far as the tracker needs it: its tree, once unmarshalled. */
92
+ export interface TrackedPart {
93
+ readonly contents?: unknown;
94
+ }
72
95
  /**
73
96
  * The per-package writer of revision markup. Created by the package while
74
97
  * `changeTrackingMode` is not `Off`; every content-API mutation asks its `Body` for one and,
@@ -80,7 +103,17 @@ export declare function dateOf(cal: wml.XmlCalendar | undefined): Date | undefin
80
103
  export declare class ChangeTracker {
81
104
  readonly host: TrackingHost;
82
105
  readonly mode: ChangeTrackingMode;
83
- constructor(host: TrackingHost, mode: ChangeTrackingMode);
106
+ /** The part this tracker writes into, when known: it decides whether `w16du:dateUtc` is written. */
107
+ readonly part?: TrackedPart | undefined;
108
+ private readonly perPart;
109
+ constructor(host: TrackingHost, mode: ChangeTrackingMode,
110
+ /** The part this tracker writes into, when known: it decides whether `w16du:dateUtc` is written. */
111
+ part?: TrackedPart | undefined);
112
+ /**
113
+ * This tracker for revisions written into one part (a `Body` asks for its own). The id counter is
114
+ * the host's, so every tracker over a document draws ids from one space.
115
+ */
116
+ forPart(part: TrackedPart | undefined): ChangeTracker;
84
117
  /** The `w:author` a new revision carries: the package author's name. */
85
118
  get author(): string;
86
119
  /**
@@ -94,11 +127,18 @@ export declare class ChangeTracker {
94
127
  * consulted on every call, so a `seedAnnotationIds()` awaited after the first id still counts.
95
128
  */
96
129
  nextId(): number;
97
- /** The `w:id`, `w:author` and `w:date` a new revision carries. */
130
+ /**
131
+ * The `w:id`, `w:author` and `w:date` a new revision carries, `w:date` in Word's local wall-clock
132
+ * convention (`wordCalendarOf`), and `w16du:dateUtc`, the true UTC, as Word writes them both. The
133
+ * part's root is made to list `w16du` in `mc:Ignorable` first (`declareDateUtc`): a consumer that
134
+ * does not know `w16du` must reject it unless it is declared ignorable. A tracker bound to no part -
135
+ * the package's own - writes `w:date` alone (CR-002 section 29).
136
+ */
98
137
  markup(): {
99
138
  id: number;
100
139
  author: string;
101
140
  date: wml.XmlCalendar;
141
+ dateUtc?: wml.XmlCalendar;
102
142
  };
103
143
  /** A `w:ins` / `w:del` attribute set: the id, this package's author and the date. */
104
144
  trackChange(): wml.CTTrackChange;
@@ -112,6 +152,16 @@ export declare class ChangeTracker {
112
152
  assertEditable(revision: RevisionHolder | undefined): void;
113
153
  /** Records the run's properties as they are now in `w:rPrChange`, once (a later write keeps the first original). */
114
154
  recordRPrChange(rPr: wml.RPr): void;
155
+ /**
156
+ * After a tracked formatting write: the run's `w:rPrChange` goes when its properties are back to
157
+ * what it recorded, since Word keeps no formatting change that changes nothing, whoever changed the
158
+ * formatting back (`test/README.md` check 24: bold then unbold by the same author, and by another; a
159
+ * character style applied then cleared; CR-002 section 29). An `w:rPr` then left empty goes too, as
160
+ * Word writes none.
161
+ */
162
+ settleRPrChange(run: {
163
+ rPr?: wml.RPr;
164
+ }): void;
115
165
  /** Records the paragraph's properties as they are now in `w:pPrChange`, once. */
116
166
  recordPPrChange(pPr: wml.PPr): void;
117
167
  /** `w:pPr/w:rPr/w:ins`: the paragraph mark is an insertion. */
@@ -124,16 +174,62 @@ export declare class ChangeTracker {
124
174
  markRowDeleted(tr: wml.Tr): void;
125
175
  }
126
176
  /**
127
- * A whole paragraph inserted: the mark is marked inserted and every run of it goes into a
128
- * `w:ins`. Word, splitting a paragraph, marks the *first* paragraph's mark instead and leaves
129
- * the new one the original mark; marking the new paragraph's own mark is the same document once
177
+ * A whole paragraph inserted: the mark is marked inserted (unless it already is) and every run of it
178
+ * goes into a `w:ins` - the runs in its hyperlinks, inline content controls, fields and smart tags as
179
+ * well (`wrapNewRuns`). Word, splitting a paragraph, marks the *first* paragraph's mark instead and
180
+ * leaves the new one the original mark; marking the new paragraph's own mark is the same document once
130
181
  * accepted or rejected and keeps the edit to the element that was added (CR-002 section 13).
131
182
  */
132
183
  export declare function trackInsertedParagraph(tracker: ChangeTracker, p: wml.P): void;
133
- /** Every top-level run of the paragraph that is not already in a revision, wrapped in one `w:ins` per group. */
184
+ /**
185
+ * Every run of the paragraph not already in a revision, wrapped in one `w:ins` per group of touching
186
+ * runs - at the top level and inside the run-level holders, where Word's form decides (`test/README.md`
187
+ * check 30, CR-002 section 37 item 7): in a hyperlink or a simple field the `w:ins` goes inside, since
188
+ * neither can be in one (`CT_RunTrackChange` holds `EG_ContentRunContent` only); a smart tag goes
189
+ * *into* the `w:ins` with the runs beside it, its own runs plain; an inline content control or custom
190
+ * XML element keeps the `w:ins` inside and is itself marked inserted (`markHolderInserted`). Before,
191
+ * only the top level was wrapped, so that rejecting an inserted paragraph left a link's text behind
192
+ * (the editor's finding on 0.3.0, CR-002 section 37 item 4).
193
+ */
134
194
  export declare function wrapNewRuns(tracker: ChangeTracker, p: wml.P): void;
135
- /** A whole table inserted: every row is marked inserted and every paragraph in it is an insertion. */
195
+ /**
196
+ * `wrapNewRuns` over run-level items that are about to go into a paragraph (`Paragraph.insertItemsAt`):
197
+ * the runs among them, and those in their holders, wrapped in `w:ins`; a content control or custom
198
+ * XML element among them marked inserted. `parent` is what the items' array belongs to.
199
+ */
200
+ export declare function wrapInsertedRuns(tracker: ChangeTracker, items: Element[] | undefined, parent: object): void;
201
+ /** A whole table inserted: every row is marked inserted and everything in its cells is an insertion (`trackInsertedBlocks`). */
136
202
  export declare function trackInsertedTable(tracker: ChangeTracker, tbl: wml.Tbl): void;
203
+ /**
204
+ * Block content inserted, whatever it holds: a paragraph (`trackInsertedParagraph`), a table
205
+ * (`trackInsertedTable`: rows inside row-level content controls, nested tables in its cells), and a
206
+ * block-level content control's or custom XML element's content, recursively, the element itself
207
+ * marked inserted (`markHolderInserted`; `owner` is the array holding `block`, which takes the
208
+ * markers). What `Body.insertElement` and `Table.addRows` track new content by (CR-002 section 37).
209
+ */
210
+ export declare function trackInsertedBlock(tracker: ChangeTracker, block: Element, owner: Element[]): void;
211
+ /** `trackInsertedBlock` over every item of a list, which is their owner. */
212
+ export declare function trackInsertedBlocks(tracker: ChangeTracker, items: Element[] | undefined): void;
213
+ /**
214
+ * The elements Word marks inserted as a whole with `w:customXmlInsRangeStart` / `End`: a content
215
+ * control or a custom XML element (check 30 measured the control at run and block level; a smart tag
216
+ * goes into the `w:ins` instead, and a hyperlink or field has no form of its own). Row- and cell-level
217
+ * ones are listed for the reader: `trackInsertedTable` walks through them to the rows and cells, and
218
+ * does not mark them.
219
+ */
220
+ export declare const HOLDER_TYPES: ReadonlySet<string>;
221
+ /** The list a holder's content is in (`w:sdtContent`'s for a control, its own for custom XML), created when absent. */
222
+ export declare function holderItemsOf(value: object): Element[] | undefined;
223
+ /**
224
+ * A content control or custom XML element inserted under tracking, as Word writes it (check 30): a
225
+ * `w:customXmlInsRangeStart` before the element with its `w:customXmlInsRangeEnd` first inside its
226
+ * content, and a second start last inside with its end after the element. Rejecting takes the element
227
+ * away and leaves its content where it stood (`TrackedChange`'s `holder` target); without the markers
228
+ * an element holding only an insertion reads as one that was there before and was typed into, which
229
+ * rejecting keeps (check 30, existing-control-typed). `inside` tracks the content between the two
230
+ * pairs, so that the ids run in document order as Word's do.
231
+ */
232
+ export declare function markHolderInserted(tracker: ChangeTracker, holder: Element, owner: Element[], inside: () => void): void;
137
233
  /** The paragraph mark's run properties (`w:pPr/w:rPr`), created when absent. */
138
234
  export declare function paraRPrOf(p: wml.P): wml.ParaRPr;
139
235
  /** The row's properties (`w:trPr`), created when absent. */