@docx4j/core-ts 0.3.2 → 0.5.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 +8 -3
- package/dist/html/convert.d.mts +116 -0
- package/dist/html/convert.mjs +565 -0
- package/dist/html/elements.d.mts +19 -0
- package/dist/html/elements.mjs +182 -0
- package/dist/html/index.d.mts +2 -0
- package/dist/html/index.mjs +4 -0
- package/dist/model/content/Body.d.mts +13 -1
- package/dist/model/content/Body.mjs +97 -19
- package/dist/model/content/Comment.mjs +7 -0
- package/dist/model/content/InlinePicture.d.mts +89 -10
- package/dist/model/content/InlinePicture.mjs +245 -35
- package/dist/model/content/List.mjs +10 -1
- package/dist/model/content/Paragraph.d.mts +28 -5
- package/dist/model/content/Paragraph.mjs +77 -37
- package/dist/model/content/Range.d.mts +18 -2
- package/dist/model/content/Range.mjs +194 -64
- package/dist/model/content/Table.d.mts +38 -0
- package/dist/model/content/Table.mjs +80 -2
- package/dist/model/content/TrackedChange.d.mts +129 -3
- package/dist/model/content/TrackedChange.mjs +577 -12
- package/dist/model/content/binder.d.mts +2 -1
- package/dist/model/content/binder.mjs +48 -1
- package/dist/model/content/index.d.mts +4 -3
- package/dist/model/content/index.mjs +3 -2
- package/dist/model/content/relationships.d.mts +30 -0
- package/dist/model/content/relationships.mjs +133 -0
- package/dist/model/content/stylesFacade.d.mts +4 -2
- package/dist/model/content/stylesFacade.mjs +20 -6
- package/dist/model/content/tableColumns.d.mts +48 -0
- package/dist/model/content/tableColumns.mjs +484 -0
- package/dist/model/content/tree.d.mts +52 -2
- package/dist/model/content/tree.mjs +48 -3
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
- package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
- package/dist/model/customxml/bindings.d.mts +5 -2
- package/dist/model/customxml/bindings.mjs +21 -6
- package/dist/model/customxml/index.d.mts +3 -0
- package/dist/model/customxml/index.mjs +3 -0
- package/dist/model/customxml/opendope.d.mts +32 -0
- package/dist/model/customxml/opendope.mjs +56 -0
- package/dist/model/customxml/pictures.d.mts +57 -0
- package/dist/model/customxml/pictures.mjs +192 -0
- package/dist/model/customxml/xhtml.d.mts +42 -0
- package/dist/model/customxml/xhtml.mjs +162 -0
- package/dist/model/fonts/families.generated.mjs +2 -2
- package/dist/model/fonts/registry.mjs +7 -1
- package/dist/model/fonts/substitutions.generated.mjs +2 -2
- package/dist/model/fonts/themes.generated.mjs +1 -1
- package/dist/model/listnumbering/definitions.mjs +18 -0
- package/dist/model/properties/PropertyResolver.d.mts +27 -6
- package/dist/model/properties/PropertyResolver.mjs +83 -16
- package/dist/model/properties/catalogue.d.mts +15 -1
- package/dist/model/properties/catalogue.mjs +80 -27
- package/dist/model/properties/table.d.mts +16 -2
- package/dist/model/properties/table.mjs +13 -3
- package/dist/opc/Load.d.mts +9 -2
- package/dist/opc/index.d.mts +2 -2
- package/dist/opc/index.mjs +2 -2
- package/dist/opc/mce/preprocessor.d.mts +62 -6
- package/dist/opc/mce/preprocessor.mjs +142 -35
- package/dist/opc/mce/understood.d.mts +10 -0
- package/dist/opc/mce/understood.mjs +25 -3
- package/dist/packages/OpcPackage.d.mts +7 -0
- package/dist/packages/OpcPackage.mjs +15 -3
- package/dist/packages/WordprocessingMLPackage.d.mts +35 -2
- package/dist/packages/WordprocessingMLPackage.mjs +99 -2
- package/dist/parts/XmlPart.d.mts +26 -5
- package/dist/parts/XmlPart.mjs +67 -18
- package/dist/parts/wml/knownStyles.d.mts +1 -0
- package/dist/parts/wml/knownStyles.mjs +2233 -0
- package/package.json +7 -2
|
@@ -5,6 +5,7 @@ import { CustomXmlPart, type CustomXmlPartOwner } from './CustomXmlPart.mjs';
|
|
|
5
5
|
import type { CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
6
6
|
import type { XPathEngine } from './xpath.mjs';
|
|
7
7
|
import { type BindingResult } from './bindings.mjs';
|
|
8
|
+
import { type XhtmlHost, type XhtmlBindingOptions } from './xhtml.mjs';
|
|
8
9
|
/**
|
|
9
10
|
* What the collection needs of its package. A structural interface, so that this module does not
|
|
10
11
|
* import `WordprocessingMLPackage` (which imports it): the package hands itself in.
|
|
@@ -18,6 +19,8 @@ export interface CustomXmlHost {
|
|
|
18
19
|
boundBodies(): Body[];
|
|
19
20
|
/** Where a new custom XML part's relationship goes: the main document part (Word drops parts related from the package alone). */
|
|
20
21
|
customXmlRelationshipSource(): Part;
|
|
22
|
+
/** The package, for the XHTML pass (list definitions, styles, relationships); absent, XHTML controls are left and noted. */
|
|
23
|
+
readonly xhtmlHost?: XhtmlHost;
|
|
21
24
|
}
|
|
22
25
|
/** A subset of Office JS `Word.CustomXmlPartCollection`. */
|
|
23
26
|
export declare class CustomXmlPartCollection implements CustomXmlPartOwner, CustomXmlPartLookup {
|
|
@@ -62,7 +65,9 @@ export declare class CustomXmlPartCollection implements CustomXmlPartOwner, Cust
|
|
|
62
65
|
* which is what Word does when it opens the document. Unmarshals the main document part (and the
|
|
63
66
|
* headers and footers it has) and parses the custom XML parts.
|
|
64
67
|
*/
|
|
65
|
-
applyBindings(
|
|
68
|
+
applyBindings(options?: {
|
|
69
|
+
html?: XhtmlBindingOptions;
|
|
70
|
+
}): Promise<BindingResult>;
|
|
66
71
|
/**
|
|
67
72
|
* docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
|
|
68
73
|
* back into the custom XML. The reverse of `applyBindings`, and what makes an edit to a bound
|
|
@@ -14,6 +14,8 @@ import { parseXml, decodeXmlText } from '../../xml/dom.mjs';
|
|
|
14
14
|
import { createDatastoreItem } from '@docx4j/generated-objects-ts/factory/org_docx4j_customXmlProperties';
|
|
15
15
|
import { CustomXmlPart } from './CustomXmlPart.mjs';
|
|
16
16
|
import { applyBindingsTo, updateFromControls } from './bindings.mjs';
|
|
17
|
+
import { applyXhtmlBindingsTo, isXhtmlBound } from './xhtml.mjs';
|
|
18
|
+
import { applyPictureHandlersTo, updateFromPictureControls } from './pictures.mjs';
|
|
17
19
|
/** A subset of Office JS `Word.CustomXmlPartCollection`. */
|
|
18
20
|
export class CustomXmlPartCollection {
|
|
19
21
|
constructor(host) {
|
|
@@ -133,9 +135,19 @@ export class CustomXmlPartCollection {
|
|
|
133
135
|
* which is what Word does when it opens the document. Unmarshals the main document part (and the
|
|
134
136
|
* headers and footers it has) and parses the custom XML parts.
|
|
135
137
|
*/
|
|
136
|
-
async applyBindings() {
|
|
138
|
+
async applyBindings(options = {}) {
|
|
139
|
+
var _a, _b;
|
|
137
140
|
await this.load();
|
|
138
|
-
|
|
141
|
+
const controls = this.controlsOf(await this.host.getBoundBodies());
|
|
142
|
+
const result = applyBindingsTo(controls);
|
|
143
|
+
// od:Handler=picture through the tag's od:xpath entry (CR-005 section 9): after the text bindings, which took the picture controls.
|
|
144
|
+
applyPictureHandlersTo(controls, this, result);
|
|
145
|
+
// Escaped XHTML through the tag's od:xpath entry (CR-005 section 8.2): after the text bindings, asynchronous.
|
|
146
|
+
if (this.host.xhtmlHost)
|
|
147
|
+
await applyXhtmlBindingsTo(controls, this, this.host.xhtmlHost, result, (_a = options.html) !== null && _a !== void 0 ? _a : {});
|
|
148
|
+
else if (controls.some(isXhtmlBound))
|
|
149
|
+
((_b = result.notes) !== null && _b !== void 0 ? _b : (result.notes = [])).push('XHTML-bound controls left: the collection has no package to make their lists, styles and links');
|
|
150
|
+
return result;
|
|
139
151
|
}
|
|
140
152
|
/**
|
|
141
153
|
* docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
|
|
@@ -144,7 +156,11 @@ export class CustomXmlPartCollection {
|
|
|
144
156
|
*/
|
|
145
157
|
async updateFromContentControls() {
|
|
146
158
|
await this.load();
|
|
147
|
-
|
|
159
|
+
const controls = this.controlsOf(await this.host.getBoundBodies());
|
|
160
|
+
const result = updateFromControls(controls);
|
|
161
|
+
// a mapped picture control's image back to its node, as Word 15 writes it (CR-005 section 9, check 38)
|
|
162
|
+
await updateFromPictureControls(controls, result);
|
|
163
|
+
return result;
|
|
148
164
|
}
|
|
149
165
|
controlsOf(bodies) {
|
|
150
166
|
return bodies.flatMap((body) => body.contentControls);
|
|
@@ -12,6 +12,8 @@ export interface BindingResult {
|
|
|
12
12
|
updated: number;
|
|
13
13
|
/** Controls left alone: the part is missing, the XPath selects nothing, or the kind is not handled. */
|
|
14
14
|
skipped: number;
|
|
15
|
+
/** What the XHTML and picture passes left or dropped, one line each (CR-005 sections 8.2 and 9; REQ-076). */
|
|
16
|
+
notes?: string[];
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
|
|
@@ -32,9 +34,10 @@ export declare function formatDate(pattern: string, date: Date, locale?: string)
|
|
|
32
34
|
/**
|
|
33
35
|
* Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
|
|
34
36
|
* false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
|
|
35
|
-
* this phase does not handle (
|
|
37
|
+
* this phase does not handle (explicit rich text), a node holding a Flat OPC package, or a
|
|
38
|
+
* picture control whose node holds no image (`notes` says which; CR-005 section 9).
|
|
36
39
|
*/
|
|
37
|
-
export declare function applyBindingTo(control: ContentControl): boolean;
|
|
40
|
+
export declare function applyBindingTo(control: ContentControl, notes?: string[]): boolean;
|
|
38
41
|
/** `applyBindings` over a list of controls. */
|
|
39
42
|
export declare function applyBindingsTo(controls: ContentControl[]): BindingResult;
|
|
40
43
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { deepCopy } from '@docx4j/generated-objects-ts';
|
|
2
2
|
import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
|
|
3
3
|
import { sdtProperty } from '@docx4j/generated-objects-ts/builders/wml';
|
|
4
|
+
import { bindPicture } from './pictures.mjs';
|
|
4
5
|
/** The style Word gives placeholder text, and the text docx4j's placeholder.xml carries. */
|
|
5
6
|
export const PLACEHOLDER_STYLE = 'PlaceholderText';
|
|
6
7
|
export const PLACEHOLDER_TEXT = 'Click here to enter text.';
|
|
@@ -114,9 +115,10 @@ function dateOf(value) {
|
|
|
114
115
|
/**
|
|
115
116
|
* Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
|
|
116
117
|
* false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
|
|
117
|
-
* this phase does not handle (
|
|
118
|
+
* this phase does not handle (explicit rich text), a node holding a Flat OPC package, or a
|
|
119
|
+
* picture control whose node holds no image (`notes` says which; CR-005 section 9).
|
|
118
120
|
*/
|
|
119
|
-
export function applyBindingTo(control) {
|
|
121
|
+
export function applyBindingTo(control, notes) {
|
|
120
122
|
var _a;
|
|
121
123
|
const mapping = control.xmlMapping;
|
|
122
124
|
if (!mapping.isMapped)
|
|
@@ -125,9 +127,14 @@ export function applyBindingTo(control) {
|
|
|
125
127
|
if (!node)
|
|
126
128
|
return false;
|
|
127
129
|
const value = node.text.trim();
|
|
128
|
-
// A picture
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
// A picture control's node holds base64 image data: the picture's a:blip is pointed at a new
|
|
131
|
+
// image part and the drawing kept, as docx4j's bind.xslt mode picture3 does (CR-005 section 9).
|
|
132
|
+
if (control.type === 'Picture') {
|
|
133
|
+
const outcome = bindPicture(control, value, { kind: 'keep' }, control.title || control.tag || `picture control ${control.id}`);
|
|
134
|
+
if (outcome.note)
|
|
135
|
+
notes === null || notes === void 0 ? void 0 : notes.push(outcome.note);
|
|
136
|
+
return outcome.done;
|
|
137
|
+
}
|
|
131
138
|
// Explicit rich text is bound from flat OPC or XHTML in docx4j; deferred, as it is there for this route.
|
|
132
139
|
if (sdtProperty(control.sdt.sdtPr, 'richText') !== undefined)
|
|
133
140
|
return false;
|
|
@@ -169,15 +176,18 @@ export function applyBindingTo(control) {
|
|
|
169
176
|
/** `applyBindings` over a list of controls. */
|
|
170
177
|
export function applyBindingsTo(controls) {
|
|
171
178
|
const result = { bound: 0, updated: 0, skipped: 0 };
|
|
179
|
+
const notes = [];
|
|
172
180
|
for (const control of controls) {
|
|
173
181
|
if (!control.xmlMapping.isMapped)
|
|
174
182
|
continue;
|
|
175
183
|
result.bound++;
|
|
176
|
-
if (applyBindingTo(control))
|
|
184
|
+
if (applyBindingTo(control, notes))
|
|
177
185
|
result.updated++;
|
|
178
186
|
else
|
|
179
187
|
result.skipped++;
|
|
180
188
|
}
|
|
189
|
+
if (notes.length > 0)
|
|
190
|
+
result.notes = notes;
|
|
181
191
|
return result;
|
|
182
192
|
}
|
|
183
193
|
/**
|
|
@@ -194,6 +204,8 @@ export function updateFromControl(control) {
|
|
|
194
204
|
const node = mapping.customXmlNode;
|
|
195
205
|
if (!node)
|
|
196
206
|
return false;
|
|
207
|
+
// A picture control is the asynchronous pass's (updateFromPictureControl, CR-005 section 9): its
|
|
208
|
+
// image's bytes go back to the node as base64, as Word 15 writes them (check 38).
|
|
197
209
|
if (control.type === 'Picture')
|
|
198
210
|
return false;
|
|
199
211
|
if (sdtProperty(control.sdt.sdtPr, 'richText') !== undefined)
|
|
@@ -236,6 +248,9 @@ export function updateFromControls(controls) {
|
|
|
236
248
|
for (const control of controls) {
|
|
237
249
|
if (!control.xmlMapping.isMapped)
|
|
238
250
|
continue;
|
|
251
|
+
// counted by the picture pass, which reads the image part (CR-005 section 9)
|
|
252
|
+
if (control.type === 'Picture')
|
|
253
|
+
continue;
|
|
239
254
|
result.bound++;
|
|
240
255
|
if (updateFromControl(control))
|
|
241
256
|
result.updated++;
|
|
@@ -5,3 +5,6 @@ export { XmlMapping, type CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
|
5
5
|
export { CheckboxContentControl, DatePickerContentControl, ListContentControl, ContentControlListItem, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKED_SYMBOL, UNCHECKED_SYMBOL, CHECKBOX_FONT, } from './kinds.mjs';
|
|
6
6
|
export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, holdsFlatOpc, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT, type BindingResult } from './bindings.mjs';
|
|
7
7
|
export { controlIdScope, sdtKindFor, isRunLevel } from './insert.mjs';
|
|
8
|
+
export { tagParamsOf, xpathsEntriesOf, selectEntryNode, OPENDOPE_XPATHS_NS, XHTML_CONTENT_TYPE, PICTURE_HANDLER, type XPathsEntry } from './opendope.mjs';
|
|
9
|
+
export { applyXhtmlBindingTo, applyXhtmlBindingsTo, isXhtmlBound, type XhtmlHost, type XhtmlBindingOptions } from './xhtml.mjs';
|
|
10
|
+
export { applyPictureHandlerTo, applyPictureHandlersTo, isPictureHandlerBound, bindPicture, pictureWidthOf, updateFromPictureControl, updateFromPictureControls, type PictureWidth, type PictureOutcome } from './pictures.mjs';
|
|
@@ -7,3 +7,6 @@ export { XmlMapping } from './XmlMapping.mjs';
|
|
|
7
7
|
export { CheckboxContentControl, DatePickerContentControl, ListContentControl, ContentControlListItem, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKED_SYMBOL, UNCHECKED_SYMBOL, CHECKBOX_FONT, } from './kinds.mjs';
|
|
8
8
|
export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, holdsFlatOpc, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT } from './bindings.mjs';
|
|
9
9
|
export { controlIdScope, sdtKindFor, isRunLevel } from './insert.mjs';
|
|
10
|
+
export { tagParamsOf, xpathsEntriesOf, selectEntryNode, OPENDOPE_XPATHS_NS, XHTML_CONTENT_TYPE, PICTURE_HANDLER } from './opendope.mjs';
|
|
11
|
+
export { applyXhtmlBindingTo, applyXhtmlBindingsTo, isXhtmlBound } from './xhtml.mjs';
|
|
12
|
+
export { applyPictureHandlerTo, applyPictureHandlersTo, isPictureHandlerBound, bindPicture, pictureWidthOf, updateFromPictureControl, updateFromPictureControls } from './pictures.mjs';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
2
|
+
import type { CustomXmlNode } from './CustomXmlPart.mjs';
|
|
3
|
+
export declare const OPENDOPE_XPATHS_NS = "http://opendope.org/xpaths";
|
|
4
|
+
export declare const XHTML_CONTENT_TYPE = "application/xhtml+xml";
|
|
5
|
+
/** The tag's `od:Handler` value for a picture (the specification's section 9.3; CR-005 section 9). */
|
|
6
|
+
export declare const PICTURE_HANDLER = "picture";
|
|
7
|
+
/** The tag's parameters, `key=value` pairs separated by `&`, in order; a value is as written (the content type keeps its `/` and `+`). */
|
|
8
|
+
export declare function tagParamsOf(tag: string): Map<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* An XPaths part entry: `<xpath id="x1"><dataBinding storeItemID xpath prefixMappings/></xpath>`.
|
|
11
|
+
* The attributes are unqualified (`xpaths.xsd`: `attributeFormDefault="unqualified"`; docx4j's
|
|
12
|
+
* `org.opendope.xpaths.Xpaths` and its invoice.docx write them so); a `w:`-qualified form, which
|
|
13
|
+
* this package's own check 37 and 38 files were made with, is read as well.
|
|
14
|
+
*/
|
|
15
|
+
export interface XPathsEntry {
|
|
16
|
+
id: string;
|
|
17
|
+
storeItemID: string;
|
|
18
|
+
xpath: string;
|
|
19
|
+
prefixMappings: string;
|
|
20
|
+
}
|
|
21
|
+
/** The XPaths part's entries by id, from the first custom XML part in the OpenDoPE XPaths namespace; empty without one. */
|
|
22
|
+
export declare function xpathsEntriesOf(parts: CustomXmlPartLookup): Map<string, XPathsEntry>;
|
|
23
|
+
/**
|
|
24
|
+
* The node an `od:xpath` entry selects: the entry's XPath over the part its `storeItemID` names
|
|
25
|
+
* (the id compared without braces or case, as `getItem` does). What a tag-bound control's pass
|
|
26
|
+
* starts from; the note says why there is no node.
|
|
27
|
+
*/
|
|
28
|
+
export declare function selectEntryNode(entryId: string, parts: CustomXmlPartLookup): {
|
|
29
|
+
node: CustomXmlNode;
|
|
30
|
+
} | {
|
|
31
|
+
note: string;
|
|
32
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const OPENDOPE_XPATHS_NS = 'http://opendope.org/xpaths';
|
|
2
|
+
export const XHTML_CONTENT_TYPE = 'application/xhtml+xml';
|
|
3
|
+
/** The tag's `od:Handler` value for a picture (the specification's section 9.3; CR-005 section 9). */
|
|
4
|
+
export const PICTURE_HANDLER = 'picture';
|
|
5
|
+
/** The tag's parameters, `key=value` pairs separated by `&`, in order; a value is as written (the content type keeps its `/` and `+`). */
|
|
6
|
+
export function tagParamsOf(tag) {
|
|
7
|
+
const out = new Map();
|
|
8
|
+
for (const pair of tag.split('&')) {
|
|
9
|
+
const eq = pair.indexOf('=');
|
|
10
|
+
if (eq <= 0)
|
|
11
|
+
continue;
|
|
12
|
+
const key = pair.slice(0, eq).trim();
|
|
13
|
+
if (!out.has(key))
|
|
14
|
+
out.set(key, pair.slice(eq + 1).trim());
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
/** The XPaths part's entries by id, from the first custom XML part in the OpenDoPE XPaths namespace; empty without one. */
|
|
19
|
+
export function xpathsEntriesOf(parts) {
|
|
20
|
+
const out = new Map();
|
|
21
|
+
const part = parts.items.find((p) => p.namespaceUri === OPENDOPE_XPATHS_NS);
|
|
22
|
+
if (!part)
|
|
23
|
+
return out;
|
|
24
|
+
const root = part.document.documentElement;
|
|
25
|
+
if (!root)
|
|
26
|
+
return out;
|
|
27
|
+
const entries = root.getElementsByTagNameNS(OPENDOPE_XPATHS_NS, 'xpath');
|
|
28
|
+
for (let i = 0; i < entries.length; i++) {
|
|
29
|
+
const entry = entries[i];
|
|
30
|
+
const id = entry.getAttribute('id');
|
|
31
|
+
const binding = entry.getElementsByTagNameNS(OPENDOPE_XPATHS_NS, 'dataBinding')[0];
|
|
32
|
+
if (!id || !binding)
|
|
33
|
+
continue;
|
|
34
|
+
const read = (name) => binding.getAttribute(name) || binding.getAttributeNS('http://schemas.openxmlformats.org/wordprocessingml/2006/main', name) || binding.getAttribute(`w:${name}`) || '';
|
|
35
|
+
out.set(id, { id, storeItemID: read('storeItemID'), xpath: read('xpath'), prefixMappings: read('prefixMappings') });
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The node an `od:xpath` entry selects: the entry's XPath over the part its `storeItemID` names
|
|
41
|
+
* (the id compared without braces or case, as `getItem` does). What a tag-bound control's pass
|
|
42
|
+
* starts from; the note says why there is no node.
|
|
43
|
+
*/
|
|
44
|
+
export function selectEntryNode(entryId, parts) {
|
|
45
|
+
var _a;
|
|
46
|
+
const entry = xpathsEntriesOf(parts).get(entryId);
|
|
47
|
+
if (!entry)
|
|
48
|
+
return { note: `od:xpath=${entryId}: no such entry in the XPaths part` };
|
|
49
|
+
const part = (_a = parts.getItem(entry.storeItemID)) !== null && _a !== void 0 ? _a : parts.items.find((p) => p.id.replace(/[{}]/g, '').toLowerCase() === entry.storeItemID.replace(/[{}]/g, '').toLowerCase());
|
|
50
|
+
if (!part)
|
|
51
|
+
return { note: `od:xpath=${entryId}: no custom XML part ${entry.storeItemID}` };
|
|
52
|
+
const node = part.selectSingleNode(entry.xpath, entry.prefixMappings);
|
|
53
|
+
if (!node)
|
|
54
|
+
return { note: `od:xpath=${entryId}: ${entry.xpath} selects nothing` };
|
|
55
|
+
return { node };
|
|
56
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ContentControl } from '../content/ContentControl.mjs';
|
|
2
|
+
import type { CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
3
|
+
import type { BindingResult } from './bindings.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* What the tag's `width` says: absent keeps the control's drawing and replaces its image; `auto`
|
|
6
|
+
* a new inline picture at its natural size, scaled down to the text width; `N` the same, scaled
|
|
7
|
+
* down to N twips when that is narrower than the text width (a value that is neither is `auto`,
|
|
8
|
+
* which is what docx4j does with one it cannot parse).
|
|
9
|
+
*/
|
|
10
|
+
export type PictureWidth = {
|
|
11
|
+
kind: 'keep';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'auto';
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'twips';
|
|
16
|
+
twips: number;
|
|
17
|
+
};
|
|
18
|
+
export interface PictureOutcome {
|
|
19
|
+
done: boolean;
|
|
20
|
+
/** Why the control was left as it was. */
|
|
21
|
+
note?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Whether a control is bound to a picture through its tag (`od:Handler=picture` with an `od:xpath`). */
|
|
24
|
+
export declare function isPictureHandlerBound(control: ContentControl): boolean;
|
|
25
|
+
/** The tag's `width` parameter, read as `PictureWidth` says. */
|
|
26
|
+
export declare function pictureWidthOf(params: Map<string, string>): PictureWidth;
|
|
27
|
+
/**
|
|
28
|
+
* Binds one control to a value: base64 image data (a data URL accepted). With `keep`, the first
|
|
29
|
+
* `a:blip` is pointed at a new image part and the drawing is left as it is, as Word binds a picture
|
|
30
|
+
* control (`setBlipImage`: a raster image in `r:embed`, the `asvg:svgBlip` taken away; an SVG under
|
|
31
|
+
* the `asvg:svgBlip`, `r:embed` taken away; the effects kept; CR-002 section 52); a picture
|
|
32
|
+
* control holding no picture gets a new inline one at its natural size instead (docx4j's pre-v3
|
|
33
|
+
* fallback), a tag-bound control holding none is left and noted. Otherwise the control's content
|
|
34
|
+
* becomes one run holding a new inline picture, sized as `PictureWidth` says, in the first
|
|
35
|
+
* paragraph of a block-level control (its properties kept) or as a run-level control's content.
|
|
36
|
+
* An empty value, a value that is not an image this package reads (PNG, JPEG, GIF, BMP; SVG where
|
|
37
|
+
* the drawing is kept, since it has no pixel size to place a new picture at), and a body with no
|
|
38
|
+
* part each leave the control as it was, with a note. The image part the control showed before
|
|
39
|
+
* stays in the package, as docx4j leaves it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function bindPicture(control: ContentControl, value: string, width: PictureWidth, label: string): PictureOutcome;
|
|
42
|
+
/** Binds one tag-bound control (shape b): the entry's node, the tag's width. */
|
|
43
|
+
export declare function applyPictureHandlerTo(control: ContentControl, parts: CustomXmlPartLookup): PictureOutcome;
|
|
44
|
+
/** The picture pass over the tag-bound controls, after the text bindings: counted into the result, what was left said in its notes. */
|
|
45
|
+
export declare function applyPictureHandlersTo(controls: ContentControl[], parts: CustomXmlPartLookup, result: BindingResult): void;
|
|
46
|
+
/**
|
|
47
|
+
* The reverse direction for a mapped picture control (docx4j's `UpdateXmlFromDocumentSurface`
|
|
48
|
+
* skips pictures; Word 15 writes the picture back, check 38): the bytes of the image part the
|
|
49
|
+
* first `a:blip` shows, as base64, into the node: its `asvg:svgBlip`'s SVG where it has one, as
|
|
50
|
+
* `InlinePicture.getBase64` gives (CR-002 section 52), else its `r:embed`'s image. Nothing is written when the node already holds
|
|
51
|
+
* those bytes (compared decoded, so Word's line-wrapped base64 counts as the same), when the
|
|
52
|
+
* control shows no picture, or when the blip's part is not an image part. Asynchronous, since a
|
|
53
|
+
* part's bytes may still be in the container.
|
|
54
|
+
*/
|
|
55
|
+
export declare function updateFromPictureControl(control: ContentControl): Promise<boolean>;
|
|
56
|
+
/** `updateFromContentControls`' picture pass: the mapped picture controls, counted into the result. */
|
|
57
|
+
export declare function updateFromPictureControls(controls: ContentControl[], result: BindingResult): Promise<void>;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { find } from '../content/tree.mjs';
|
|
2
|
+
import { addImage, addImagePart, writableWidthEmu, setBlipImage, svgBlipOf } from '../content/InlinePicture.mjs';
|
|
3
|
+
import { ContentTypes } from '../../opc/ContentTypes.mjs';
|
|
4
|
+
import { ImagePart } from '../../parts/BinaryPart.mjs';
|
|
5
|
+
import { base64Decode, base64Encode } from '../../xml/dom.mjs';
|
|
6
|
+
import { enclosingCellOf } from '../properties/table.mjs';
|
|
7
|
+
import { tagParamsOf, selectEntryNode, PICTURE_HANDLER } from './opendope.mjs';
|
|
8
|
+
const EMU_PER_TWIP = 635;
|
|
9
|
+
const BLIP = 'org_docx4j_dml.CTBlip';
|
|
10
|
+
/** Whether a control is bound to a picture through its tag (`od:Handler=picture` with an `od:xpath`). */
|
|
11
|
+
export function isPictureHandlerBound(control) {
|
|
12
|
+
const params = tagParamsOf(control.tag);
|
|
13
|
+
return params.get('od:Handler') === PICTURE_HANDLER && params.has('od:xpath');
|
|
14
|
+
}
|
|
15
|
+
/** The tag's `width` parameter, read as `PictureWidth` says. */
|
|
16
|
+
export function pictureWidthOf(params) {
|
|
17
|
+
const width = params.get('width');
|
|
18
|
+
if (width === undefined)
|
|
19
|
+
return { kind: 'keep' };
|
|
20
|
+
const twips = /^\d+$/.test(width.trim()) ? Number(width) : 0;
|
|
21
|
+
return twips > 0 ? { kind: 'twips', twips } : { kind: 'auto' };
|
|
22
|
+
}
|
|
23
|
+
/** The node's value as base64: a data URL's prefix dropped, whitespace left to the lenient decoder. */
|
|
24
|
+
function base64Of(value) {
|
|
25
|
+
return value.replace(/^\s*data:[^,]*,/, '').trim();
|
|
26
|
+
}
|
|
27
|
+
function messageOf(e) {
|
|
28
|
+
return e instanceof Error ? e.message : String(e);
|
|
29
|
+
}
|
|
30
|
+
/** The first `a:blip` anywhere in the control's content, whose `r:embed` is the image. */
|
|
31
|
+
function firstBlipOf(control) {
|
|
32
|
+
var _a;
|
|
33
|
+
for (const blip of find((_a = control.sdt.sdtContent) !== null && _a !== void 0 ? _a : {}, BLIP))
|
|
34
|
+
return blip;
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
/** A width stated in twips (`dxa`, or no type), else undefined. */
|
|
38
|
+
function twipsOf(width) {
|
|
39
|
+
return width && typeof width.w === 'number' && width.w >= 0 && (width.type === undefined || width.type === 'dxa') ? width.w : undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Word's default cell margins, 108 twips left and right (the `Normal Table` style's
|
|
43
|
+
* `w:tblCellMar`, which every table inherits), taken where neither the cell nor the table states
|
|
44
|
+
* one; a table style stating other margins is not read.
|
|
45
|
+
*/
|
|
46
|
+
const DEFAULT_CELL_MARGIN_TWIPS = 108;
|
|
47
|
+
/** The cell's left and right margins together, in twips: `w:tcMar`, else the table's `w:tblCellMar`, else Word's defaults. */
|
|
48
|
+
function cellMarginsTwips(tc, tbl) {
|
|
49
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
50
|
+
const own = (_a = tc.tcPr) === null || _a === void 0 ? void 0 : _a.tcMar;
|
|
51
|
+
const table = (_b = tbl.tblPr) === null || _b === void 0 ? void 0 : _b.tblCellMar;
|
|
52
|
+
const left = (_e = (_c = twipsOf(own === null || own === void 0 ? void 0 : own.left)) !== null && _c !== void 0 ? _c : twipsOf((_d = table === null || table === void 0 ? void 0 : table.left) !== null && _d !== void 0 ? _d : table === null || table === void 0 ? void 0 : table.start)) !== null && _e !== void 0 ? _e : DEFAULT_CELL_MARGIN_TWIPS;
|
|
53
|
+
const right = (_h = (_f = twipsOf(own === null || own === void 0 ? void 0 : own.right)) !== null && _f !== void 0 ? _f : twipsOf((_g = table === null || table === void 0 ? void 0 : table.right) !== null && _g !== void 0 ? _g : table === null || table === void 0 ? void 0 : table.end)) !== null && _h !== void 0 ? _h : DEFAULT_CELL_MARGIN_TWIPS;
|
|
54
|
+
return left + right;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The width a new picture must fit, in EMU: the cell's `w:tcW` less its margins when the control
|
|
58
|
+
* is in a table cell and the width is stated in twips (a percentage or `auto` falls to the page),
|
|
59
|
+
* else the page's text width; narrowed to `width=N`. Undefined where nothing states a width (a
|
|
60
|
+
* header's body has no `w:sectPr`): the natural size then.
|
|
61
|
+
*/
|
|
62
|
+
function maxWidthEmu(control, width) {
|
|
63
|
+
var _a;
|
|
64
|
+
const cell = enclosingCellOf(control.element.value);
|
|
65
|
+
const tcW = cell ? twipsOf((_a = cell.tc.tcPr) === null || _a === void 0 ? void 0 : _a.tcW) : undefined;
|
|
66
|
+
const inner = cell && tcW !== undefined ? tcW - cellMarginsTwips(cell.tc, cell.tbl) : undefined;
|
|
67
|
+
const cellEmu = inner !== undefined && inner > 0 ? inner * EMU_PER_TWIP : undefined;
|
|
68
|
+
const text = cellEmu !== null && cellEmu !== void 0 ? cellEmu : writableWidthEmu(control.parentBody.container);
|
|
69
|
+
if (width.kind === 'twips') {
|
|
70
|
+
const wanted = width.twips * EMU_PER_TWIP;
|
|
71
|
+
return text === undefined ? wanted : Math.min(wanted, text);
|
|
72
|
+
}
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Binds one control to a value: base64 image data (a data URL accepted). With `keep`, the first
|
|
77
|
+
* `a:blip` is pointed at a new image part and the drawing is left as it is, as Word binds a picture
|
|
78
|
+
* control (`setBlipImage`: a raster image in `r:embed`, the `asvg:svgBlip` taken away; an SVG under
|
|
79
|
+
* the `asvg:svgBlip`, `r:embed` taken away; the effects kept; CR-002 section 52); a picture
|
|
80
|
+
* control holding no picture gets a new inline one at its natural size instead (docx4j's pre-v3
|
|
81
|
+
* fallback), a tag-bound control holding none is left and noted. Otherwise the control's content
|
|
82
|
+
* becomes one run holding a new inline picture, sized as `PictureWidth` says, in the first
|
|
83
|
+
* paragraph of a block-level control (its properties kept) or as a run-level control's content.
|
|
84
|
+
* An empty value, a value that is not an image this package reads (PNG, JPEG, GIF, BMP; SVG where
|
|
85
|
+
* the drawing is kept, since it has no pixel size to place a new picture at), and a body with no
|
|
86
|
+
* part each leave the control as it was, with a note. The image part the control showed before
|
|
87
|
+
* stays in the package, as docx4j leaves it.
|
|
88
|
+
*/
|
|
89
|
+
export function bindPicture(control, value, width, label) {
|
|
90
|
+
const base64 = base64Of(value);
|
|
91
|
+
if (base64 === '')
|
|
92
|
+
return { done: false, note: `${label}: the node is empty; the control is left as it is` };
|
|
93
|
+
const source = control.parentBody.part;
|
|
94
|
+
if (!source)
|
|
95
|
+
return { done: false, note: `${label}: the control's body has no part to hold an image` };
|
|
96
|
+
if (width.kind === 'keep') {
|
|
97
|
+
const blip = firstBlipOf(control);
|
|
98
|
+
if (blip) {
|
|
99
|
+
try {
|
|
100
|
+
const added = addImagePart(source, base64);
|
|
101
|
+
setBlipImage(blip, added.relId, added.info.contentType === ContentTypes.IMAGE_SVG);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
return { done: false, note: `${label}: not an image this package reads (${messageOf(e)})` };
|
|
105
|
+
}
|
|
106
|
+
control.isShowingPlaceholder = false;
|
|
107
|
+
return { done: true };
|
|
108
|
+
}
|
|
109
|
+
if (control.type !== 'Picture')
|
|
110
|
+
return { done: false, note: `${label}: the control holds no a:blip to point at the image (od:Handler=picture without width keeps the drawing)` };
|
|
111
|
+
width = { kind: 'auto' };
|
|
112
|
+
}
|
|
113
|
+
let run;
|
|
114
|
+
try {
|
|
115
|
+
run = addImage(source, base64, control.parentBody.container, maxWidthEmu(control, width)).run;
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
return { done: false, note: `${label}: not an image this package reads (${messageOf(e)})` };
|
|
119
|
+
}
|
|
120
|
+
control.setBoundContent([run]);
|
|
121
|
+
return { done: true };
|
|
122
|
+
}
|
|
123
|
+
/** Binds one tag-bound control (shape b): the entry's node, the tag's width. */
|
|
124
|
+
export function applyPictureHandlerTo(control, parts) {
|
|
125
|
+
var _a;
|
|
126
|
+
const params = tagParamsOf(control.tag);
|
|
127
|
+
const entryId = (_a = params.get('od:xpath')) !== null && _a !== void 0 ? _a : '';
|
|
128
|
+
const selected = selectEntryNode(entryId, parts);
|
|
129
|
+
if ('note' in selected)
|
|
130
|
+
return { done: false, note: selected.note };
|
|
131
|
+
return bindPicture(control, selected.node.text, pictureWidthOf(params), `od:xpath=${entryId}`);
|
|
132
|
+
}
|
|
133
|
+
/** The picture pass over the tag-bound controls, after the text bindings: counted into the result, what was left said in its notes. */
|
|
134
|
+
export function applyPictureHandlersTo(controls, parts, result) {
|
|
135
|
+
var _a;
|
|
136
|
+
for (const control of controls) {
|
|
137
|
+
if (!isPictureHandlerBound(control))
|
|
138
|
+
continue;
|
|
139
|
+
result.bound++;
|
|
140
|
+
const outcome = applyPictureHandlerTo(control, parts);
|
|
141
|
+
if (outcome.done)
|
|
142
|
+
result.updated++;
|
|
143
|
+
else
|
|
144
|
+
result.skipped++;
|
|
145
|
+
if (outcome.note)
|
|
146
|
+
((_a = result.notes) !== null && _a !== void 0 ? _a : (result.notes = [])).push(outcome.note);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The reverse direction for a mapped picture control (docx4j's `UpdateXmlFromDocumentSurface`
|
|
151
|
+
* skips pictures; Word 15 writes the picture back, check 38): the bytes of the image part the
|
|
152
|
+
* first `a:blip` shows, as base64, into the node: its `asvg:svgBlip`'s SVG where it has one, as
|
|
153
|
+
* `InlinePicture.getBase64` gives (CR-002 section 52), else its `r:embed`'s image. Nothing is written when the node already holds
|
|
154
|
+
* those bytes (compared decoded, so Word's line-wrapped base64 counts as the same), when the
|
|
155
|
+
* control shows no picture, or when the blip's part is not an image part. Asynchronous, since a
|
|
156
|
+
* part's bytes may still be in the container.
|
|
157
|
+
*/
|
|
158
|
+
export async function updateFromPictureControl(control) {
|
|
159
|
+
var _a, _b;
|
|
160
|
+
const mapping = control.xmlMapping;
|
|
161
|
+
if (!mapping.isMapped || control.type !== 'Picture')
|
|
162
|
+
return false;
|
|
163
|
+
const node = mapping.customXmlNode;
|
|
164
|
+
if (!node)
|
|
165
|
+
return false;
|
|
166
|
+
const blip = firstBlipOf(control);
|
|
167
|
+
const source = control.parentBody.part;
|
|
168
|
+
const relId = ((_a = svgBlipOf(blip)) === null || _a === void 0 ? void 0 : _a.embed) || (blip === null || blip === void 0 ? void 0 : blip.embed);
|
|
169
|
+
if (!relId || !source)
|
|
170
|
+
return false;
|
|
171
|
+
const part = (_b = source.relationshipsPart) === null || _b === void 0 ? void 0 : _b.getPart(relId);
|
|
172
|
+
if (!(part instanceof ImagePart))
|
|
173
|
+
return false;
|
|
174
|
+
const bytes = await part.getBytes();
|
|
175
|
+
const held = base64Decode(node.text);
|
|
176
|
+
if (held.length === bytes.length && held.every((b, i) => b === bytes[i]))
|
|
177
|
+
return false;
|
|
178
|
+
node.text = base64Encode(bytes);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
/** `updateFromContentControls`' picture pass: the mapped picture controls, counted into the result. */
|
|
182
|
+
export async function updateFromPictureControls(controls, result) {
|
|
183
|
+
for (const control of controls) {
|
|
184
|
+
if (control.type !== 'Picture' || !control.xmlMapping.isMapped)
|
|
185
|
+
continue;
|
|
186
|
+
result.bound++;
|
|
187
|
+
if (await updateFromPictureControl(control))
|
|
188
|
+
result.updated++;
|
|
189
|
+
else
|
|
190
|
+
result.skipped++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
|
|
2
|
+
import type { PropertyResolver } from '../properties/index.mjs';
|
|
3
|
+
import type { ContentControl } from '../content/ContentControl.mjs';
|
|
4
|
+
import type { CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
5
|
+
import type { BindingResult } from './bindings.mjs';
|
|
6
|
+
/** What the XHTML pass needs of the package. */
|
|
7
|
+
export interface XhtmlHost {
|
|
8
|
+
numbering: {
|
|
9
|
+
newList(options?: {
|
|
10
|
+
bullet?: boolean;
|
|
11
|
+
}): Promise<string>;
|
|
12
|
+
restart(numId: string): string;
|
|
13
|
+
};
|
|
14
|
+
styles: {
|
|
15
|
+
ensure(ids: readonly string[]): Promise<string[]>;
|
|
16
|
+
};
|
|
17
|
+
requireStyles(ids: Iterable<string>): void;
|
|
18
|
+
getPropertyResolver(): Promise<PropertyResolver>;
|
|
19
|
+
getMainDocumentPart(): {
|
|
20
|
+
styleDefinitionsPart?: {
|
|
21
|
+
readContents(): Promise<wml.Styles>;
|
|
22
|
+
} | undefined;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface XhtmlBindingOptions {
|
|
26
|
+
/** The HTML parser: a browser's `DOMParser` by default; jsdom's or linkedom's in Node. */
|
|
27
|
+
parser?: (html: string) => Document;
|
|
28
|
+
}
|
|
29
|
+
/** Whether a control is bound to XHTML through its tag. */
|
|
30
|
+
export declare function isXhtmlBound(control: ContentControl): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Binds one XHTML control. Returns what was done, or a note saying why not: no entry, no part, no
|
|
33
|
+
* node, no parser in this runtime. The content the markup gave that the control cannot hold is in
|
|
34
|
+
* `dropped` (a run-level control's blocks after the first, and what the converter dropped).
|
|
35
|
+
*/
|
|
36
|
+
export declare function applyXhtmlBindingTo(control: ContentControl, parts: CustomXmlPartLookup, host: XhtmlHost, options?: XhtmlBindingOptions): Promise<{
|
|
37
|
+
done: boolean;
|
|
38
|
+
note?: string;
|
|
39
|
+
dropped?: Record<string, number>;
|
|
40
|
+
}>;
|
|
41
|
+
/** The XHTML pass over the controls, after the text bindings: counted into the result, what was left said in its notes. */
|
|
42
|
+
export declare function applyXhtmlBindingsTo(controls: ContentControl[], parts: CustomXmlPartLookup, host: XhtmlHost, result: BindingResult, options?: XhtmlBindingOptions): Promise<void>;
|