@docx4j/core-ts 0.2.1 → 0.3.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.
@@ -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, 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, wrapNewRuns, 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, 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, wrapNewRuns, 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';
@@ -26,6 +26,14 @@ export declare function findAll(text: string, pattern: RegExp): [number, number]
26
26
  * Every match of a pattern in a text, with its capture groups: what `replaceText` needs to expand
27
27
  * `$1` and Word's `\1`. An empty match advances by one rather than looping forever, as `findAll`
28
28
  * has always done.
29
+ *
30
+ * A match that starts or ends inside a grapheme cluster is dropped: half of an emoji's surrogate
31
+ * pair (a wildcard `?` or class matches one UTF-16 unit), or an Arabic letter without the
32
+ * combining mark after it (`\p{L}+` stops before the mark). Such a range is not text a person can
33
+ * see, and replacing it splits a character - half a surrogate pair is saved as U+FFFD (CR-002
34
+ * section 32 item 1, found by the editor's agreement suite). The clusters are `Intl.Segmenter`'s,
35
+ * computed only for a text with a code unit at or above U+0300, below which every code unit is a
36
+ * cluster of its own, so Latin text pays nothing; a runtime without `Intl.Segmenter` keeps every match.
29
37
  */
30
38
  export declare function matchesOf(text: string, pattern: RegExp): RegExpExecArray[];
31
39
  /**
@@ -94,6 +94,14 @@ export function findAll(text, pattern) {
94
94
  * Every match of a pattern in a text, with its capture groups: what `replaceText` needs to expand
95
95
  * `$1` and Word's `\1`. An empty match advances by one rather than looping forever, as `findAll`
96
96
  * has always done.
97
+ *
98
+ * A match that starts or ends inside a grapheme cluster is dropped: half of an emoji's surrogate
99
+ * pair (a wildcard `?` or class matches one UTF-16 unit), or an Arabic letter without the
100
+ * combining mark after it (`\p{L}+` stops before the mark). Such a range is not text a person can
101
+ * see, and replacing it splits a character - half a surrogate pair is saved as U+FFFD (CR-002
102
+ * section 32 item 1, found by the editor's agreement suite). The clusters are `Intl.Segmenter`'s,
103
+ * computed only for a text with a code unit at or above U+0300, below which every code unit is a
104
+ * cluster of its own, so Latin text pays nothing; a runtime without `Intl.Segmenter` keeps every match.
97
105
  */
98
106
  export function matchesOf(text, pattern) {
99
107
  const out = [];
@@ -105,7 +113,20 @@ export function matchesOf(text, pattern) {
105
113
  }
106
114
  out.push(m);
107
115
  }
108
- return out;
116
+ const boundaries = out.length > 0 ? graphemeBoundaries(text) : undefined;
117
+ return boundaries === undefined ? out : out.filter((m) => boundaries.has(m.index) && boundaries.has(m.index + m[0].length));
118
+ }
119
+ /** The code-unit offsets where a grapheme cluster starts, and the text's end; undefined where every unit is its own cluster. */
120
+ function graphemeBoundaries(text) {
121
+ if (!/[\u0300-\uffff]/.test(text))
122
+ return undefined;
123
+ const Segmenter = Intl.Segmenter;
124
+ if (Segmenter === undefined)
125
+ return undefined;
126
+ const boundaries = new Set([text.length]);
127
+ for (const segment of new Segmenter(undefined, { granularity: 'grapheme' }).segment(text))
128
+ boundaries.add(segment.index);
129
+ return boundaries;
109
130
  }
110
131
  /**
111
132
  * A replacement with its group references expanded against one match: `$1` to `$9`, `$&` and `$$`
@@ -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. */