@docx4j/core-ts 0.1.5 → 0.2.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.
- package/README.md +81 -0
- package/dist/model/content/Body.d.mts +36 -2
- package/dist/model/content/Body.mjs +79 -4
- package/dist/model/content/Comment.d.mts +15 -5
- package/dist/model/content/Comment.mjs +70 -22
- package/dist/model/content/List.d.mts +13 -2
- package/dist/model/content/List.mjs +48 -9
- package/dist/model/content/Paragraph.d.mts +11 -1
- package/dist/model/content/Paragraph.mjs +37 -21
- package/dist/model/content/Range.d.mts +36 -5
- package/dist/model/content/Range.mjs +97 -26
- package/dist/model/content/binder.d.mts +44 -0
- package/dist/model/content/binder.mjs +128 -0
- package/dist/model/content/comments.d.mts +19 -1
- package/dist/model/content/index.d.mts +5 -3
- package/dist/model/content/index.mjs +3 -1
- package/dist/model/content/search.d.mts +27 -2
- package/dist/model/content/search.mjs +59 -3
- package/dist/model/content/stylesFacade.d.mts +37 -0
- package/dist/model/content/stylesFacade.mjs +114 -0
- package/dist/model/content/tracking.d.mts +57 -2
- package/dist/model/content/tracking.mjs +47 -15
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +7 -0
- package/dist/model/customxml/CustomXmlPartCollection.mjs +40 -6
- package/dist/model/customxml/xpath.d.mts +18 -3
- package/dist/model/customxml/xpath.mjs +18 -3
- package/dist/office-js/index.d.mts +3 -1
- package/dist/office-js/run.d.mts +12 -2
- package/dist/office-js/run.mjs +8 -1
- package/dist/office-js/shimmed.d.mts +46 -0
- package/dist/office-js/shimmed.mjs +1 -0
- package/dist/opc/exceptions.d.mts +25 -0
- package/dist/opc/exceptions.mjs +20 -0
- package/dist/opc/index.d.mts +1 -1
- package/dist/opc/index.mjs +1 -1
- package/dist/opc/mce/understood.mjs +37 -8
- package/dist/packages/WordprocessingMLPackage.d.mts +105 -3
- package/dist/packages/WordprocessingMLPackage.mjs +165 -2
- package/dist/parts/XmlPart.d.mts +44 -0
- package/dist/parts/XmlPart.mjs +86 -3
- package/dist/parts/sml/index.d.mts +9 -8
- package/dist/parts/sml/index.mjs +23 -19
- package/dist/parts/wml/index.d.mts +10 -2
- package/dist/parts/wml/index.mjs +11 -3
- package/dist/parts/wml/spliceableStyles.d.mts +1 -0
- package/dist/parts/wml/spliceableStyles.mjs +122 -0
- package/dist/xml/dom.d.mts +10 -1
- package/dist/xml/dom.mjs +46 -2
- package/package.json +2 -2
|
@@ -1,12 +1,37 @@
|
|
|
1
|
-
/** Office JS Word.SearchOptions, the subset honoured here. */
|
|
1
|
+
/** Office JS Word.SearchOptions, the subset honoured here, plus `matchRegExp` (CR-002 section 26). */
|
|
2
2
|
export interface SearchOptions {
|
|
3
3
|
matchCase?: boolean;
|
|
4
4
|
matchWholeWord?: boolean;
|
|
5
5
|
/** Word wildcards: `?` one character, `*` any run, `[a-z]`, `[!x]`, `<` / `>` word start and end, `@` one or more of the previous. */
|
|
6
6
|
matchWildcards?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* The search text is an ECMAScript regular expression, compiled with `u` (extension beyond
|
|
9
|
+
* Office JS, as `replaceText` is; `Word.SearchOptions` has no such member, so it stays out of
|
|
10
|
+
* `Word.supported`). Never `m` or `s`: a match stays inside one paragraph, as every search here
|
|
11
|
+
* does (CR-002 sections 26 and 29). `matchWildcards` as well is an error - they are two
|
|
12
|
+
* languages for the same argument.
|
|
13
|
+
*/
|
|
14
|
+
matchRegExp?: boolean;
|
|
7
15
|
}
|
|
8
16
|
/** A regular expression for a search text under the options; global so it can iterate matches. */
|
|
9
17
|
export declare function searchPattern(text: string, options?: SearchOptions): RegExp;
|
|
10
18
|
export declare function escapeRegExp(s: string): string;
|
|
11
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* All [start, end) matches of a pattern in a text. Kept as it is - it is a public export, and every
|
|
21
|
+
* caller that only wants spans would have to change - so the groups a replacement needs come from
|
|
22
|
+
* `matchesOf` instead (CR-002 section 26's review note, trap 1).
|
|
23
|
+
*/
|
|
12
24
|
export declare function findAll(text: string, pattern: RegExp): [number, number][];
|
|
25
|
+
/**
|
|
26
|
+
* Every match of a pattern in a text, with its capture groups: what `replaceText` needs to expand
|
|
27
|
+
* `$1` and Word's `\1`. An empty match advances by one rather than looping forever, as `findAll`
|
|
28
|
+
* has always done.
|
|
29
|
+
*/
|
|
30
|
+
export declare function matchesOf(text: string, pattern: RegExp): RegExpExecArray[];
|
|
31
|
+
/**
|
|
32
|
+
* A replacement with its group references expanded against one match: `$1` to `$9`, `$&` and `$$`
|
|
33
|
+
* for a regular expression (JavaScript's `String.prototype.replace` rules), and Word's `\1` to
|
|
34
|
+
* `\9` for wildcards (CR-002 section 26). A reference to a group the pattern does not have expands
|
|
35
|
+
* to nothing, as both languages do.
|
|
36
|
+
*/
|
|
37
|
+
export declare function expandReplacement(replacement: string, match: RegExpExecArray, options?: SearchOptions): string;
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
1
2
|
/** A regular expression for a search text under the options; global so it can iterate matches. */
|
|
2
3
|
export function searchPattern(text, options = {}) {
|
|
4
|
+
if (options.matchRegExp && options.matchWildcards) {
|
|
5
|
+
throw new RangeError('matchRegExp and matchWildcards are two languages for the same argument; choose one');
|
|
6
|
+
}
|
|
7
|
+
// The `u` flag goes on this branch ONLY. Under it several escapes that are merely redundant become
|
|
8
|
+
// syntax errors, and the other two branches write some: the wildcard `[...]` doubles backslashes
|
|
9
|
+
// and `escapeRegExp` escapes `-`. So it must not be moved to the one `new RegExp` below.
|
|
10
|
+
if (options.matchRegExp) {
|
|
11
|
+
// A user expression may have a top-level alternation, so `matchWholeWord` has to wrap it in a
|
|
12
|
+
// group: `(?<!\w)cat|dog(?!\w)` is two alternatives with one guard each and matches `dog` in
|
|
13
|
+
// `dogma`. A non-capturing group keeps the group numbers `$1` refers to.
|
|
14
|
+
const source = options.matchWholeWord ? `(?<![\\w])(?:${text})(?![\\w])` : text;
|
|
15
|
+
return new RegExp(source, options.matchCase ? 'gu' : 'giu');
|
|
16
|
+
}
|
|
3
17
|
let source;
|
|
4
18
|
if (options.matchWildcards) {
|
|
5
19
|
source = '';
|
|
@@ -39,6 +53,14 @@ export function searchPattern(text, options = {}) {
|
|
|
39
53
|
else if (c === '\\' && i + 1 < text.length) {
|
|
40
54
|
source += '\\' + text[++i];
|
|
41
55
|
}
|
|
56
|
+
else if (c === '(' || c === ')') {
|
|
57
|
+
// Word's wildcard grouping, which `\1` to `\9` in a replacement refer to (CR-002 section 26);
|
|
58
|
+
// a literal parenthesis is `\(`, taken by the escape case above. Before section 26 both were
|
|
59
|
+
// escaped to literals, so this changes what a pattern with them finds: `f(x)` matched the
|
|
60
|
+
// text "f(x)" and now matches "fx", as Word's wildcards do. An unbalanced one is refused
|
|
61
|
+
// below (CR-002 section 31).
|
|
62
|
+
source += c;
|
|
63
|
+
}
|
|
42
64
|
else
|
|
43
65
|
source += escapeRegExp(c);
|
|
44
66
|
}
|
|
@@ -48,13 +70,32 @@ export function searchPattern(text, options = {}) {
|
|
|
48
70
|
}
|
|
49
71
|
if (options.matchWholeWord)
|
|
50
72
|
source = `(?<![\\w])${source}(?![\\w])`;
|
|
51
|
-
|
|
73
|
+
try {
|
|
74
|
+
return new RegExp(source, options.matchCase ? 'g' : 'gi');
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
// Only a wildcard pattern can fail here - a plain text is escaped whole - and the RegExp's own
|
|
78
|
+
// message quotes the translation, a pattern the caller never wrote.
|
|
79
|
+
throw new Docx4JException(`Not a valid wildcard pattern: ${text}`, { cause });
|
|
80
|
+
}
|
|
52
81
|
}
|
|
53
82
|
export function escapeRegExp(s) {
|
|
54
83
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
55
84
|
}
|
|
56
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* All [start, end) matches of a pattern in a text. Kept as it is - it is a public export, and every
|
|
87
|
+
* caller that only wants spans would have to change - so the groups a replacement needs come from
|
|
88
|
+
* `matchesOf` instead (CR-002 section 26's review note, trap 1).
|
|
89
|
+
*/
|
|
57
90
|
export function findAll(text, pattern) {
|
|
91
|
+
return matchesOf(text, pattern).map((m) => [m.index, m.index + m[0].length]);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Every match of a pattern in a text, with its capture groups: what `replaceText` needs to expand
|
|
95
|
+
* `$1` and Word's `\1`. An empty match advances by one rather than looping forever, as `findAll`
|
|
96
|
+
* has always done.
|
|
97
|
+
*/
|
|
98
|
+
export function matchesOf(text, pattern) {
|
|
58
99
|
const out = [];
|
|
59
100
|
pattern.lastIndex = 0;
|
|
60
101
|
for (let m = pattern.exec(text); m !== null; m = pattern.exec(text)) {
|
|
@@ -62,7 +103,22 @@ export function findAll(text, pattern) {
|
|
|
62
103
|
pattern.lastIndex++;
|
|
63
104
|
continue;
|
|
64
105
|
}
|
|
65
|
-
out.push(
|
|
106
|
+
out.push(m);
|
|
66
107
|
}
|
|
67
108
|
return out;
|
|
68
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* A replacement with its group references expanded against one match: `$1` to `$9`, `$&` and `$$`
|
|
112
|
+
* for a regular expression (JavaScript's `String.prototype.replace` rules), and Word's `\1` to
|
|
113
|
+
* `\9` for wildcards (CR-002 section 26). A reference to a group the pattern does not have expands
|
|
114
|
+
* to nothing, as both languages do.
|
|
115
|
+
*/
|
|
116
|
+
export function expandReplacement(replacement, match, options = {}) {
|
|
117
|
+
if (options.matchRegExp) {
|
|
118
|
+
return replacement.replace(/\$(\$|&|[1-9])/g, (_, token) => { var _a; return token === '$' ? '$' : token === '&' ? match[0] : (_a = match[Number(token)]) !== null && _a !== void 0 ? _a : ''; });
|
|
119
|
+
}
|
|
120
|
+
if (options.matchWildcards) {
|
|
121
|
+
return replacement.replace(/\\([1-9])/g, (_, digit) => { var _a; return (_a = match[Number(digit)]) !== null && _a !== void 0 ? _a : ''; });
|
|
122
|
+
}
|
|
123
|
+
return replacement;
|
|
124
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { StyleDefinitionsPart, MainDocumentPart } from '../../parts/wml/index.mjs';
|
|
2
|
+
/** What the facade needs of its package; `WordprocessingMLPackage` satisfies it. */
|
|
3
|
+
export interface StylePackageLike {
|
|
4
|
+
getMainDocumentPart(): MainDocumentPart;
|
|
5
|
+
refreshPropertyResolver(): void;
|
|
6
|
+
/** Creates the styles part, with the defaults, when the document has none. */
|
|
7
|
+
ensureStyleDefinitionsPart(): StyleDefinitionsPart;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The document's style definitions as verbs that need no `Body`: `pkg.styles`.
|
|
11
|
+
*
|
|
12
|
+
* `ensure` is the one verb, and the point of it is the closure: a style is useless without what it
|
|
13
|
+
* is `w:basedOn` and `w:link`ed to, and a caller asking for `FootnoteText` means the four styles
|
|
14
|
+
* that make a footnote look like a footnote, not one.
|
|
15
|
+
*/
|
|
16
|
+
export declare class StylesFacade {
|
|
17
|
+
private readonly pkg;
|
|
18
|
+
constructor(pkg: StylePackageLike);
|
|
19
|
+
/**
|
|
20
|
+
* Adds each style the document lacks, from the same defaults `createPackage()` writes, together
|
|
21
|
+
* with the styles it is `w:basedOn` and `w:link`ed to, recursively. Returns the ids actually
|
|
22
|
+
* added, in the order they were written, so a caller can tell whether it changed anything.
|
|
23
|
+
*
|
|
24
|
+
* A style already in the document is left exactly as it is - the document's own definition wins
|
|
25
|
+
* over the default, always, since a consumer must not silently restyle a document it was asked to
|
|
26
|
+
* add a footnote to. An id the defaults do not carry throws a `Docx4JException` naming it: the
|
|
27
|
+
* caller asked for a definition nothing here has, and inventing one would be worse than saying so.
|
|
28
|
+
* The whole closure is worked out before anything is written, so a call that throws has changed
|
|
29
|
+
* nothing (CR-002 section 31).
|
|
30
|
+
*
|
|
31
|
+
* A document that already has every id keeps its styles part **byte for byte**: the part is not
|
|
32
|
+
* unmarshalled at all when its XML names them, the check `ensureCommentStyles` uses, and when that
|
|
33
|
+
* text check misses (another prefix, single quotes) it is read privately rather than unmarshalled,
|
|
34
|
+
* so the part is still written back from its source unless something is added.
|
|
35
|
+
*/
|
|
36
|
+
ensure(ids: string | readonly string[]): Promise<string[]>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { deepCopy, unmarshalNode } from '@docx4j/generated-objects-ts';
|
|
2
|
+
import { linkParents } from './tree.mjs';
|
|
3
|
+
import { parseXml } from '../../xml/dom.mjs';
|
|
4
|
+
import { DEFAULT_STYLES_XML } from '../../parts/wml/defaultStyles.mjs';
|
|
5
|
+
import { SPLICEABLE_STYLES_XML } from '../../parts/wml/spliceableStyles.mjs';
|
|
6
|
+
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
7
|
+
/**
|
|
8
|
+
* Where a definition is looked up, unmarshalled once and never handed out (a copy is spliced in):
|
|
9
|
+
* the defaults `createPackage()` writes, and then the nine styles docx4j's own `styles.xml` carries
|
|
10
|
+
* **commented out** - which is why a created document has no `FootnoteText` although the file
|
|
11
|
+
* appears to contain one, and why this facade exists (CR-002 section 22.2).
|
|
12
|
+
*/
|
|
13
|
+
let sources;
|
|
14
|
+
function styleSources() {
|
|
15
|
+
return (sources !== null && sources !== void 0 ? sources : (sources = (async () => {
|
|
16
|
+
const read = async (xml) => { var _a; return (_a = (await unmarshalNode(parseXml(xml))).value.style) !== null && _a !== void 0 ? _a : []; };
|
|
17
|
+
const defaults = await read(DEFAULT_STYLES_XML);
|
|
18
|
+
return { defaults, all: [...defaults, ...(await read(SPLICEABLE_STYLES_XML))] };
|
|
19
|
+
})()));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The document's style definitions as verbs that need no `Body`: `pkg.styles`.
|
|
23
|
+
*
|
|
24
|
+
* `ensure` is the one verb, and the point of it is the closure: a style is useless without what it
|
|
25
|
+
* is `w:basedOn` and `w:link`ed to, and a caller asking for `FootnoteText` means the four styles
|
|
26
|
+
* that make a footnote look like a footnote, not one.
|
|
27
|
+
*/
|
|
28
|
+
export class StylesFacade {
|
|
29
|
+
constructor(pkg) {
|
|
30
|
+
this.pkg = pkg;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Adds each style the document lacks, from the same defaults `createPackage()` writes, together
|
|
34
|
+
* with the styles it is `w:basedOn` and `w:link`ed to, recursively. Returns the ids actually
|
|
35
|
+
* added, in the order they were written, so a caller can tell whether it changed anything.
|
|
36
|
+
*
|
|
37
|
+
* A style already in the document is left exactly as it is - the document's own definition wins
|
|
38
|
+
* over the default, always, since a consumer must not silently restyle a document it was asked to
|
|
39
|
+
* add a footnote to. An id the defaults do not carry throws a `Docx4JException` naming it: the
|
|
40
|
+
* caller asked for a definition nothing here has, and inventing one would be worse than saying so.
|
|
41
|
+
* The whole closure is worked out before anything is written, so a call that throws has changed
|
|
42
|
+
* nothing (CR-002 section 31).
|
|
43
|
+
*
|
|
44
|
+
* A document that already has every id keeps its styles part **byte for byte**: the part is not
|
|
45
|
+
* unmarshalled at all when its XML names them, the check `ensureCommentStyles` uses, and when that
|
|
46
|
+
* text check misses (another prefix, single quotes) it is read privately rather than unmarshalled,
|
|
47
|
+
* so the part is still written back from its source unless something is added.
|
|
48
|
+
*/
|
|
49
|
+
async ensure(ids) {
|
|
50
|
+
var _a, _b;
|
|
51
|
+
const wanted = typeof ids === 'string' ? [ids] : [...ids];
|
|
52
|
+
for (const id of wanted) {
|
|
53
|
+
if (typeof id !== 'string' || id === '')
|
|
54
|
+
throw new Docx4JException(`Not a styleId: ${String(id)}`);
|
|
55
|
+
}
|
|
56
|
+
if (wanted.length === 0)
|
|
57
|
+
return [];
|
|
58
|
+
const existing = this.pkg.getMainDocumentPart().styleDefinitionsPart;
|
|
59
|
+
// The cheap path: an untouched part whose XML names them all is left alone, so a document that
|
|
60
|
+
// has them keeps its styles part byte for byte. The comments must go first - docx4j's own
|
|
61
|
+
// default styles carry nine definitions COMMENTED OUT, so a plain `includes` finds
|
|
62
|
+
// `w:styleId="FootnoteText"` in a document that does not have the style and this would answer
|
|
63
|
+
// "nothing to do" for exactly the documents the facade exists for.
|
|
64
|
+
if (existing !== undefined && !existing.isUnmarshalled) {
|
|
65
|
+
const xml = (await existing.getXml()).replace(/<!--[\s\S]*?-->/g, '');
|
|
66
|
+
if (wanted.every((id) => xml.includes(`w:styleId="${id}"`)))
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
// What the document holds now, read without unmarshalling; a part created below holds the defaults.
|
|
70
|
+
const source = await styleSources();
|
|
71
|
+
const present = existing === undefined ? source.defaults : ((_a = (await existing.readContents()).style) !== null && _a !== void 0 ? _a : []);
|
|
72
|
+
const plan = closureOf(wanted, present, source.all);
|
|
73
|
+
// a document with no styles part gets the defaults, whether or not anything else is needed
|
|
74
|
+
const part = existing !== null && existing !== void 0 ? existing : this.pkg.ensureStyleDefinitionsPart();
|
|
75
|
+
if (plan.length === 0)
|
|
76
|
+
return [];
|
|
77
|
+
const styles = await part.getContents();
|
|
78
|
+
const list = ((_b = styles.style) !== null && _b !== void 0 ? _b : (styles.style = []));
|
|
79
|
+
for (const template of plan) {
|
|
80
|
+
const style = deepCopy(template);
|
|
81
|
+
list.push(style);
|
|
82
|
+
linkParents(style, styles);
|
|
83
|
+
}
|
|
84
|
+
this.pkg.refreshPropertyResolver();
|
|
85
|
+
return plan.map((style) => style.styleId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The definitions to splice, in the order they are written: each wanted id the document lacks,
|
|
90
|
+
* after what it is `w:basedOn` and `w:link`ed to, recursively. Throws for an id nothing carries.
|
|
91
|
+
*/
|
|
92
|
+
function closureOf(wanted, present, source) {
|
|
93
|
+
const plan = [];
|
|
94
|
+
const has = (id) => present.some((s) => s.styleId === id) || plan.some((s) => s.styleId === id);
|
|
95
|
+
const add = (id, seen) => {
|
|
96
|
+
var _a, _b;
|
|
97
|
+
if (has(id) || seen.has(id))
|
|
98
|
+
return;
|
|
99
|
+
seen.add(id);
|
|
100
|
+
const template = source.find((s) => s.styleId === id);
|
|
101
|
+
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.`);
|
|
103
|
+
}
|
|
104
|
+
// what it is based on and linked to must be there too, and before it reads better in the part
|
|
105
|
+
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]) {
|
|
106
|
+
if (typeof dependency === 'string')
|
|
107
|
+
add(dependency, seen);
|
|
108
|
+
}
|
|
109
|
+
plan.push(template);
|
|
110
|
+
};
|
|
111
|
+
for (const id of wanted)
|
|
112
|
+
add(id, new Set());
|
|
113
|
+
return plan;
|
|
114
|
+
}
|
|
@@ -3,6 +3,19 @@ import { type Element, type RevisionHolder } from './tree.mjs';
|
|
|
3
3
|
import type { Author } from './comments.mjs';
|
|
4
4
|
/** Office JS `Word.ChangeTrackingMode`. */
|
|
5
5
|
export type ChangeTrackingMode = 'Off' | 'TrackAll' | 'TrackMineOnly';
|
|
6
|
+
/**
|
|
7
|
+
* `w:id` on these is the one annotation id space (ECMA-376 17.13.5.4): revisions, bookmarks,
|
|
8
|
+
* comment range marks and permissions share it. `w:comment/@w:id` (docx4j `Comments.Comment`,
|
|
9
|
+
* which also extends `CTMarkup`) is a different space and is excluded, so that phase G's
|
|
10
|
+
* comment ids and phase F's revision ids never have to agree.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MARKUP_TYPES: ReadonlySet<string>;
|
|
13
|
+
/**
|
|
14
|
+
* The highest annotation id in a tree (`MARKUP_TYPES` only, the one `w:id` space of ECMA-376
|
|
15
|
+
* 17.13.5.4). Shared so that the tracker's scan of the unmarshalled parts and the package's
|
|
16
|
+
* private read of the rest cannot drift apart (CR-002 section 28.2).
|
|
17
|
+
*/
|
|
18
|
+
export declare function highestAnnotationId(root: object, from?: number): number;
|
|
6
19
|
/** What a package offers so that its edits can be tracked; `WordprocessingMLPackage` implements it. */
|
|
7
20
|
export interface TrackingHost {
|
|
8
21
|
/**
|
|
@@ -15,9 +28,43 @@ export interface TrackingHost {
|
|
|
15
28
|
readonly trackedChangeDate?: Date | undefined;
|
|
16
29
|
/** The trees to scan for the highest annotation id in use: the parts already unmarshalled. */
|
|
17
30
|
markupRoots(): object[];
|
|
31
|
+
/**
|
|
32
|
+
* A floor for the annotation id, above every id in the parts that were **not** unmarshalled
|
|
33
|
+
* (`seedAnnotationIds()` reads them privately). 0 until it is seeded; `markupRoots()` covers the
|
|
34
|
+
* rest, so the next id is one above the greater of the two (CR-002 section 28.2).
|
|
35
|
+
*/
|
|
36
|
+
readonly annotationIdFloor: number;
|
|
37
|
+
/**
|
|
38
|
+
* The last annotation id issued in the document, or `undefined` until the first, when the tracker
|
|
39
|
+
* scans for the highest in use. The host holds it rather than the tracker because a document has
|
|
40
|
+
* one id space and may have several trackers over its life - `withTracking` can make one with
|
|
41
|
+
* another mode - and a tracker with a counter of its own reissues the ids another one gave out
|
|
42
|
+
* (CR-002 section 31).
|
|
43
|
+
*/
|
|
44
|
+
annotationIdCounter: number | undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* What `WordprocessingMLPackage.withTracking` records for the duration of one call: who, when, and
|
|
48
|
+
* which mode (`TrackAll` unless said otherwise). The document's own `w:trackRevisions` is not
|
|
49
|
+
* touched - an editor's agent records its edits as itself in a room whose setting belongs to the
|
|
50
|
+
* document, not to the caller (CR-002 section 28.1).
|
|
51
|
+
*/
|
|
52
|
+
export interface TrackingScope {
|
|
53
|
+
author: Author;
|
|
54
|
+
/** The `w:date` for every revision in the call; `undefined` means now, to the minute. */
|
|
55
|
+
date?: Date | undefined;
|
|
56
|
+
/** `TrackAll` unless given; `Off` runs `fn` with tracking suppressed for the call. */
|
|
57
|
+
mode?: ChangeTrackingMode | undefined;
|
|
18
58
|
}
|
|
19
59
|
/** The tracker of a package whose mode is not `Off`; `undefined` when it is (duck typed, no import cycle). */
|
|
20
60
|
export declare function trackerOf(pkg: unknown): ChangeTracker | undefined;
|
|
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.
|
|
66
|
+
*/
|
|
67
|
+
export declare function nowToTheMinute(): Date;
|
|
21
68
|
/** An `XmlCalendar` in UTC, as Word writes `w:date` (an xsd:dateTime ending in `Z`). */
|
|
22
69
|
export declare function calendarOf(date: Date): wml.XmlCalendar;
|
|
23
70
|
/** The `Date` of a `w:date`, or undefined when there is none. */
|
|
@@ -33,11 +80,19 @@ export declare function dateOf(cal: wml.XmlCalendar | undefined): Date | undefin
|
|
|
33
80
|
export declare class ChangeTracker {
|
|
34
81
|
readonly host: TrackingHost;
|
|
35
82
|
readonly mode: ChangeTrackingMode;
|
|
36
|
-
private counter;
|
|
37
83
|
constructor(host: TrackingHost, mode: ChangeTrackingMode);
|
|
38
84
|
/** The `w:author` a new revision carries: the package author's name. */
|
|
39
85
|
get author(): string;
|
|
40
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* The next annotation id: one above the highest in use anywhere in the document - the parts
|
|
88
|
+
* already unmarshalled, scanned here, and the rest through the host's `annotationIdFloor`,
|
|
89
|
+
* which `seedAnnotationIds()` fills from their bytes without unmarshalling them (CR-002
|
|
90
|
+
* section 28.2; before that this saw only the unmarshalled parts, so a revision in an unread
|
|
91
|
+
* footnote or comment part did not raise the counter).
|
|
92
|
+
*
|
|
93
|
+
* The scan runs once per document, not per tracker: the counter is the host's. The floor is
|
|
94
|
+
* consulted on every call, so a `seedAnnotationIds()` awaited after the first id still counts.
|
|
95
|
+
*/
|
|
41
96
|
nextId(): number;
|
|
42
97
|
/** The `w:id`, `w:author` and `w:date` a new revision carries. */
|
|
43
98
|
markup(): {
|
|
@@ -9,7 +9,7 @@ import { W_NS, linkParents, walk, typeNameOf, revisionKindOf } from './tree.mjs'
|
|
|
9
9
|
* which also extends `CTMarkup`) is a different space and is excluded, so that phase G's
|
|
10
10
|
* comment ids and phase F's revision ids never have to agree.
|
|
11
11
|
*/
|
|
12
|
-
const MARKUP_TYPES = new Set([
|
|
12
|
+
export const MARKUP_TYPES = new Set([
|
|
13
13
|
'org_docx4j_wml.CTMarkup', 'org_docx4j_wml.CTBookmark', 'org_docx4j_wml.CTBookmarkRange',
|
|
14
14
|
'org_docx4j_wml.CTCellMergeTrackChange', 'org_docx4j_wml.CTMarkupRange', 'org_docx4j_wml.CTMathRunTrackChange',
|
|
15
15
|
'org_docx4j_wml.CTMoveBookmark', 'org_docx4j_wml.CTMoveFromRangeEnd', 'org_docx4j_wml.CTMoveToRangeEnd',
|
|
@@ -19,10 +19,36 @@ const MARKUP_TYPES = new Set([
|
|
|
19
19
|
'org_docx4j_wml.CTTrackChangeNumbering', 'org_docx4j_wml.CTTrackChangeRange',
|
|
20
20
|
'org_docx4j_wml.ParaRPrChange', 'org_docx4j_wml.RunDel', 'org_docx4j_wml.RunIns', 'org_docx4j_wml.RunTrackChange',
|
|
21
21
|
]);
|
|
22
|
+
/**
|
|
23
|
+
* The highest annotation id in a tree (`MARKUP_TYPES` only, the one `w:id` space of ECMA-376
|
|
24
|
+
* 17.13.5.4). Shared so that the tracker's scan of the unmarshalled parts and the package's
|
|
25
|
+
* private read of the rest cannot drift apart (CR-002 section 28.2).
|
|
26
|
+
*/
|
|
27
|
+
export function highestAnnotationId(root, from = 0) {
|
|
28
|
+
let highest = from;
|
|
29
|
+
walk(root, (value) => {
|
|
30
|
+
const tn = value.TYPE_NAME;
|
|
31
|
+
const id = value.id;
|
|
32
|
+
if (tn !== undefined && MARKUP_TYPES.has(tn) && typeof id === 'number' && id > highest)
|
|
33
|
+
highest = id;
|
|
34
|
+
});
|
|
35
|
+
return highest;
|
|
36
|
+
}
|
|
22
37
|
/** The tracker of a package whose mode is not `Off`; `undefined` when it is (duck typed, no import cycle). */
|
|
23
38
|
export function trackerOf(pkg) {
|
|
24
39
|
return pkg === null || pkg === void 0 ? void 0 : pkg.changeTracker;
|
|
25
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Now, with the seconds and milliseconds dropped, which is what Word stamps a revision with.
|
|
43
|
+
* Not cosmetic: Word groups adjacent revisions by author **and** timestamp, so a per-second stamp
|
|
44
|
+
* makes one change of every keystroke run and an editor's typing unreviewable (CR-002 section 28.2).
|
|
45
|
+
* A caller that sets `trackedChangeDate` gets exactly the date it asked for, truncated or not.
|
|
46
|
+
*/
|
|
47
|
+
export function nowToTheMinute() {
|
|
48
|
+
const now = new Date();
|
|
49
|
+
now.setUTCSeconds(0, 0);
|
|
50
|
+
return now;
|
|
51
|
+
}
|
|
26
52
|
/** An `XmlCalendar` in UTC, as Word writes `w:date` (an xsd:dateTime ending in `Z`). */
|
|
27
53
|
export function calendarOf(date) {
|
|
28
54
|
return {
|
|
@@ -58,26 +84,32 @@ export class ChangeTracker {
|
|
|
58
84
|
get author() {
|
|
59
85
|
return this.host.author.name;
|
|
60
86
|
}
|
|
61
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* The next annotation id: one above the highest in use anywhere in the document - the parts
|
|
89
|
+
* already unmarshalled, scanned here, and the rest through the host's `annotationIdFloor`,
|
|
90
|
+
* which `seedAnnotationIds()` fills from their bytes without unmarshalling them (CR-002
|
|
91
|
+
* section 28.2; before that this saw only the unmarshalled parts, so a revision in an unread
|
|
92
|
+
* footnote or comment part did not raise the counter).
|
|
93
|
+
*
|
|
94
|
+
* The scan runs once per document, not per tracker: the counter is the host's. The floor is
|
|
95
|
+
* consulted on every call, so a `seedAnnotationIds()` awaited after the first id still counts.
|
|
96
|
+
*/
|
|
62
97
|
nextId() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (tn !== undefined && MARKUP_TYPES.has(tn) && typeof id === 'number' && id > highest)
|
|
70
|
-
highest = id;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
this.counter = highest;
|
|
98
|
+
const host = this.host;
|
|
99
|
+
let counter = host.annotationIdCounter;
|
|
100
|
+
if (counter === undefined) {
|
|
101
|
+
counter = 0;
|
|
102
|
+
for (const root of host.markupRoots())
|
|
103
|
+
counter = highestAnnotationId(root, counter);
|
|
74
104
|
}
|
|
75
|
-
|
|
105
|
+
counter = Math.max(counter, host.annotationIdFloor) + 1;
|
|
106
|
+
host.annotationIdCounter = counter;
|
|
107
|
+
return counter;
|
|
76
108
|
}
|
|
77
109
|
/** The `w:id`, `w:author` and `w:date` a new revision carries. */
|
|
78
110
|
markup() {
|
|
79
111
|
var _a;
|
|
80
|
-
return { id: this.nextId(), author: this.author, date: calendarOf((_a = this.host.trackedChangeDate) !== null && _a !== void 0 ? _a :
|
|
112
|
+
return { id: this.nextId(), author: this.author, date: calendarOf((_a = this.host.trackedChangeDate) !== null && _a !== void 0 ? _a : nowToTheMinute()) };
|
|
81
113
|
}
|
|
82
114
|
/** A `w:ins` / `w:del` attribute set: the id, this package's author and the date. */
|
|
83
115
|
trackChange() {
|
|
@@ -40,9 +40,16 @@ export declare class CustomXmlPartCollection implements CustomXmlPartOwner, Cust
|
|
|
40
40
|
* A new custom XML part holding this XML, with its properties part and a fresh itemID, related
|
|
41
41
|
* from the main document part (docx4j's `AbstractMigrator.addPropertiesPart` recipe: Word
|
|
42
42
|
* silently drops a custom XML part that only the package relates to).
|
|
43
|
+
*
|
|
44
|
+
* `itemID` and `partName` may be given, both checked unused, for a co-editing room where two
|
|
45
|
+
* sessions allocate at once and the next free name is not the right answer (CR-002 section 24).
|
|
46
|
+
* A clash throws rather than renaming, as a chosen list `numId` does: the caller is asserting
|
|
47
|
+
* something about the room's state, and renaming would break the peer that chose it.
|
|
43
48
|
*/
|
|
44
49
|
add(xml: string, options?: {
|
|
45
50
|
schemaRefs?: string[];
|
|
51
|
+
itemID?: string;
|
|
52
|
+
partName?: string;
|
|
46
53
|
}): CustomXmlPart;
|
|
47
54
|
/**
|
|
48
55
|
* Removes a part, its properties part and the relationships (Office JS CustomXmlPart.delete).
|
|
@@ -33,7 +33,10 @@ export class CustomXmlPartCollection {
|
|
|
33
33
|
if (!this.views.has(part))
|
|
34
34
|
this.views.set(part, await this.viewFor(part));
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
// A parsed document, not the global one: whether a native `document.evaluate` can read these
|
|
37
|
+
// trees is a property of the parser that built them (CR-002 section 21).
|
|
38
|
+
const parsed = this.views.size > 0 ? [...this.views.values()][0].document : undefined;
|
|
39
|
+
await this.host.xpathEngine.ready(parsed);
|
|
37
40
|
return this.items;
|
|
38
41
|
}
|
|
39
42
|
/** The parts, in the order they were registered. Call `load()` once first: a DOM has to be parsed. */
|
|
@@ -61,20 +64,40 @@ export class CustomXmlPartCollection {
|
|
|
61
64
|
* A new custom XML part holding this XML, with its properties part and a fresh itemID, related
|
|
62
65
|
* from the main document part (docx4j's `AbstractMigrator.addPropertiesPart` recipe: Word
|
|
63
66
|
* silently drops a custom XML part that only the package relates to).
|
|
67
|
+
*
|
|
68
|
+
* `itemID` and `partName` may be given, both checked unused, for a co-editing room where two
|
|
69
|
+
* sessions allocate at once and the next free name is not the right answer (CR-002 section 24).
|
|
70
|
+
* A clash throws rather than renaming, as a chosen list `numId` does: the caller is asserting
|
|
71
|
+
* something about the room's state, and renaming would break the peer that chose it.
|
|
64
72
|
*/
|
|
65
73
|
add(xml, options = {}) {
|
|
66
|
-
var _a;
|
|
74
|
+
var _a, _b;
|
|
67
75
|
const document = parseXml(xml);
|
|
68
76
|
const root = document.documentElement;
|
|
69
77
|
if (!root)
|
|
70
78
|
throw new Docx4JException('A custom XML part needs a document element');
|
|
71
|
-
|
|
79
|
+
// Everything that can refuse the call runs before a part is made, so a refusal leaves the package
|
|
80
|
+
// as it was (CR-002 section 31: a malformed itemID used to be refused after the data part was
|
|
81
|
+
// related, which left it in the package without a properties part). The itemID is checked
|
|
82
|
+
// against the keys the package indexes its custom XML parts by, which needs no parse; the views
|
|
83
|
+
// do, and `load()` is not a precondition of `add()`.
|
|
84
|
+
const chosenName = options.partName === undefined ? undefined : PartName.of(options.partName);
|
|
85
|
+
if (chosenName && ((_a = this.host.customXmlRelationshipSource().package) === null || _a === void 0 ? void 0 : _a.parts.get(chosenName.name))) {
|
|
86
|
+
throw new Docx4JException(`The part name ${chosenName.name} is already in this package`);
|
|
87
|
+
}
|
|
88
|
+
const itemID = options.itemID === undefined ? `{${uuid().toUpperCase()}}` : normaliseChosenItemID(options.itemID);
|
|
89
|
+
if (options.itemID !== undefined) {
|
|
90
|
+
const key = normaliseId(itemID);
|
|
91
|
+
if ([...this.host.customXmlDataStorageParts.keys()].some((existing) => normaliseId(existing) === key)) {
|
|
92
|
+
throw new Docx4JException(`The itemID ${options.itemID} is already in use by a custom XML part`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const dataPart = new CustomXmlDataStoragePart(chosenName !== null && chosenName !== void 0 ? chosenName : '/customXml/item1.xml');
|
|
72
96
|
dataPart.setDocument(document);
|
|
73
|
-
this.host.customXmlRelationshipSource().addTargetPart(dataPart, 'RENAME_IF_NAME_EXISTS');
|
|
74
|
-
const itemID = `{${uuid().toUpperCase()}}`;
|
|
97
|
+
this.host.customXmlRelationshipSource().addTargetPart(dataPart, chosenName ? 'OVERWRITE_IF_NAME_EXISTS' : 'RENAME_IF_NAME_EXISTS');
|
|
75
98
|
const propsName = PartName.of(`/customXml/itemProps${suffixOf(dataPart.partName)}.xml`);
|
|
76
99
|
const props = new CustomXmlDataStoragePropertiesPart(propsName);
|
|
77
|
-
const refs = (
|
|
100
|
+
const refs = (_b = options.schemaRefs) !== null && _b !== void 0 ? _b : (root.namespaceURI ? [root.namespaceURI] : []);
|
|
78
101
|
props.setContents(createDatastoreItem({
|
|
79
102
|
itemID,
|
|
80
103
|
...(refs.length > 0 ? { schemaRefs: { schemaRef: refs.map((uri) => ({ uri })) } } : {}),
|
|
@@ -152,6 +175,17 @@ export class CustomXmlPartCollection {
|
|
|
152
175
|
}
|
|
153
176
|
}
|
|
154
177
|
/** An itemID without braces, lower-cased: how docx4j keys the map and how Office JS compares ids. */
|
|
178
|
+
/**
|
|
179
|
+
* A chosen itemID in the form the properties part carries: braced and upper case, as Word and
|
|
180
|
+
* `uuid()` write it. A caller may pass it either way round (CR-002 section 24).
|
|
181
|
+
*/
|
|
182
|
+
function normaliseChosenItemID(id) {
|
|
183
|
+
const bare = id.trim().replace(/^\{|\}$/g, '');
|
|
184
|
+
if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(bare)) {
|
|
185
|
+
throw new Docx4JException(`An itemID must be a GUID, with or without braces, not ${id}`);
|
|
186
|
+
}
|
|
187
|
+
return `{${bare.toUpperCase()}}`;
|
|
188
|
+
}
|
|
155
189
|
function normaliseId(id) {
|
|
156
190
|
return id.replace(/[{}]/g, '').toLowerCase();
|
|
157
191
|
}
|
|
@@ -19,8 +19,15 @@ export type BooleanConversionMode = 'java' | 'xpath1' | 'xpath2';
|
|
|
19
19
|
* string, number or boolean result. `namespaces` maps prefixes used in the expression to URIs.
|
|
20
20
|
*/
|
|
21
21
|
export interface XPathEngine {
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Prepares the engine (loads the `xpath` package in Node); idempotent, and cheap once done.
|
|
24
|
+
*
|
|
25
|
+
* `context` is a node from a document the engine will be asked about, when the caller has one:
|
|
26
|
+
* whether a native `document.evaluate` can be used is a property of **that document**, not of
|
|
27
|
+
* the global one, and the two differ in a mixed environment - a jsdom test whose XML is parsed
|
|
28
|
+
* by xmldom has a global `document.evaluate` that cannot evaluate over the parsed tree.
|
|
29
|
+
*/
|
|
30
|
+
ready(context?: Node): Promise<void>;
|
|
24
31
|
/** True when `select` and `selectValue` can be called synchronously. */
|
|
25
32
|
readonly isReady: boolean;
|
|
26
33
|
select(expression: string, context: Node, namespaces?: Record<string, string>): Node[];
|
|
@@ -55,7 +62,15 @@ export declare class DefaultXPathEngine implements XPathEngine {
|
|
|
55
62
|
/** True when the runtime has a native `document.evaluate` (a browser, a Word add-in). */
|
|
56
63
|
private native;
|
|
57
64
|
get isReady(): boolean;
|
|
58
|
-
|
|
65
|
+
/**
|
|
66
|
+
* @param context a node from a document this engine will be asked about. Given one, readiness is
|
|
67
|
+
* decided from **that document** rather than from the global: a global `document.evaluate` says
|
|
68
|
+
* nothing about a tree some other parser built, and in a mixed environment - jsdom's global
|
|
69
|
+
* with xmldom's parser, which is what a Node test runner gives when the runtime resolves its
|
|
70
|
+
* `node` export condition - the global would claim readiness the parsed tree cannot honour.
|
|
71
|
+
* `pkg.customXmlParts.load()` passes a parsed document for exactly this reason.
|
|
72
|
+
*/
|
|
73
|
+
ready(context?: Node): Promise<void>;
|
|
59
74
|
select(expression: string, context: Node, namespaces?: Record<string, string>): Node[];
|
|
60
75
|
selectValue(expression: string, context: Node, namespaces?: Record<string, string>): XPathValue;
|
|
61
76
|
private call;
|
|
@@ -67,9 +67,19 @@ export class DefaultXPathEngine {
|
|
|
67
67
|
get isReady() {
|
|
68
68
|
return this.native || this.module !== undefined;
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* @param context a node from a document this engine will be asked about. Given one, readiness is
|
|
72
|
+
* decided from **that document** rather than from the global: a global `document.evaluate` says
|
|
73
|
+
* nothing about a tree some other parser built, and in a mixed environment - jsdom's global
|
|
74
|
+
* with xmldom's parser, which is what a Node test runner gives when the runtime resolves its
|
|
75
|
+
* `node` export condition - the global would claim readiness the parsed tree cannot honour.
|
|
76
|
+
* `pkg.customXmlParts.load()` passes a parsed document for exactly this reason.
|
|
77
|
+
*/
|
|
78
|
+
async ready(context) {
|
|
71
79
|
var _a;
|
|
72
|
-
if (this.
|
|
80
|
+
if (this.module !== undefined)
|
|
81
|
+
return;
|
|
82
|
+
if (context === undefined ? this.native : nativeEvaluatorFor(context) !== undefined)
|
|
73
83
|
return;
|
|
74
84
|
(_a = this.loading) !== null && _a !== void 0 ? _a : (this.loading = (async () => {
|
|
75
85
|
const specifier = 'xpath';
|
|
@@ -124,7 +134,12 @@ export class DefaultXPathEngine {
|
|
|
124
134
|
}
|
|
125
135
|
call(expression, context, namespaces) {
|
|
126
136
|
if (!this.module) {
|
|
127
|
-
throw new Docx4JException(
|
|
137
|
+
throw new Docx4JException(this.native
|
|
138
|
+
? 'This document cannot be evaluated by the host DOM: it was parsed by another one (jsdom\'s '
|
|
139
|
+
+ 'document.evaluate over an xmldom tree, say). Install the optional peer dependency '
|
|
140
|
+
+ "'xpath' and await pkg.xpathEngine.ready(node) with a node of this document, or set "
|
|
141
|
+
+ 'pkg.xpathEngine to an engine that can read it'
|
|
142
|
+
: 'The XPath engine is not ready yet: await pkg.customXmlParts.load() (or pkg.xpathEngine.ready()) once before selecting nodes');
|
|
128
143
|
}
|
|
129
144
|
const select = Object.keys(namespaces).length > 0 ? this.module.useNamespaces(namespaces) : this.module.select;
|
|
130
145
|
return select(expression, context);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { run, RequestContext, type RunOptions, type TrackedObjects } from './run.mjs';
|
|
1
|
+
import { run, RequestContext, type RunOptions, type TrackedObjects, type ShimmedDocument } from './run.mjs';
|
|
2
2
|
import { Document, DocumentProperties } from './document.mjs';
|
|
3
3
|
import { supported, SUBSET_MEMBERS } from './supported.mjs';
|
|
4
4
|
import { ClientResult, unwrap, nullObject, Wrapper, type Collection, type PendingSink } from './proxy.mjs';
|
|
@@ -6,6 +6,8 @@ import { NotSupportedError, ItemNotFoundError, ValueNotLoadedError } from './err
|
|
|
6
6
|
import { toApiScript, type ApiScriptOptions, type ApiScriptTarget } from './toApiScript.mjs';
|
|
7
7
|
import { InsertLocation, Alignment, UnderlineType, ChangeTrackingMode, BreakType, ContentControlType, Style, ErrorCodes, SearchOptions, ListLevelType, ListNumbering, ListBullet, type InsertLocationValue, type ChangeTrackingModeValue, type BreakTypeValue, type ContentControlTypeValue, type StyleValue, type AlignmentValue, type UnderlineTypeValue, type ListLevelTypeValue, type ListNumberingValue, type ListBulletValue } from './enums.mjs';
|
|
8
8
|
export { run, RequestContext, Document, DocumentProperties, ClientResult, unwrap, nullObject, Wrapper, toApiScript, supported, SUBSET_MEMBERS };
|
|
9
|
+
export type { Shimmed, ShimmedObject, ShimmedCollection, Loadable } from './shimmed.mjs';
|
|
10
|
+
export type { ShimmedDocument };
|
|
9
11
|
export { NotSupportedError, ItemNotFoundError, ValueNotLoadedError };
|
|
10
12
|
export { InsertLocation, Alignment, UnderlineType, ChangeTrackingMode, BreakType, ContentControlType, Style, ErrorCodes, SearchOptions };
|
|
11
13
|
export { ListLevelType, ListNumbering, ListBullet };
|