@docx4j/core-ts 0.1.5 → 0.2.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.
- package/README.md +76 -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 +18 -7
- package/dist/model/content/Range.d.mts +7 -2
- package/dist/model/content/Range.mjs +21 -8
- package/dist/model/content/binder.d.mts +44 -0
- package/dist/model/content/binder.mjs +95 -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 +92 -2
- package/dist/packages/WordprocessingMLPackage.mjs +144 -1
- 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/package.json +2 -2
|
@@ -10,7 +10,7 @@ function withRPr(run, rPr) {
|
|
|
10
10
|
import { Font } from './Font.mjs';
|
|
11
11
|
import { runFontSelectorOf } from '../fonts/lookup.mjs';
|
|
12
12
|
import { Range } from './Range.mjs';
|
|
13
|
-
import { searchPattern,
|
|
13
|
+
import { searchPattern, matchesOf, expandReplacement } from './search.mjs';
|
|
14
14
|
import { builtInOf, idOfBuiltIn, styleNameOf, styleIdOf } from './styles.mjs';
|
|
15
15
|
import { cellOf } from './Table.mjs';
|
|
16
16
|
import { ContentControl, collectRunControls } from './ContentControl.mjs';
|
|
@@ -336,7 +336,16 @@ export class Paragraph {
|
|
|
336
336
|
}
|
|
337
337
|
/** Matches within this paragraph. */
|
|
338
338
|
search(text, options) {
|
|
339
|
-
return
|
|
339
|
+
return this.searchMatches(text, options).map((m) => m.range);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* `search`, keeping each match's capture groups, which is what a replacement's `$1` or Word's `\1`
|
|
343
|
+
* expands from (CR-002 section 26). Not an Office JS member; `replaceText` is its only caller here
|
|
344
|
+
* and `Body.replaceText` the other.
|
|
345
|
+
*/
|
|
346
|
+
searchMatches(text, options) {
|
|
347
|
+
return matchesOf(this.text, searchPattern(text, options))
|
|
348
|
+
.map((match) => ({ range: new Range(this, match.index, match.index + match[0].length), match }));
|
|
340
349
|
}
|
|
341
350
|
/**
|
|
342
351
|
* Replaces every match of `find` with `replace`, last match first so that the offsets of the
|
|
@@ -344,9 +353,11 @@ export class Paragraph {
|
|
|
344
353
|
* the package's `changeTrackingMode` is on.
|
|
345
354
|
*/
|
|
346
355
|
replaceText(find, replace, options) {
|
|
347
|
-
const matches = this.
|
|
348
|
-
for (let i = matches.length - 1; i >= 0; i--)
|
|
349
|
-
matches[i]
|
|
356
|
+
const matches = this.searchMatches(find, options);
|
|
357
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
358
|
+
const { range, match } = matches[i];
|
|
359
|
+
range.insertText(expandReplacement(replace, match, options), 'Replace');
|
|
360
|
+
}
|
|
350
361
|
return matches.length;
|
|
351
362
|
}
|
|
352
363
|
/** The tracked changes in this paragraph, in document order (CR-002 phase F). */
|
|
@@ -672,8 +683,8 @@ export class Paragraph {
|
|
|
672
683
|
return commentApi().commentsOf(this);
|
|
673
684
|
}
|
|
674
685
|
/** Comments the whole paragraph (sugar over `getRange().insertComment`). */
|
|
675
|
-
async insertComment(
|
|
676
|
-
return commentApi().insertComment(this.getRange(),
|
|
686
|
+
async insertComment(content, options) {
|
|
687
|
+
return commentApi().insertComment(this.getRange(), content, options);
|
|
677
688
|
}
|
|
678
689
|
// --- content controls (CR-002 phase E) ---
|
|
679
690
|
/**
|
|
@@ -5,6 +5,7 @@ import type { BlockElement } from './Body.mjs';
|
|
|
5
5
|
import { type Element, type TextViewOptions } from './tree.mjs';
|
|
6
6
|
import { ContentControl, type ContentControlType } from './ContentControl.mjs';
|
|
7
7
|
import { type SearchOptions } from './search.mjs';
|
|
8
|
+
import { type CommentContent, type CommentOptions } from './comments.mjs';
|
|
8
9
|
import type { Comment } from './Comment.mjs';
|
|
9
10
|
import type { TrackedChange } from './TrackedChange.mjs';
|
|
10
11
|
/**
|
|
@@ -54,7 +55,11 @@ export declare class Range {
|
|
|
54
55
|
/** Removes the span's text. */
|
|
55
56
|
delete(): void;
|
|
56
57
|
search(text: string, options?: SearchOptions): Range[];
|
|
57
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Replaces every match in this span, last first so the offsets stay valid; returns the count
|
|
60
|
+
* (CR-002 section 3.7). The replacement's `$1` or Word's `\1` expands as it does on a paragraph
|
|
61
|
+
* (section 26; this one was missed there, section 31).
|
|
62
|
+
*/
|
|
58
63
|
replaceText(find: string, replace: string, options?: SearchOptions): number;
|
|
59
64
|
/** The tracked changes this span covers, in document order (CR-002 phase F). */
|
|
60
65
|
getTrackedChanges(): TrackedChange[];
|
|
@@ -68,7 +73,7 @@ export declare class Range {
|
|
|
68
73
|
* boundaries, as `font` does), the comment itself and the side-part entries, creating any of
|
|
69
74
|
* the comment parts the document lacks. Asynchronous because it unmarshals those parts.
|
|
70
75
|
*/
|
|
71
|
-
insertComment(
|
|
76
|
+
insertComment(content: CommentContent, options?: CommentOptions): Promise<Comment>;
|
|
72
77
|
/**
|
|
73
78
|
* Wraps the runs this span covers in a run-level content control (Office JS
|
|
74
79
|
* `Range.insertContentControl`), splitting the runs at the boundaries as `font` does. An empty
|
|
@@ -8,7 +8,7 @@ import { hyperlink as hyperlinkOf } from '@docx4j/generated-objects-ts/el/org_do
|
|
|
8
8
|
import { Namespaces } from '../../parts/Namespaces.mjs';
|
|
9
9
|
import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
|
|
10
10
|
import { contentOf } from './ooxml.mjs';
|
|
11
|
-
import { searchPattern, findAll } from './search.mjs';
|
|
11
|
+
import { searchPattern, findAll, matchesOf, expandReplacement } from './search.mjs';
|
|
12
12
|
import { commentApi } from './comments.mjs';
|
|
13
13
|
/**
|
|
14
14
|
* A subset of Office JS `Word.Range`: a span of text within one paragraph, [start, end) in the
|
|
@@ -151,11 +151,19 @@ export class Range {
|
|
|
151
151
|
const base = this.start;
|
|
152
152
|
return findAll(this.text, searchPattern(text, options)).map(([s, e]) => new Range(this.paragraph, base + s, base + e));
|
|
153
153
|
}
|
|
154
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* Replaces every match in this span, last first so the offsets stay valid; returns the count
|
|
156
|
+
* (CR-002 section 3.7). The replacement's `$1` or Word's `\1` expands as it does on a paragraph
|
|
157
|
+
* (section 26; this one was missed there, section 31).
|
|
158
|
+
*/
|
|
155
159
|
replaceText(find, replace, options) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
const base = this.start;
|
|
161
|
+
const matches = matchesOf(this.text, searchPattern(find, options));
|
|
162
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
163
|
+
const match = matches[i];
|
|
164
|
+
new Range(this.paragraph, base + match.index, base + match.index + match[0].length)
|
|
165
|
+
.insertText(expandReplacement(replace, match, options), 'Replace');
|
|
166
|
+
}
|
|
159
167
|
return matches.length;
|
|
160
168
|
}
|
|
161
169
|
/** The tracked changes this span covers, in document order (CR-002 phase F). */
|
|
@@ -188,8 +196,8 @@ export class Range {
|
|
|
188
196
|
* boundaries, as `font` does), the comment itself and the side-part entries, creating any of
|
|
189
197
|
* the comment parts the document lacks. Asynchronous because it unmarshals those parts.
|
|
190
198
|
*/
|
|
191
|
-
async insertComment(
|
|
192
|
-
return commentApi().insertComment(this,
|
|
199
|
+
async insertComment(content, options) {
|
|
200
|
+
return commentApi().insertComment(this, content, options);
|
|
193
201
|
}
|
|
194
202
|
// --- content controls (CR-002 phase E) ---
|
|
195
203
|
/**
|
|
@@ -259,7 +267,12 @@ export class Range {
|
|
|
259
267
|
const hash = value.indexOf('#');
|
|
260
268
|
const address = hash === -1 ? value : value.slice(0, hash);
|
|
261
269
|
const location = hash === -1 ? '' : value.slice(hash + 1);
|
|
262
|
-
|
|
270
|
+
// w:history="1" is what Word writes on every hyperlink it makes, and what docx4j writes when
|
|
271
|
+
// it builds one (MainDocumentPart.addHyperlink): "add the target to the viewed hyperlinks"
|
|
272
|
+
// (ECMA-376 17.16.22). Readers do not depend on it, but a document holding one holder from
|
|
273
|
+
// each source should not differ over it. Found by the docx4j-ts-editor session, ED-003
|
|
274
|
+
// section 11.8.
|
|
275
|
+
const holder = hyperlinkOf({ content: [], history: true });
|
|
263
276
|
if (location !== '')
|
|
264
277
|
holder.value.anchor = location;
|
|
265
278
|
if (address !== '')
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Jsonix } from '@docx4j/generated-objects-ts';
|
|
2
|
+
/** What an XPath selected, and the tree object behind it (CR-006 section 2). */
|
|
3
|
+
export interface Bound {
|
|
4
|
+
/**
|
|
5
|
+
* The DOM node the expression selected. It is in a snapshot marshalled for this call, so changing
|
|
6
|
+
* it changes nothing: edits go through the content API or `setContents`, never here.
|
|
7
|
+
*/
|
|
8
|
+
node: Node;
|
|
9
|
+
/**
|
|
10
|
+
* The tree object: for an element, what a walk of the typed tree would hand you at that path,
|
|
11
|
+
* never a `{ name, value }` wrapper; for an attribute or a text node, the object of the element
|
|
12
|
+
* that owns it. Undefined for an element the model does not build - a wildcard child is its own
|
|
13
|
+
* object, so this is undefined only above the root or for a node the marshal did not report.
|
|
14
|
+
*/
|
|
15
|
+
object: unknown;
|
|
16
|
+
/** Set when an attribute was selected: its qualified name on `object`'s element. */
|
|
17
|
+
attribute?: string;
|
|
18
|
+
/** The node's path from the part's root, `/w:document/w:body/w:p[3]/w:r[2]`. */
|
|
19
|
+
path: string;
|
|
20
|
+
}
|
|
21
|
+
/** A marshalled snapshot and the element-to-object map recorded while it was written. */
|
|
22
|
+
export interface BoundSnapshot {
|
|
23
|
+
root: Element;
|
|
24
|
+
/** The object each element was marshalled from; a wildcard child maps to the original tree node. */
|
|
25
|
+
objects: WeakMap<Element, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Marshals a root element and records what each DOM element came from. One marshal per call: the
|
|
29
|
+
* tree has no change log a cached snapshot could be checked against, and a stale answer is worse
|
|
30
|
+
* than a slow one (docx4j's `refreshXmlFirst`, always true here). The editor measured 349 to 411 ms
|
|
31
|
+
* for a 255-page main document part, against 975 ms for its first unmarshal, and runs a selection
|
|
32
|
+
* on Enter - so no cache is needed, which is what CR-006 section 2 was uncertain about.
|
|
33
|
+
*/
|
|
34
|
+
export declare function bindSnapshot<T>(element: Jsonix.TypedNamedValue<T>): Promise<BoundSnapshot>;
|
|
35
|
+
/** Turns selected nodes into `Bound`s against a snapshot. */
|
|
36
|
+
export declare function boundsOf(nodes: readonly Node[], snapshot: BoundSnapshot): Bound[];
|
|
37
|
+
/**
|
|
38
|
+
* The node's path from the document element, in the prefixed form the part itself uses, with a
|
|
39
|
+
* position on every step: `/w:document/w:body/w:p[3]/w:r[2]`, and `/@w:val` for an attribute. The
|
|
40
|
+
* position counts siblings of the same qualified name, as XPath does, and is written even when
|
|
41
|
+
* there is only one - a path that is stable whether or not a sibling is added later is worth more
|
|
42
|
+
* than a short one, and docx4j's harness records the same form.
|
|
43
|
+
*/
|
|
44
|
+
export declare function pathOf(node: Node): string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// CR-006: XPath over the WordprocessingML tree, through a read-only binder recorded while marshalling.
|
|
2
|
+
//
|
|
3
|
+
// docx4j answers `getJAXBNodesViaXPath` with a JAXB `Binder<Node>`, which keeps the link between a
|
|
4
|
+
// DOM node and the object it came from in both directions. This runtime has no binder, so CR-002
|
|
5
|
+
// section 3.6 left XPath over the typed tree out; the demand appeared (the editor's Developer
|
|
6
|
+
// perspective selects by XPath) and this is the cheap half - DOM node to object, which is what a
|
|
7
|
+
// selection needs. Nothing is written back through the DOM (CR-006 section 7).
|
|
8
|
+
//
|
|
9
|
+
// The link is recorded by `@docx4j/jsonix` 3.4.0's `onElement` callback, reached through the objects
|
|
10
|
+
// facade's `marshalNode(value, { onElement })` (objects CR-007): the runtime calls it once per
|
|
11
|
+
// class-typed value with the element it was written into, and once per wildcard DOM child with the
|
|
12
|
+
// **imported copy** of that child - `writeNode` does `importNode`, so the element in the snapshot is
|
|
13
|
+
// not the node in the tree and a map keyed on it would miss exactly the content a caller most wants
|
|
14
|
+
// (jsonix-CR-006 section 2, finding 3).
|
|
15
|
+
import { marshalNode } from '@docx4j/generated-objects-ts';
|
|
16
|
+
/**
|
|
17
|
+
* Marshals a root element and records what each DOM element came from. One marshal per call: the
|
|
18
|
+
* tree has no change log a cached snapshot could be checked against, and a stale answer is worse
|
|
19
|
+
* than a slow one (docx4j's `refreshXmlFirst`, always true here). The editor measured 349 to 411 ms
|
|
20
|
+
* for a 255-page main document part, against 975 ms for its first unmarshal, and runs a selection
|
|
21
|
+
* on Enter - so no cache is needed, which is what CR-006 section 2 was uncertain about.
|
|
22
|
+
*/
|
|
23
|
+
export async function bindSnapshot(element) {
|
|
24
|
+
const objects = new WeakMap();
|
|
25
|
+
const root = await marshalNode(element, {
|
|
26
|
+
onElement: (node, value) => {
|
|
27
|
+
if (isElement(node))
|
|
28
|
+
objects.set(node, value);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
return { root, objects };
|
|
32
|
+
}
|
|
33
|
+
/** Turns selected nodes into `Bound`s against a snapshot. */
|
|
34
|
+
export function boundsOf(nodes, snapshot) {
|
|
35
|
+
return nodes.map((node) => {
|
|
36
|
+
const owner = owningElement(node);
|
|
37
|
+
const bound = {
|
|
38
|
+
node,
|
|
39
|
+
object: owner === undefined ? undefined : snapshot.objects.get(owner),
|
|
40
|
+
path: pathOf(node),
|
|
41
|
+
};
|
|
42
|
+
if (node.nodeType === 2)
|
|
43
|
+
bound.attribute = node.name;
|
|
44
|
+
return bound;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The element an XPath result belongs to: itself for an element, its owner for an attribute, its
|
|
49
|
+
* parent for a text, comment or processing-instruction node. A document node has none.
|
|
50
|
+
*/
|
|
51
|
+
function owningElement(node) {
|
|
52
|
+
if (node.nodeType === 1)
|
|
53
|
+
return node;
|
|
54
|
+
if (node.nodeType === 2) {
|
|
55
|
+
const owner = node.ownerElement;
|
|
56
|
+
return owner === null ? undefined : owner;
|
|
57
|
+
}
|
|
58
|
+
const parent = node.parentNode;
|
|
59
|
+
return parent !== null && parent.nodeType === 1 ? parent : undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The node's path from the document element, in the prefixed form the part itself uses, with a
|
|
63
|
+
* position on every step: `/w:document/w:body/w:p[3]/w:r[2]`, and `/@w:val` for an attribute. The
|
|
64
|
+
* position counts siblings of the same qualified name, as XPath does, and is written even when
|
|
65
|
+
* there is only one - a path that is stable whether or not a sibling is added later is worth more
|
|
66
|
+
* than a short one, and docx4j's harness records the same form.
|
|
67
|
+
*/
|
|
68
|
+
export function pathOf(node) {
|
|
69
|
+
if (node.nodeType === 2) {
|
|
70
|
+
const owner = node.ownerElement;
|
|
71
|
+
return `${owner === null ? '' : pathOf(owner)}/@${node.name}`;
|
|
72
|
+
}
|
|
73
|
+
if (node.nodeType === 3 || node.nodeType === 4) {
|
|
74
|
+
const parent = node.parentNode;
|
|
75
|
+
return `${parent === null ? '' : pathOf(parent)}/text()`;
|
|
76
|
+
}
|
|
77
|
+
if (node.nodeType !== 1)
|
|
78
|
+
return '';
|
|
79
|
+
const element = node;
|
|
80
|
+
const parent = element.parentNode;
|
|
81
|
+
if (parent === null || parent.nodeType !== 1)
|
|
82
|
+
return `/${element.nodeName}`;
|
|
83
|
+
let index = 0;
|
|
84
|
+
for (let sibling = parent.firstChild; sibling; sibling = sibling.nextSibling) {
|
|
85
|
+
if (sibling.nodeType === 1 && sibling.nodeName === element.nodeName) {
|
|
86
|
+
index++;
|
|
87
|
+
if (sibling === element)
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return `${pathOf(parent)}/${element.nodeName}[${index}]`;
|
|
92
|
+
}
|
|
93
|
+
function isElement(node) {
|
|
94
|
+
return typeof node === 'object' && node !== null && node.nodeType === 1;
|
|
95
|
+
}
|
|
@@ -16,6 +16,24 @@ export declare const COMMENT_REFERENCE_STYLE = "CommentReference";
|
|
|
16
16
|
* Who a comment (and, with phase F, a tracked change) is by. `WordprocessingMLPackage.author`;
|
|
17
17
|
* there is no signed-in user here, so the package carries the identity.
|
|
18
18
|
*/
|
|
19
|
+
/**
|
|
20
|
+
* What a comment is made of: the text, or the paragraphs of a comment that was edited rich
|
|
21
|
+
* (CR-002 section 28.3). A string splits on newlines, as it did before this option existed.
|
|
22
|
+
*/
|
|
23
|
+
export type CommentContent = string | Element<wml.P>[];
|
|
24
|
+
/**
|
|
25
|
+
* Who made a comment and when, and its `w:id`, for a caller that knows better than the package does:
|
|
26
|
+
* in a co-editing room the author is the peer who made it and the date is when they made it
|
|
27
|
+
* (CR-002 section 28.3). Anything omitted falls back to what the package would have used.
|
|
28
|
+
*/
|
|
29
|
+
export interface CommentOptions {
|
|
30
|
+
author?: Author | undefined;
|
|
31
|
+
/** Overrides the initials derived from the author's name. */
|
|
32
|
+
initials?: string | undefined;
|
|
33
|
+
date?: Date | undefined;
|
|
34
|
+
/** Checked unused across the comments part and the story's markers; a clash throws. */
|
|
35
|
+
id?: number | undefined;
|
|
36
|
+
}
|
|
19
37
|
export interface Author {
|
|
20
38
|
name: string;
|
|
21
39
|
/** Word's `w:initials`; derived from the name when not given. */
|
|
@@ -69,7 +87,7 @@ export declare function commentPartsAccess(): CommentPartsAccess;
|
|
|
69
87
|
/** How `Body`, `Paragraph` and `Range` reach the `Comment` views; registered by `Comment.mts`. */
|
|
70
88
|
export interface CommentApi {
|
|
71
89
|
commentsOf(scope: Body | Paragraph | Range): Promise<Comment[]>;
|
|
72
|
-
insertComment(range: Range,
|
|
90
|
+
insertComment(range: Range, content: CommentContent, options?: CommentOptions): Promise<Comment>;
|
|
73
91
|
}
|
|
74
92
|
export declare function setCommentApi(implementation: CommentApi): void;
|
|
75
93
|
export declare function commentApi(): CommentApi;
|
|
@@ -4,14 +4,16 @@ export { Range } from './Range.mjs';
|
|
|
4
4
|
export { BUILT_IN_STYLES, builtInOf, idOfBuiltIn, displayNameOf, styleNameOf, styleIdOf, type BuiltInStyle } from './styles.mjs';
|
|
5
5
|
export { Comment } 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 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 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';
|
|
11
11
|
export { ContentControl, collectControls, collectRunControls, W14_NS, W15_NS, type ContentControlType, type ContentControlForm, type ContentControlAppearance } from './ContentControl.mjs';
|
|
12
12
|
export { contentOf, isFlatOpc, rewriteRelationshipIds, type OoxmlOptions } from './ooxml.mjs';
|
|
13
|
-
export { type SearchOptions, searchPattern } from './search.mjs';
|
|
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, } from './tracking.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';
|
|
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
|
+
export { StylesFacade, type StylePackageLike } from './stylesFacade.mjs';
|
|
19
|
+
export { bindSnapshot, boundsOf, pathOf, type Bound, type BoundSnapshot } from './binder.mjs';
|
|
@@ -12,10 +12,12 @@ export { Table, TableRow, TableCell, cellOf } from './Table.mjs';
|
|
|
12
12
|
export { InlinePicture, imageInfoOf, naturalSizeEmu, addImage, writableWidthEmu } from './InlinePicture.mjs';
|
|
13
13
|
export { ContentControl, collectControls, collectRunControls, W14_NS, W15_NS } from './ContentControl.mjs';
|
|
14
14
|
export { contentOf, isFlatOpc, rewriteRelationshipIds } from './ooxml.mjs';
|
|
15
|
-
export { searchPattern } from './search.mjs';
|
|
15
|
+
export { searchPattern, findAll, matchesOf, expandReplacement } from './search.mjs';
|
|
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
19
|
export { ChangeTracker, trackerOf, calendarOf, dateOf, copyRPr, markDeleted, markInserted, 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
|
+
export { StylesFacade } from './stylesFacade.mjs';
|
|
23
|
+
export { bindSnapshot, boundsOf, pathOf } from './binder.mjs';
|
|
@@ -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
|
+
}
|