@docx4j/core-ts 0.6.0 → 0.7.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 +60 -0
- package/dist/model/content/Body.mjs +6 -17
- package/dist/model/content/Comment.d.mts +2 -2
- package/dist/model/content/Comment.mjs +12 -7
- package/dist/model/content/ContentControl.d.mts +1 -1
- package/dist/model/content/ContentControl.mjs +8 -4
- package/dist/model/content/Font.d.mts +4 -1
- package/dist/model/content/Font.mjs +6 -2
- package/dist/model/content/List.d.mts +6 -3
- package/dist/model/content/List.mjs +15 -8
- package/dist/model/content/Paragraph.d.mts +12 -2
- package/dist/model/content/Paragraph.mjs +31 -11
- package/dist/model/content/Table.mjs +26 -16
- package/dist/model/content/TrackedChange.d.mts +2 -2
- package/dist/model/content/TrackedChange.mjs +6 -3
- package/dist/model/content/index.d.mts +3 -1
- package/dist/model/content/index.mjs +4 -1
- package/dist/model/content/ooxml.mjs +12 -1
- package/dist/model/content/paraIds.d.mts +5 -0
- package/dist/model/content/paraIds.mjs +54 -0
- package/dist/model/content/search.d.mts +2 -0
- package/dist/model/content/search.mjs +51 -7
- package/dist/model/content/story.d.mts +5 -2
- package/dist/model/content/story.mjs +6 -3
- package/dist/model/content/tableRules.mjs +2 -0
- package/dist/model/content/tracking.d.mts +35 -6
- package/dist/model/content/tracking.mjs +93 -12
- package/dist/model/content/tree.d.mts +7 -8
- package/dist/model/content/tree.mjs +7 -9
- package/dist/model/content/wordRange.d.mts +48 -0
- package/dist/model/content/wordRange.mjs +97 -0
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +22 -1
- package/dist/model/customxml/CustomXmlPartCollection.mjs +48 -2
- package/dist/model/customxml/XmlMapping.d.mts +31 -7
- package/dist/model/customxml/XmlMapping.mjs +53 -10
- package/dist/model/customxml/bindings.d.mts +12 -2
- package/dist/model/customxml/bindings.mjs +98 -16
- package/dist/model/customxml/index.d.mts +1 -0
- package/dist/model/customxml/index.mjs +1 -0
- package/dist/model/customxml/opendope.d.mts +4 -1
- package/dist/model/customxml/opendope.mjs +9 -6
- package/dist/model/customxml/pictures.mjs +5 -3
- package/dist/model/customxml/storeItems.d.mts +57 -0
- package/dist/model/customxml/storeItems.mjs +122 -0
- package/dist/model/customxml/xhtml.mjs +15 -20
- package/dist/model/dateZone.d.mts +25 -0
- package/dist/model/dateZone.mjs +57 -0
- package/dist/model/index.d.mts +1 -0
- package/dist/model/index.mjs +1 -0
- package/dist/model/properties/PropertyResolver.d.mts +14 -1
- package/dist/model/properties/PropertyResolver.mjs +18 -3
- package/dist/model/properties/index.d.mts +1 -1
- package/dist/model/properties/log.mjs +2 -2
- package/dist/model/properties/styleUtil.d.mts +3 -1
- package/dist/model/properties/styleUtil.mjs +20 -24
- package/dist/model/text/CaseMapping.d.mts +38 -0
- package/dist/model/text/CaseMapping.mjs +86 -0
- package/dist/model/text/Extractor.d.mts +66 -6
- package/dist/model/text/Extractor.mjs +300 -56
- package/dist/model/text/PlainText.mjs +7 -4
- package/dist/model/text/TextContext.d.mts +103 -8
- package/dist/model/text/TextContext.mjs +228 -53
- package/dist/model/text/TextOptions.d.mts +88 -5
- package/dist/model/text/TextOptions.mjs +158 -17
- package/dist/model/text/TextResult.d.mts +126 -9
- package/dist/model/text/TextResult.mjs +115 -12
- package/dist/model/text/WmlToMarkdown.mjs +32 -7
- package/dist/model/text/caseMapping.generated.d.mts +7 -0
- package/dist/model/text/caseMapping.generated.mjs +214 -0
- package/dist/model/text/index.d.mts +3 -2
- package/dist/model/text/index.mjs +1 -0
- package/dist/model/wordIntegers.d.mts +29 -0
- package/dist/model/wordIntegers.mjs +78 -0
- package/dist/office-js/document.mjs +15 -1
- package/dist/office-js/toApiScript.mjs +8 -7
- package/dist/opc/DirectoryPartStore.d.mts +6 -2
- package/dist/opc/DirectoryPartStore.mjs +11 -4
- package/dist/opc/FlatOpcPartStore.d.mts +4 -2
- package/dist/opc/FlatOpcPartStore.mjs +11 -8
- package/dist/opc/Load.d.mts +32 -0
- package/dist/opc/Load.mjs +115 -5
- package/dist/opc/PartStore.d.mts +34 -1
- package/dist/opc/PartStore.mjs +71 -8
- package/dist/opc/Save.d.mts +17 -2
- package/dist/opc/Save.mjs +100 -4
- package/dist/opc/ZipPartStore.d.mts +4 -2
- package/dist/opc/ZipPartStore.mjs +12 -7
- package/dist/opc/exceptions.d.mts +140 -0
- package/dist/opc/exceptions.mjs +108 -0
- package/dist/opc/index.d.mts +7 -4
- package/dist/opc/index.mjs +5 -3
- package/dist/opc/mce/preprocessor.d.mts +121 -10
- package/dist/opc/mce/preprocessor.mjs +122 -21
- package/dist/opc/mce/understood.d.mts +3 -35
- package/dist/opc/mce/understood.mjs +27 -53
- package/dist/opc/repair/breaks.d.mts +23 -0
- package/dist/opc/repair/breaks.mjs +288 -0
- package/dist/opc/repair/hosts.d.mts +12 -0
- package/dist/opc/repair/hosts.mjs +33 -0
- package/dist/opc/repair/mainRoot.d.mts +19 -0
- package/dist/opc/repair/mainRoot.mjs +168 -0
- package/dist/opc/repair/unknownElements.d.mts +8 -0
- package/dist/opc/repair/unknownElements.mjs +180 -0
- package/dist/opc/repair/wmlNames.generated.d.mts +2 -0
- package/dist/opc/repair/wmlNames.generated.mjs +80 -0
- package/dist/opc/skipped.d.mts +50 -0
- package/dist/opc/skipped.mjs +140 -0
- package/dist/packages/OpcPackage.d.mts +98 -5
- package/dist/packages/OpcPackage.mjs +122 -7
- package/dist/packages/WordprocessingMLPackage.mjs +3 -1
- package/dist/packages/index.d.mts +1 -1
- package/dist/parts/Part.d.mts +5 -1
- package/dist/parts/RelationshipsPart.d.mts +15 -0
- package/dist/parts/RelationshipsPart.mjs +66 -1
- package/dist/parts/XmlPart.d.mts +54 -5
- package/dist/parts/XmlPart.mjs +203 -10
- package/dist/parts/wml/index.d.mts +12 -1
- package/dist/parts/wml/index.mjs +25 -1
- package/package.json +2 -2
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// CR-002 section 65: a measurement setter writes a value only where Word 365 stores it as written. Word keeps each
|
|
2
|
+
// integer attribute in a field of its binary format's width, 16 bits for these, and reads a value past it wrapped, with
|
|
3
|
+
// no error or repair (docx4j CR-065 round 2, CR-066 rounds 5 and 6, run by Jason 2026-10-26): a left indent of 400,000 pt,
|
|
4
|
+
// 8,000,000 twips, opens as 4,608. So the setters refuse such a value, as Office JS refuses an argument it won't take.
|
|
5
|
+
// The width is the attribute's, not its simple type's (CR-066 section 3.1). A file's value is read and kept as written.
|
|
6
|
+
// Since section 65.3 the storage comes from the objects package's table (`WORD_INTEGER_STORAGE`, its CR-016), through
|
|
7
|
+
// `keptRange`, one source for the setters' guards and the out-of-range warnings (CR-017); what each row adds is the
|
|
8
|
+
// setters' own policy, a range narrowed below Word's storage where a value Word keeps is still a caller's mistake.
|
|
9
|
+
import { wordIntegerReading } from '@docx4j/generated-objects-ts/helpers/wml';
|
|
10
|
+
import { keptRange } from '../wordIntegers.mjs';
|
|
11
|
+
import { InvalidArgument } from './generalException.mjs';
|
|
12
|
+
const WML = 'org_docx4j_wml.';
|
|
13
|
+
const WORD_SPACING_LIMIT = "Word's own setter stops at 22 inches, 1584 pt (check 71b); its storage runs to 65535, which its save writes back as -1";
|
|
14
|
+
/**
|
|
15
|
+
* A row from the objects table: the range Word keeps as written there (`keptRange`), narrowed by the setter's policy
|
|
16
|
+
* where `min`, `max` and `why` say so; `reads` through `wordIntegerReading`. A row whose entry the table lacks, or
|
|
17
|
+
* no longer measures, fails here, at load, so a table change announces itself.
|
|
18
|
+
*/
|
|
19
|
+
function row(attribute, unit, typeName, property, policy = {}) {
|
|
20
|
+
var _a, _b;
|
|
21
|
+
const range = keptRange(WML + typeName, property, policy.rule);
|
|
22
|
+
if (range === undefined || range === null)
|
|
23
|
+
throw new Error(`wordRange: the objects table has no measured range for ${typeName}.${property}`);
|
|
24
|
+
const name = (p) => (p === 'hanging' ? 'a hanging indent' : 'a first-line indent');
|
|
25
|
+
return {
|
|
26
|
+
attribute, unit, measured: true, stored: range,
|
|
27
|
+
min: (_a = policy.min) !== null && _a !== void 0 ? _a : range[0], max: (_b = policy.max) !== null && _b !== void 0 ? _b : range[1],
|
|
28
|
+
...(policy.why !== undefined ? { why: policy.why } : {}),
|
|
29
|
+
objects: { typeName: WML + typeName, property, ...(policy.rule !== undefined ? { rule: policy.rule } : {}) },
|
|
30
|
+
reads(value) {
|
|
31
|
+
const r = wordIntegerReading(WML + typeName, property, value, policy.rule);
|
|
32
|
+
if (r === undefined || r.value === null)
|
|
33
|
+
return 'not known';
|
|
34
|
+
if (r.attribute !== undefined && r.attribute !== property)
|
|
35
|
+
return `${name(r.attribute)} of ${r.value}`;
|
|
36
|
+
if (r.rule !== undefined && policy.rule !== undefined)
|
|
37
|
+
return `${r.rule} ${r.value}`;
|
|
38
|
+
return String(r.value);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The attributes the content API's setters write, by Word 365's storage (the objects table; CR-066 sections 3.1 and 3.4),
|
|
44
|
+
* narrowed where Word's own setter stops short of its storage (check 71b, CR-002 section 65.9, D2): there this package
|
|
45
|
+
* refuses where Word does, and no earlier.
|
|
46
|
+
*/
|
|
47
|
+
export const WORD_STORAGE = {
|
|
48
|
+
// int16, wraps (w:left measured by round 5, w:right by round 6); Word's own indent setters wrap too, and this package
|
|
49
|
+
// refuses instead (section 65.5, D1)
|
|
50
|
+
indLeft: row('w:ind/@w:left', 'twips', 'PPrBase.Ind', 'left'),
|
|
51
|
+
indRight: row('w:ind/@w:right', 'twips', 'PPrBase.Ind', 'right'),
|
|
52
|
+
// one signed 16-bit field, the binary format's dxaLeft1: a first-line indent as itself, a hanging indent as its negative,
|
|
53
|
+
// so 0 to 32767 and 0 to 32768 (check 71: Word's firstLineIndent setter wrote 1638.4 pt as a hanging indent of 32768;
|
|
54
|
+
// round 6, the file side: a first-line indent of 32768 reads as a hanging indent of 32768)
|
|
55
|
+
indFirstLine: row('w:ind/@w:firstLine', 'twips', 'PPrBase.Ind', 'firstLine'),
|
|
56
|
+
indHanging: row('w:ind/@w:hanging', 'twips', 'PPrBase.Ind', 'hanging'),
|
|
57
|
+
// a list level's text indent, the same int16 field, but Word's own setLevelIndents refuses at 1584 pt either side
|
|
58
|
+
// (check 71b: 1583.95 and -1583.95 written, 1584 and -1584 InvalidArgument), so 31679 twips either way
|
|
59
|
+
lvlIndLeft: row('w:ind/@w:left', 'twips', 'PPrBase.Ind', 'left', { min: -31679, max: 31679, why: "Word's own setLevelIndents stops short of 22 inches, 1584 pt, either way (check 71b)" }),
|
|
60
|
+
// uint16, wraps, and Word's save writes 0xFFFF back as -1; Word's own setters stop at 22 inches, 1584 pt (check 71b: 1584
|
|
61
|
+
// written as 31680, 1584.05 InvalidArgument; a negative refused, the schema's type being unsigned too)
|
|
62
|
+
spacingBefore: row('w:spacing/@w:before', 'twips', 'PPrBase.Spacing', 'before', { max: 31680, why: WORD_SPACING_LIMIT }),
|
|
63
|
+
spacingAfter: row('w:spacing/@w:after', 'twips', 'PPrBase.Spacing', 'after', { max: 31680, why: WORD_SPACING_LIMIT }),
|
|
64
|
+
// Office JS's lineSpacing, in points, is the line as a multiple of 12 pt ("in the Word UI, this value is divided by 12"):
|
|
65
|
+
// Word's own setter writes w:line = 20 × points under w:lineRule="auto" (check 71b: 0.75 pt as w:line="15"
|
|
66
|
+
// w:lineRule="auto", 1584 as 31680), and takes 0.75 to 1584 pt (0.7 and 1584.05 InvalidArgument); int16, wraps
|
|
67
|
+
spacingLineAuto: row('w:spacing/@w:line', 'twips', 'PPrBase.Spacing', 'line', { rule: 'auto', min: 15, max: 31680, why: "Word's own setter takes 0.75 to 1584 pt, 15 to 31680 twips under w:lineRule=\"auto\" (check 71b)" }),
|
|
68
|
+
// int16, wraps, under dxa and under pct; a width is never negative
|
|
69
|
+
tcWDxa: row('w:tcW/@w:w', 'twips', 'TblWidth', 'w', { min: 0, why: "a cell's width is never negative" }),
|
|
70
|
+
tcWPct: row('w:tcW/@w:w', 'fiftieths of a percent', 'TblWidth', 'w', { min: 0, why: "a cell's width is never negative" }),
|
|
71
|
+
// uint16, wraps; Word's own setter takes 1 to 1638 pt, in half-points after rounding (check 71b: 0.9 written as w:sz
|
|
72
|
+
// w:val="2", 1638.05 as 3276, 0.5 and 1638.4 InvalidArgument), where its storage runs to 65535
|
|
73
|
+
sz: row('w:sz/@w:val', 'half-points', 'HpsMeasure', 'val', { min: 2, max: 3276, why: "Word's own setter takes 1 to 1638 pt, 2 to 3276 half-points after rounding (check 71b)" }),
|
|
74
|
+
};
|
|
75
|
+
const PER_POINT = { twips: 20, 'half-points': 2 };
|
|
76
|
+
/**
|
|
77
|
+
* `points` in the attribute's unit, rounded, where Word stores it as written and the setter's policy allows; else
|
|
78
|
+
* `InvalidArgument`, naming the range and what Word would read, or why the policy stops short of Word's storage (CR-002
|
|
79
|
+
* section 65; the policy section 65.9's). `where` is the setter (`Paragraph.leftIndent`). `units` is the value already
|
|
80
|
+
* in the attribute's unit, where the setter computes it otherwise (a percentage width's fiftieths).
|
|
81
|
+
*/
|
|
82
|
+
export function withinWord(where, points, storage, units) {
|
|
83
|
+
if (!Number.isFinite(points))
|
|
84
|
+
throw new InvalidArgument(`${where}: ${points} is not a measurement in points`);
|
|
85
|
+
const unit = storage.unit;
|
|
86
|
+
const value = units !== null && units !== void 0 ? units : Math.round(points * PER_POINT[unit]);
|
|
87
|
+
if (value >= storage.min && value <= storage.max)
|
|
88
|
+
return value;
|
|
89
|
+
const inPoints = unit in PER_POINT
|
|
90
|
+
? `, ${storage.min / PER_POINT[unit]} to ${storage.max / PER_POINT[unit]} pt`
|
|
91
|
+
: '';
|
|
92
|
+
// within Word's storage the value is kept as written, so the refusal is the setter's policy; past it, what Word reads
|
|
93
|
+
const kept = value >= storage.stored[0] && value <= storage.stored[1];
|
|
94
|
+
const reason = kept && storage.why !== undefined ? storage.why : `Word 365 would read it as ${storage.reads(value)}`;
|
|
95
|
+
throw new InvalidArgument(`${where}: ${points} pt is ${value} ${unit} in ${storage.attribute}, outside the range written there `
|
|
96
|
+
+ `(${storage.min} to ${storage.max} ${unit}${inPoints}): ${reason} (CR-002 section 65)`);
|
|
97
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
|
6
6
|
import type { XPathEngine } from './xpath.mjs';
|
|
7
7
|
import { type BindingResult } from './bindings.mjs';
|
|
8
8
|
import { type XhtmlHost, type XhtmlBindingOptions } from './xhtml.mjs';
|
|
9
|
+
import { type StoreItemResolution } from './storeItems.mjs';
|
|
9
10
|
/**
|
|
10
11
|
* What the collection needs of its package. A structural interface, so that this module does not
|
|
11
12
|
* import `WordprocessingMLPackage` (which imports it): the package hands itself in.
|
|
@@ -21,24 +22,44 @@ export interface CustomXmlHost {
|
|
|
21
22
|
customXmlRelationshipSource(): Part;
|
|
22
23
|
/** The package, for the XHTML pass (list definitions, styles, relationships); absent, XHTML controls are left and noted. */
|
|
23
24
|
readonly xhtmlHost?: XhtmlHost;
|
|
25
|
+
/** The package's property resolver, made if need be: a date control without `w:lid` is shown in its document default language (CR-020 D5). */
|
|
26
|
+
getPropertyResolver?(): Promise<unknown>;
|
|
24
27
|
}
|
|
25
28
|
/** A subset of Office JS `Word.CustomXmlPartCollection`. */
|
|
26
29
|
export declare class CustomXmlPartCollection implements CustomXmlPartOwner, CustomXmlPartLookup {
|
|
27
30
|
private readonly host;
|
|
28
31
|
private readonly views;
|
|
32
|
+
/** The parts `load()` could not parse, with the parser's error: each read as absent (`notRead`). */
|
|
33
|
+
private readonly unread;
|
|
34
|
+
private readonly storeItems;
|
|
29
35
|
constructor(host: CustomXmlHost);
|
|
30
36
|
get xpathEngine(): XPathEngine;
|
|
31
37
|
/**
|
|
32
38
|
* Parses every custom XML part's DOM and its properties part, and warms the XPath engine. One
|
|
33
39
|
* await, and the collection and the node views are synchronous from then on (section 3.6).
|
|
40
|
+
* A part that cannot be parsed is read as absent (`notRead`), not refused for the whole collection.
|
|
34
41
|
*/
|
|
35
42
|
load(): Promise<CustomXmlPart[]>;
|
|
36
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The custom XML parts the last `load()` could not parse (not well formed), by part name, with the parser's error. Each
|
|
45
|
+
* is read as absent, as Word reads such a store when it repairs the document (CR-012 section 28's D3, WD 0.69; CR-020
|
|
46
|
+
* section 8.2): it is in no `items`, `getItem` does not find it, and a binding naming it resolves as one naming no part,
|
|
47
|
+
* to the part its XPath fits, else to none. The part stays in the package, and an untouched save copies its bytes.
|
|
48
|
+
*/
|
|
49
|
+
get notRead(): ReadonlyMap<string, unknown>;
|
|
50
|
+
/** The parts, in the order they were registered, but those `load()` could not parse. Call `load()` once first: a DOM has to be parsed. */
|
|
37
51
|
get items(): CustomXmlPart[];
|
|
38
52
|
/** The parts whose document element is in that namespace (Office JS getByNamespace). */
|
|
39
53
|
getByNamespace(namespaceUri: string): CustomXmlPart[];
|
|
40
54
|
/** The part with that itemID; the comparison ignores case and accepts the id with or without braces. */
|
|
41
55
|
getItem(id: string): CustomXmlPart | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The part a binding is evaluated against (CR-020, docx4j's `StoreItemResolver`): the one its `w:storeItemID` names,
|
|
58
|
+
* else, where that names no part, the one its XPath fits, as Word repairs such a binding on open; undefined where none
|
|
59
|
+
* is named and none fits, and for the core and application properties' IDs, which name no part here. `repaired` says
|
|
60
|
+
* which. Call `load()` once first, as for `items`.
|
|
61
|
+
*/
|
|
62
|
+
resolveStoreItem(storeItemID: string, xpath: string, prefixMappings?: string): StoreItemResolution<CustomXmlPart> | undefined;
|
|
42
63
|
/**
|
|
43
64
|
* A new custom XML part holding this XML, with its properties part and a fresh itemID, related
|
|
44
65
|
* from the main document part (docx4j's `AbstractMigrator.addPropertiesPart` recipe: Word
|
|
@@ -16,12 +16,22 @@ import { CustomXmlPart } from './CustomXmlPart.mjs';
|
|
|
16
16
|
import { applyBindingsTo, updateFromControls } from './bindings.mjs';
|
|
17
17
|
import { applyXhtmlBindingsTo, isXhtmlBound } from './xhtml.mjs';
|
|
18
18
|
import { applyPictureHandlersTo, updateFromPictureControls } from './pictures.mjs';
|
|
19
|
+
import { StoreItemResolver } from './storeItems.mjs';
|
|
19
20
|
import { inUnreadBranch, writingEveryBranch } from '../content/tree.mjs';
|
|
20
21
|
/** A subset of Office JS `Word.CustomXmlPartCollection`. */
|
|
21
22
|
export class CustomXmlPartCollection {
|
|
22
23
|
constructor(host) {
|
|
23
24
|
this.host = host;
|
|
24
25
|
this.views = new Map();
|
|
26
|
+
/** The parts `load()` could not parse, with the parser's error: each read as absent (`notRead`). */
|
|
27
|
+
this.unread = new Map();
|
|
28
|
+
this.storeItems = new StoreItemResolver(() => this.items.map((view) => ({
|
|
29
|
+
part: view,
|
|
30
|
+
id: view.id,
|
|
31
|
+
partName: view.part.partName.name,
|
|
32
|
+
root: view.document.documentElement,
|
|
33
|
+
selects: (xpath, prefixMappings) => view.selectSingleNode(xpath, prefixMappings) !== undefined,
|
|
34
|
+
})));
|
|
25
35
|
}
|
|
26
36
|
get xpathEngine() {
|
|
27
37
|
return this.host.xpathEngine;
|
|
@@ -29,10 +39,19 @@ export class CustomXmlPartCollection {
|
|
|
29
39
|
/**
|
|
30
40
|
* Parses every custom XML part's DOM and its properties part, and warms the XPath engine. One
|
|
31
41
|
* await, and the collection and the node views are synchronous from then on (section 3.6).
|
|
42
|
+
* A part that cannot be parsed is read as absent (`notRead`), not refused for the whole collection.
|
|
32
43
|
*/
|
|
33
44
|
async load() {
|
|
34
45
|
for (const part of this.host.customXmlDataStorageParts.values()) {
|
|
35
|
-
|
|
46
|
+
try {
|
|
47
|
+
await part.parseDocument();
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
this.unread.set(part, e);
|
|
51
|
+
this.views.delete(part);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
this.unread.delete(part);
|
|
36
55
|
if (!this.views.has(part))
|
|
37
56
|
this.views.set(part, await this.viewFor(part));
|
|
38
57
|
}
|
|
@@ -42,10 +61,21 @@ export class CustomXmlPartCollection {
|
|
|
42
61
|
await this.host.xpathEngine.ready(parsed);
|
|
43
62
|
return this.items;
|
|
44
63
|
}
|
|
45
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* The custom XML parts the last `load()` could not parse (not well formed), by part name, with the parser's error. Each
|
|
66
|
+
* is read as absent, as Word reads such a store when it repairs the document (CR-012 section 28's D3, WD 0.69; CR-020
|
|
67
|
+
* section 8.2): it is in no `items`, `getItem` does not find it, and a binding naming it resolves as one naming no part,
|
|
68
|
+
* to the part its XPath fits, else to none. The part stays in the package, and an untouched save copies its bytes.
|
|
69
|
+
*/
|
|
70
|
+
get notRead() {
|
|
71
|
+
return new Map([...this.unread].map(([part, error]) => [part.partName.name, error]));
|
|
72
|
+
}
|
|
73
|
+
/** The parts, in the order they were registered, but those `load()` could not parse. Call `load()` once first: a DOM has to be parsed. */
|
|
46
74
|
get items() {
|
|
47
75
|
const out = [];
|
|
48
76
|
for (const part of this.host.customXmlDataStorageParts.values()) {
|
|
77
|
+
if (this.unread.has(part))
|
|
78
|
+
continue;
|
|
49
79
|
const view = this.views.get(part);
|
|
50
80
|
if (!view) {
|
|
51
81
|
throw new Docx4JException(`Custom XML part ${part.partName} is not parsed yet: await pkg.customXmlParts.load() once`);
|
|
@@ -63,6 +93,15 @@ export class CustomXmlPartCollection {
|
|
|
63
93
|
const key = normaliseId(id);
|
|
64
94
|
return this.items.find((part) => normaliseId(part.id) === key);
|
|
65
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* The part a binding is evaluated against (CR-020, docx4j's `StoreItemResolver`): the one its `w:storeItemID` names,
|
|
98
|
+
* else, where that names no part, the one its XPath fits, as Word repairs such a binding on open; undefined where none
|
|
99
|
+
* is named and none fits, and for the core and application properties' IDs, which name no part here. `repaired` says
|
|
100
|
+
* which. Call `load()` once first, as for `items`.
|
|
101
|
+
*/
|
|
102
|
+
resolveStoreItem(storeItemID, xpath, prefixMappings) {
|
|
103
|
+
return this.storeItems.resolve(storeItemID, xpath, prefixMappings);
|
|
104
|
+
}
|
|
66
105
|
/**
|
|
67
106
|
* A new custom XML part holding this XML, with its properties part and a fresh itemID, related
|
|
68
107
|
* from the main document part (docx4j's `AbstractMigrator.addPropertiesPart` recipe: Word
|
|
@@ -141,7 +180,14 @@ export class CustomXmlPartCollection {
|
|
|
141
180
|
* headers and footers it has) and parses the custom XML parts.
|
|
142
181
|
*/
|
|
143
182
|
async applyBindings(options = {}) {
|
|
183
|
+
var _a, _b;
|
|
144
184
|
await this.load();
|
|
185
|
+
try {
|
|
186
|
+
await ((_b = (_a = this.host).getPropertyResolver) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// a styles part that can't be read: a date control without w:lid is shown in en-US
|
|
190
|
+
}
|
|
145
191
|
// every branch of a kept mc:AlternateContent is bound, as Word binds what it reads; the result counts
|
|
146
192
|
// what a reader sees, the other branches' controls bound after them and not counted (CR-013 phase 3)
|
|
147
193
|
const { read, unread } = this.controlsOf(await this.host.getBoundBodies());
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
|
|
2
2
|
import { type ContentControl } from '../content/ContentControl.mjs';
|
|
3
3
|
import type { CustomXmlPart, CustomXmlNode } from './CustomXmlPart.mjs';
|
|
4
|
+
import { type StoreItemResolution } from './storeItems.mjs';
|
|
4
5
|
/** What `XmlMapping` needs of `pkg.customXmlParts`, so that this module does not import the package. */
|
|
5
6
|
export interface CustomXmlPartLookup {
|
|
6
7
|
readonly items: CustomXmlPart[];
|
|
7
8
|
getItem(id: string): CustomXmlPart | undefined;
|
|
9
|
+
/** The part a binding is evaluated against: named by its ID, else the one its XPath fits (CR-020). */
|
|
10
|
+
resolveStoreItem(storeItemID: string, xpath: string, prefixMappings?: string): StoreItemResolution<CustomXmlPart> | undefined;
|
|
8
11
|
}
|
|
9
12
|
/**
|
|
10
|
-
* A subset of Office JS `Word.XmlMapping`: the content control's data binding
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* A subset of Office JS `Word.XmlMapping`: the content control's data binding, `w:dataBinding`
|
|
14
|
+
* (or, on a rich text control or a repeating section, the `w15:dataBinding` Word writes there,
|
|
15
|
+
* which is what `setMapping` writes on one: CR-002 section 40). `customXmlPart` is the part the
|
|
16
|
+
* binding is evaluated against, the one its `w:storeItemID` names or, where that names none, the
|
|
17
|
+
* one its XPath fits, as Word repairs it (CR-020); `customXmlNode` and `isMapped` evaluate the
|
|
18
|
+
* XPath there now, so they need the parts parsed and the XPath engine warm (`await
|
|
19
|
+
* pkg.customXmlParts.load()` once, CR-002 section 3.6).
|
|
15
20
|
*/
|
|
16
21
|
export declare class XmlMapping {
|
|
17
22
|
/** The control this mapping belongs to. */
|
|
@@ -22,15 +27,34 @@ export declare class XmlMapping {
|
|
|
22
27
|
contentControl: ContentControl, lookup: CustomXmlPartLookup | undefined);
|
|
23
28
|
/** The `w:dataBinding` value, or undefined when the control has none (extension). */
|
|
24
29
|
get dataBinding(): wml.CTDataBinding | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Whether the binding names a node, as Office JS reports it (CR-020 D8, check 73b: a control bound to its part by ID
|
|
32
|
+
* with an XPath that selects nothing is not mapped): the node the XPath selects in the part the binding resolves to, so
|
|
33
|
+
* a stale `w:storeItemID` no part fits is not mapped either. A binding to the core or application properties, which are
|
|
34
|
+
* not custom XML parts here, is mapped. `dataBinding` says whether the control carries a binding at all.
|
|
35
|
+
*/
|
|
25
36
|
get isMapped(): boolean;
|
|
26
37
|
get xpath(): string;
|
|
27
38
|
get prefixMappings(): string;
|
|
28
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* `w:storeItemID`, the itemID of the custom XML part, as stored (extension: Office JS reports the part). A stale ID stays
|
|
41
|
+
* as it is until `applyBindings` writes the resolved part's back, as Word's save does (CR-020 D2 and D3).
|
|
42
|
+
*/
|
|
29
43
|
get storeItemID(): string;
|
|
30
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* The part the binding is evaluated against: the one its `w:storeItemID` names, else the one its XPath fits, as Word
|
|
46
|
+
* binds it (CR-020; check 73b: Office JS's `customXmlPart` for a stale ID is the part Word repaired it to).
|
|
47
|
+
*/
|
|
31
48
|
get customXmlPart(): CustomXmlPart | undefined;
|
|
32
49
|
/** The node the XPath selects now, or undefined when it selects nothing. */
|
|
33
50
|
get customXmlNode(): CustomXmlNode | undefined;
|
|
51
|
+
/** @internal How the binding resolves (CR-020): its part, and whether that is Word's repair of a stale ID. */
|
|
52
|
+
get resolution(): StoreItemResolution<CustomXmlPart> | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* @internal Writes a part's ID over a stale `w:storeItemID`, in `w:dataBinding` and `w15:dataBinding` alike, as Word's
|
|
55
|
+
* save writes the part it repaired the binding to (CR-020 D2): `applyBindings`' write-back, never a reader's.
|
|
56
|
+
*/
|
|
57
|
+
repairStoreItemID(stale: string, id: string): void;
|
|
34
58
|
/**
|
|
35
59
|
* Binds the control to what the XPath selects, as Word's own mapping does: the part is the one
|
|
36
60
|
* given, else the one the control is already bound to, else the first custom XML part (the
|
|
@@ -5,15 +5,18 @@ import { holdsOneParagraph } from '../content/ContentControl.mjs';
|
|
|
5
5
|
import { NotSupportedError } from '../../office-js/errors.mjs';
|
|
6
6
|
import { canonicalXPathOf } from './xpath.mjs';
|
|
7
7
|
import { applyBindingTo } from './bindings.mjs';
|
|
8
|
+
import { isPropertiesStoreItem, normaliseStoreItemId } from './storeItems.mjs';
|
|
8
9
|
/** The Word 2012 (w15) namespace; not imported from `ContentControl`, which imports this module. */
|
|
9
10
|
const W15_NS = 'http://schemas.microsoft.com/office/word/2012/wordml';
|
|
10
11
|
const W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
|
|
11
12
|
/**
|
|
12
|
-
* A subset of Office JS `Word.XmlMapping`: the content control's data binding
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* A subset of Office JS `Word.XmlMapping`: the content control's data binding, `w:dataBinding`
|
|
14
|
+
* (or, on a rich text control or a repeating section, the `w15:dataBinding` Word writes there,
|
|
15
|
+
* which is what `setMapping` writes on one: CR-002 section 40). `customXmlPart` is the part the
|
|
16
|
+
* binding is evaluated against, the one its `w:storeItemID` names or, where that names none, the
|
|
17
|
+
* one its XPath fits, as Word repairs it (CR-020); `customXmlNode` and `isMapped` evaluate the
|
|
18
|
+
* XPath there now, so they need the parts parsed and the XPath engine warm (`await
|
|
19
|
+
* pkg.customXmlParts.load()` once, CR-002 section 3.6).
|
|
17
20
|
*/
|
|
18
21
|
export class XmlMapping {
|
|
19
22
|
constructor(
|
|
@@ -31,8 +34,20 @@ export class XmlMapping {
|
|
|
31
34
|
const item = (_a = sdtProperty(pr, 'dataBinding')) !== null && _a !== void 0 ? _a : sdtProperty(pr, 'dataBinding', W15_NS);
|
|
32
35
|
return item === null || item === void 0 ? void 0 : item.value;
|
|
33
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether the binding names a node, as Office JS reports it (CR-020 D8, check 73b: a control bound to its part by ID
|
|
39
|
+
* with an XPath that selects nothing is not mapped): the node the XPath selects in the part the binding resolves to, so
|
|
40
|
+
* a stale `w:storeItemID` no part fits is not mapped either. A binding to the core or application properties, which are
|
|
41
|
+
* not custom XML parts here, is mapped. `dataBinding` says whether the control carries a binding at all.
|
|
42
|
+
*/
|
|
34
43
|
get isMapped() {
|
|
35
|
-
|
|
44
|
+
var _a;
|
|
45
|
+
const binding = this.dataBinding;
|
|
46
|
+
if (!binding)
|
|
47
|
+
return false;
|
|
48
|
+
if (isPropertiesStoreItem((_a = binding.storeItemID) !== null && _a !== void 0 ? _a : ''))
|
|
49
|
+
return true;
|
|
50
|
+
return this.customXmlNode !== undefined;
|
|
36
51
|
}
|
|
37
52
|
get xpath() {
|
|
38
53
|
var _a, _b;
|
|
@@ -42,16 +57,21 @@ export class XmlMapping {
|
|
|
42
57
|
var _a, _b;
|
|
43
58
|
return (_b = (_a = this.dataBinding) === null || _a === void 0 ? void 0 : _a.prefixMappings) !== null && _b !== void 0 ? _b : '';
|
|
44
59
|
}
|
|
45
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* `w:storeItemID`, the itemID of the custom XML part, as stored (extension: Office JS reports the part). A stale ID stays
|
|
62
|
+
* as it is until `applyBindings` writes the resolved part's back, as Word's save does (CR-020 D2 and D3).
|
|
63
|
+
*/
|
|
46
64
|
get storeItemID() {
|
|
47
65
|
var _a, _b;
|
|
48
66
|
return (_b = (_a = this.dataBinding) === null || _a === void 0 ? void 0 : _a.storeItemID) !== null && _b !== void 0 ? _b : '';
|
|
49
67
|
}
|
|
50
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* The part the binding is evaluated against: the one its `w:storeItemID` names, else the one its XPath fits, as Word
|
|
70
|
+
* binds it (CR-020; check 73b: Office JS's `customXmlPart` for a stale ID is the part Word repaired it to).
|
|
71
|
+
*/
|
|
51
72
|
get customXmlPart() {
|
|
52
73
|
var _a;
|
|
53
|
-
|
|
54
|
-
return id === '' ? undefined : (_a = this.lookup) === null || _a === void 0 ? void 0 : _a.getItem(id);
|
|
74
|
+
return (_a = this.resolution) === null || _a === void 0 ? void 0 : _a.part;
|
|
55
75
|
}
|
|
56
76
|
/** The node the XPath selects now, or undefined when it selects nothing. */
|
|
57
77
|
get customXmlNode() {
|
|
@@ -61,6 +81,29 @@ export class XmlMapping {
|
|
|
61
81
|
return undefined;
|
|
62
82
|
return part.selectSingleNode(binding.xpath, binding.prefixMappings);
|
|
63
83
|
}
|
|
84
|
+
/** @internal How the binding resolves (CR-020): its part, and whether that is Word's repair of a stale ID. */
|
|
85
|
+
get resolution() {
|
|
86
|
+
var _a;
|
|
87
|
+
const binding = this.dataBinding;
|
|
88
|
+
if (!(binding === null || binding === void 0 ? void 0 : binding.storeItemID))
|
|
89
|
+
return undefined;
|
|
90
|
+
return (_a = this.lookup) === null || _a === void 0 ? void 0 : _a.resolveStoreItem(binding.storeItemID, binding.xpath, binding.prefixMappings);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* @internal Writes a part's ID over a stale `w:storeItemID`, in `w:dataBinding` and `w15:dataBinding` alike, as Word's
|
|
94
|
+
* save writes the part it repaired the binding to (CR-020 D2): `applyBindings`' write-back, never a reader's.
|
|
95
|
+
*/
|
|
96
|
+
repairStoreItemID(stale, id) {
|
|
97
|
+
var _a, _b, _c;
|
|
98
|
+
const key = normaliseStoreItemId(stale);
|
|
99
|
+
for (const item of ((_b = (_a = this.contentControl.sdt.sdtPr) === null || _a === void 0 ? void 0 : _a.rPrOrAliasOrLock) !== null && _b !== void 0 ? _b : [])) {
|
|
100
|
+
if (item.name.localPart !== 'dataBinding')
|
|
101
|
+
continue;
|
|
102
|
+
const binding = item.value;
|
|
103
|
+
if (normaliseStoreItemId((_c = binding.storeItemID) !== null && _c !== void 0 ? _c : '') === key)
|
|
104
|
+
binding.storeItemID = id;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
64
107
|
/**
|
|
65
108
|
* Binds the control to what the XPath selects, as Word's own mapping does: the part is the one
|
|
66
109
|
* given, else the one the control is already bound to, else the first custom XML part (the
|
|
@@ -15,7 +15,11 @@ export interface BindingResult {
|
|
|
15
15
|
updated: number;
|
|
16
16
|
/** Controls left alone: the part is missing, the XPath selects nothing, or the kind is not handled. */
|
|
17
17
|
skipped: number;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* What the XHTML and picture passes left or dropped, one line each (CR-005 sections 8.2 and 9; REQ-076), and each
|
|
20
|
+
* control whose `w:storeItemID` names no part: bound to the part its XPath fits, as Word repairs it, or left as it is
|
|
21
|
+
* where none fits (CR-020 D4).
|
|
22
|
+
*/
|
|
19
23
|
notes?: string[];
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
@@ -43,7 +47,9 @@ export declare function placeholderRun(rPr: wml.RPr | undefined, text?: string):
|
|
|
43
47
|
* A date in Word's `w:dateFormat` pattern (the .NET forms Word writes: `d MMMM yyyy`,
|
|
44
48
|
* `dddd, d MMMM yyyy`, `d/MM/yyyy`), in the control's `w:lid` locale. Month and day names come
|
|
45
49
|
* from `Intl`; text inside single or double quotes is literal. docx4j uses Java's
|
|
46
|
-
* `SimpleDateFormat` with `dddd` mapped to `EEEE`; this is the same vocabulary.
|
|
50
|
+
* `SimpleDateFormat` with `dddd` mapped to `EEEE`; this is the same vocabulary. `am/pm` and
|
|
51
|
+
* `AM/PM` (any mix of cases, as docx4j's `isAMPM`) are the locale's day-period names upper-cased,
|
|
52
|
+
* as Word shows both (CR-020 D5, docx4j CR-058's `wordAmPm`): `h:mm am/pm` at 15:05 is `3:05 PM`.
|
|
47
53
|
*
|
|
48
54
|
* The date's **UTC fields** are formatted: a bound date is its nominal date, the fields as written,
|
|
49
55
|
* which `w:fullDate` keeps with a `Z` and `DatePickerContentControl.fullDate` returns as a `Date` in
|
|
@@ -55,6 +61,10 @@ export declare function formatDate(pattern: string, date: Date, locale?: string)
|
|
|
55
61
|
* false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
|
|
56
62
|
* this phase does not handle (explicit rich text), a node holding a Flat OPC package, or a
|
|
57
63
|
* picture control whose node holds no image (`notes` says which; CR-005 section 9).
|
|
64
|
+
*
|
|
65
|
+
* The part is the one the binding resolves to (CR-020): where its `w:storeItemID` names no part,
|
|
66
|
+
* the one its XPath fits, as Word repairs it on open; a control so bound gets that part's ID in
|
|
67
|
+
* its binding, as Word's save writes it (D2), and a note, as does a stale ID no part fits (D4).
|
|
58
68
|
*/
|
|
59
69
|
export declare function applyBindingTo(control: ContentControl, notes?: string[]): boolean;
|
|
60
70
|
/** `applyBindings` over a list of controls. */
|