@docx4j/core-ts 0.1.2 → 0.1.3
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/dist/model/customxml/index.d.mts +1 -1
- package/dist/model/customxml/index.mjs +1 -1
- package/dist/model/customxml/xpath-fonto.d.mts +42 -0
- package/dist/model/customxml/xpath-fonto.mjs +123 -0
- package/dist/model/customxml/xpath.d.mts +32 -0
- package/dist/model/customxml/xpath.mjs +28 -0
- package/package.json +10 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type XPathEngine, type XPathValue, DefaultXPathEngine, parsePrefixMappings, formatPrefixMappings, canonicalXPathOf, xmlOf, type CanonicalXPath } from './xpath.mjs';
|
|
1
|
+
export { type XPathEngine, type XPathValue, type BooleanConversionMode, booleanValue, DefaultXPathEngine, parsePrefixMappings, formatPrefixMappings, canonicalXPathOf, xmlOf, type CanonicalXPath } from './xpath.mjs';
|
|
2
2
|
export { CustomXmlPart, CustomXmlNode, CustomXmlPrefixMappingCollection, type CustomXmlNodeType, type CustomXmlPrefixMapping, type CustomXmlPartOwner } from './CustomXmlPart.mjs';
|
|
3
3
|
export { CustomXmlPartCollection, type CustomXmlHost } from './CustomXmlPartCollection.mjs';
|
|
4
4
|
export { XmlMapping, type CustomXmlPartLookup } from './XmlMapping.mjs';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// CR-002 phase E: custom XML parts, XML mapping, the XPath engine and the typed content controls
|
|
2
2
|
// (CR-002 sections 3.5 and 3.6). Exported from '.' and './model', as the content API is.
|
|
3
|
-
export { DefaultXPathEngine, parsePrefixMappings, formatPrefixMappings, canonicalXPathOf, xmlOf } from './xpath.mjs';
|
|
3
|
+
export { booleanValue, DefaultXPathEngine, parsePrefixMappings, formatPrefixMappings, canonicalXPathOf, xmlOf } from './xpath.mjs';
|
|
4
4
|
export { CustomXmlPart, CustomXmlNode, CustomXmlPrefixMappingCollection } from './CustomXmlPart.mjs';
|
|
5
5
|
export { CustomXmlPartCollection } from './CustomXmlPartCollection.mjs';
|
|
6
6
|
export { XmlMapping } from './XmlMapping.mjs';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BooleanConversionMode, XPathEngine, XPathValue } from './xpath.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* XPath 3.1 through FontoXPath, for the `xpath2` boolean conversion mode and for expressions
|
|
4
|
+
* written in XPath 2.0 syntax. `select`, `selectValue` and the `java` and `xpath1` modes answer as
|
|
5
|
+
* `DefaultXPathEngine` does; `xpath2` is the mode this engine exists for.
|
|
6
|
+
*/
|
|
7
|
+
export declare class FontoXPathEngine implements XPathEngine {
|
|
8
|
+
private module;
|
|
9
|
+
private loading;
|
|
10
|
+
get isReady(): boolean;
|
|
11
|
+
ready(): Promise<void>;
|
|
12
|
+
select(expression: string, context: Node, namespaces?: Record<string, string>): Node[];
|
|
13
|
+
selectValue(expression: string, context: Node, namespaces?: Record<string, string>): XPathValue;
|
|
14
|
+
/**
|
|
15
|
+
* Table 7's three modes. `java` and `xpath1` are the rules the specification states, evaluated
|
|
16
|
+
* here rather than through the `booleanValue` function so that one engine answers all three.
|
|
17
|
+
* `xpath2` is the effective boolean value **with the specification's string rule**: see
|
|
18
|
+
* {@link xpath2BooleanValue}.
|
|
19
|
+
*/
|
|
20
|
+
booleanValue(expression: string, context: Node, namespaces: Record<string, string>, mode: BooleanConversionMode): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The `xpath2` mode of table 7, **as docx4j does it**: the expression is wrapped in
|
|
23
|
+
* `xs:boolean(...)` and the engine performs the cast, which is `XmlPart.cachedXPathGetBoolean`
|
|
24
|
+
* with `opendope.conditions.Xpathref.XPathBoolean=cast2` (measured against Saxon-HE 9.9.0-2 by
|
|
25
|
+
* the docx4j session, 2026-09-25, and matched here case for case). Doing what docx4j does,
|
|
26
|
+
* rather than casting by hand, is what keeps the two from drifting: the lexical space, the
|
|
27
|
+
* whitespace collapse, the multi-item rule and the empty sequence all come from the engine.
|
|
28
|
+
*
|
|
29
|
+
* So: `true`, `1` are true; `false`, `0` are false; leading and trailing space is collapsed
|
|
30
|
+
* first, so `" false "` is false; an expression selecting **nothing** is false; and anything
|
|
31
|
+
* else raises - `"yes"`, `"True"` (`xs:boolean`'s lexical space is case-sensitive where the
|
|
32
|
+
* `java` mode ignores case), `""` from an empty element, and a sequence of more than one node.
|
|
33
|
+
*
|
|
34
|
+
* A raise is not a false. docx4j turns it into an `InputIntegrityException` and the whole bind
|
|
35
|
+
* fails; here it is a `Docx4JException` from this call, and what a processor does with it is
|
|
36
|
+
* the processor's business (CR-005 phase B). That is the one place the two behaviours are not
|
|
37
|
+
* yet the same thing, and the specification does not say which is right.
|
|
38
|
+
*/
|
|
39
|
+
private xpath2BooleanValue;
|
|
40
|
+
private options;
|
|
41
|
+
private fonto;
|
|
42
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// CR-005 phase A: an XPath 2.0 (in fact 3.1) `XPathEngine`, for the OpenDoPE specification's
|
|
2
|
+
// `xpath2` boolean conversion mode (its section 7.2, table 7).
|
|
3
|
+
//
|
|
4
|
+
// `DefaultXPathEngine` is XPath 1.0, which is what `document.evaluate` and the `xpath` package
|
|
5
|
+
// give. The specification's `xpath2` mode needs a 2.0 evaluator, and expressions written for it
|
|
6
|
+
// may use 2.0 syntax; docx4j uses Saxon. [FontoXPath](https://github.com/FontoXML/fontoxpath) is
|
|
7
|
+
// XPath 3.1 in JavaScript (MIT), over DOM nodes in browsers and in Node, and XPath 3.1 is a
|
|
8
|
+
// superset of 2.0.
|
|
9
|
+
//
|
|
10
|
+
// It is an **optional peer dependency** and this module is its own entry point
|
|
11
|
+
// (`@docx4j/core-ts/xpath-fonto`), so nothing that imports the package pays its ~665 KB: only a
|
|
12
|
+
// consumer who needs the `xpath2` mode installs it and imports this.
|
|
13
|
+
//
|
|
14
|
+
// import { FontoXPathEngine } from '@docx4j/core-ts/xpath-fonto';
|
|
15
|
+
// pkg.xpathEngine = new FontoXPathEngine();
|
|
16
|
+
// await pkg.customXmlParts.load();
|
|
17
|
+
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
18
|
+
/** `evaluateXPath`'s ANY_TYPE: the value in its natural type, which is what `selectValue` reports. */
|
|
19
|
+
const ANY_TYPE = 0;
|
|
20
|
+
/**
|
|
21
|
+
* XPath 3.1 through FontoXPath, for the `xpath2` boolean conversion mode and for expressions
|
|
22
|
+
* written in XPath 2.0 syntax. `select`, `selectValue` and the `java` and `xpath1` modes answer as
|
|
23
|
+
* `DefaultXPathEngine` does; `xpath2` is the mode this engine exists for.
|
|
24
|
+
*/
|
|
25
|
+
export class FontoXPathEngine {
|
|
26
|
+
get isReady() {
|
|
27
|
+
return this.module !== undefined;
|
|
28
|
+
}
|
|
29
|
+
async ready() {
|
|
30
|
+
var _a;
|
|
31
|
+
if (this.isReady)
|
|
32
|
+
return;
|
|
33
|
+
(_a = this.loading) !== null && _a !== void 0 ? _a : (this.loading = (async () => {
|
|
34
|
+
var _a;
|
|
35
|
+
const specifier = 'fontoxpath';
|
|
36
|
+
try {
|
|
37
|
+
// The published ESM build has no named exports under Node's interop, only a default.
|
|
38
|
+
const loaded = await import(/* @vite-ignore */ specifier);
|
|
39
|
+
this.module = (_a = loaded.default) !== null && _a !== void 0 ? _a : loaded;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
throw new Docx4JException("FontoXPathEngine needs the optional peer dependency 'fontoxpath' (npm install fontoxpath)", { cause: e });
|
|
43
|
+
}
|
|
44
|
+
})());
|
|
45
|
+
await this.loading;
|
|
46
|
+
}
|
|
47
|
+
select(expression, context, namespaces = {}) {
|
|
48
|
+
return this.fonto().evaluateXPathToNodes(expression, context, null, null, this.options(namespaces));
|
|
49
|
+
}
|
|
50
|
+
selectValue(expression, context, namespaces = {}) {
|
|
51
|
+
const value = this.fonto().evaluateXPath(expression, context, null, null, ANY_TYPE, this.options(namespaces));
|
|
52
|
+
return valueOf(value);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Table 7's three modes. `java` and `xpath1` are the rules the specification states, evaluated
|
|
56
|
+
* here rather than through the `booleanValue` function so that one engine answers all three.
|
|
57
|
+
* `xpath2` is the effective boolean value **with the specification's string rule**: see
|
|
58
|
+
* {@link xpath2BooleanValue}.
|
|
59
|
+
*/
|
|
60
|
+
booleanValue(expression, context, namespaces, mode) {
|
|
61
|
+
switch (mode) {
|
|
62
|
+
case 'java': {
|
|
63
|
+
const value = this.selectValue(expression, context, namespaces);
|
|
64
|
+
return typeof value === 'boolean' ? value : String(value !== null && value !== void 0 ? value : '').toLowerCase() === 'true';
|
|
65
|
+
}
|
|
66
|
+
case 'xpath1':
|
|
67
|
+
// XPath 1.0's boolean() rules, asked for in that language so that 3.1's do not apply.
|
|
68
|
+
return this.fonto().evaluateXPathToBoolean(`boolean(${expression})`, context, null, null, { ...this.options(namespaces), language: 'XPath3.1' });
|
|
69
|
+
default:
|
|
70
|
+
return this.xpath2BooleanValue(expression, context, namespaces);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The `xpath2` mode of table 7, **as docx4j does it**: the expression is wrapped in
|
|
75
|
+
* `xs:boolean(...)` and the engine performs the cast, which is `XmlPart.cachedXPathGetBoolean`
|
|
76
|
+
* with `opendope.conditions.Xpathref.XPathBoolean=cast2` (measured against Saxon-HE 9.9.0-2 by
|
|
77
|
+
* the docx4j session, 2026-09-25, and matched here case for case). Doing what docx4j does,
|
|
78
|
+
* rather than casting by hand, is what keeps the two from drifting: the lexical space, the
|
|
79
|
+
* whitespace collapse, the multi-item rule and the empty sequence all come from the engine.
|
|
80
|
+
*
|
|
81
|
+
* So: `true`, `1` are true; `false`, `0` are false; leading and trailing space is collapsed
|
|
82
|
+
* first, so `" false "` is false; an expression selecting **nothing** is false; and anything
|
|
83
|
+
* else raises - `"yes"`, `"True"` (`xs:boolean`'s lexical space is case-sensitive where the
|
|
84
|
+
* `java` mode ignores case), `""` from an empty element, and a sequence of more than one node.
|
|
85
|
+
*
|
|
86
|
+
* A raise is not a false. docx4j turns it into an `InputIntegrityException` and the whole bind
|
|
87
|
+
* fails; here it is a `Docx4JException` from this call, and what a processor does with it is
|
|
88
|
+
* the processor's business (CR-005 phase B). That is the one place the two behaviours are not
|
|
89
|
+
* yet the same thing, and the specification does not say which is right.
|
|
90
|
+
*/
|
|
91
|
+
xpath2BooleanValue(expression, context, namespaces) {
|
|
92
|
+
try {
|
|
93
|
+
return this.fonto().evaluateXPathToBoolean(`xs:boolean(${expression})`, context, null, null, this.options(namespaces));
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
throw new Docx4JException(`The xpath2 boolean conversion mode casts to xs:boolean, which accepts only "true", "false", "1" and "0", `
|
|
97
|
+
+ `and one value at a time: ${expression} gives none of them`, { cause: e });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
options(namespaces) {
|
|
101
|
+
return { namespaceResolver: (prefix) => { var _a; return (_a = namespaces[prefix]) !== null && _a !== void 0 ? _a : null; } };
|
|
102
|
+
}
|
|
103
|
+
fonto() {
|
|
104
|
+
if (!this.module) {
|
|
105
|
+
throw new Docx4JException('The XPath engine is not ready yet: await pkg.customXmlParts.load() (or pkg.xpathEngine.ready()) once before selecting nodes');
|
|
106
|
+
}
|
|
107
|
+
return this.module;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** A FontoXPath result in the four-type union `selectValue` reports. */
|
|
111
|
+
function valueOf(value) {
|
|
112
|
+
var _a;
|
|
113
|
+
if (value === null || value === undefined)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')
|
|
116
|
+
return value;
|
|
117
|
+
if (Array.isArray(value))
|
|
118
|
+
return value.length === 0 ? undefined : valueOf(value[0]);
|
|
119
|
+
if (typeof value === 'object' && typeof value.nodeType === 'number') {
|
|
120
|
+
return (_a = value.textContent) !== null && _a !== void 0 ? _a : '';
|
|
121
|
+
}
|
|
122
|
+
return String(value);
|
|
123
|
+
}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
/** The XPath result kinds `selectValue` can report. */
|
|
2
2
|
export type XPathValue = string | number | boolean | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* How an XPaths entry becomes a boolean (OpenDoPE specification v3 section 7.2, table 7, declared
|
|
5
|
+
* in the XPaths part's `booleanConversion` attribute; CR-005 phase A):
|
|
6
|
+
*
|
|
7
|
+
* - `java`: the string value is true only if it equals `true`, ignoring case. `"1"` and `"yes"`
|
|
8
|
+
* are false. docx4j's `Boolean.parseBoolean`, and the default for a template that declares no
|
|
9
|
+
* mode.
|
|
10
|
+
* - `xpath1`: XPath 1.0 `boolean()`. A node-set is true if non-empty, a string if non-empty - so
|
|
11
|
+
* `"false"` is **true**.
|
|
12
|
+
* - `xpath2`: the effective boolean value, a string cast to `xs:boolean` accepting only `true`,
|
|
13
|
+
* `false`, `1` and `0` - so `"false"` is false and `"yes"` is an **error**. Needs an XPath 2.0
|
|
14
|
+
* engine: `FontoXPathEngine` from `@docx4j/core-ts/xpath-fonto`.
|
|
15
|
+
*/
|
|
16
|
+
export type BooleanConversionMode = 'java' | 'xpath1' | 'xpath2';
|
|
3
17
|
/**
|
|
4
18
|
* XPath 1.0 over a DOM (CR-002 section 3.6). `select` answers a node set; `selectValue` answers a
|
|
5
19
|
* string, number or boolean result. `namespaces` maps prefixes used in the expression to URIs.
|
|
@@ -11,7 +25,25 @@ export interface XPathEngine {
|
|
|
11
25
|
readonly isReady: boolean;
|
|
12
26
|
select(expression: string, context: Node, namespaces?: Record<string, string>): Node[];
|
|
13
27
|
selectValue(expression: string, context: Node, namespaces?: Record<string, string>): XPathValue;
|
|
28
|
+
/**
|
|
29
|
+
* The expression as a boolean in one conversion mode, for an engine that can do more than the
|
|
30
|
+
* function {@link booleanValue} can do over `selectValue` - in practice `xpath2`, which needs an
|
|
31
|
+
* XPath 2.0 evaluator. Optional: `XPathEngine` is public and a consumer may implement it, so a
|
|
32
|
+
* required member would break every implementation that predates this. Callers go through
|
|
33
|
+
* {@link booleanValue}, never here.
|
|
34
|
+
*/
|
|
35
|
+
booleanValue?(expression: string, context: Node, namespaces: Record<string, string>, mode: BooleanConversionMode): boolean;
|
|
14
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The expression as a boolean in a declared conversion mode (table 7 above).
|
|
39
|
+
*
|
|
40
|
+
* `java` and `xpath1` need nothing an `XPathEngine` does not already have, so they work over any
|
|
41
|
+
* engine: `java` is the string value compared with `true`, `xpath1` is XPath 1.0 `boolean()` of the
|
|
42
|
+
* expression. `xpath2` is delegated to the engine's own `booleanValue` when it has one, and
|
|
43
|
+
* otherwise throws naming the engine to install - which is the specification's REQ-032, a processor
|
|
44
|
+
* evaluating in the declared mode or refusing to process the template.
|
|
45
|
+
*/
|
|
46
|
+
export declare function booleanValue(engine: XPathEngine, expression: string, context: Node, namespaces?: Record<string, string>, mode?: BooleanConversionMode): boolean;
|
|
15
47
|
/**
|
|
16
48
|
* The default engine: `document.evaluate` where the runtime has it, else the `xpath` package,
|
|
17
49
|
* imported on `ready()`. The import specifier is a variable so that a bundler does not follow it
|
|
@@ -12,6 +12,34 @@
|
|
|
12
12
|
// afterwards is synchronous. A call before the engine is ready throws, naming the fix.
|
|
13
13
|
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
14
14
|
import { serializeXml } from '../../xml/dom.mjs';
|
|
15
|
+
/**
|
|
16
|
+
* The expression as a boolean in a declared conversion mode (table 7 above).
|
|
17
|
+
*
|
|
18
|
+
* `java` and `xpath1` need nothing an `XPathEngine` does not already have, so they work over any
|
|
19
|
+
* engine: `java` is the string value compared with `true`, `xpath1` is XPath 1.0 `boolean()` of the
|
|
20
|
+
* expression. `xpath2` is delegated to the engine's own `booleanValue` when it has one, and
|
|
21
|
+
* otherwise throws naming the engine to install - which is the specification's REQ-032, a processor
|
|
22
|
+
* evaluating in the declared mode or refusing to process the template.
|
|
23
|
+
*/
|
|
24
|
+
export function booleanValue(engine, expression, context, namespaces = {}, mode = 'java') {
|
|
25
|
+
if (engine.booleanValue)
|
|
26
|
+
return engine.booleanValue(expression, context, namespaces, mode);
|
|
27
|
+
switch (mode) {
|
|
28
|
+
case 'java': {
|
|
29
|
+
const value = engine.selectValue(expression, context, namespaces);
|
|
30
|
+
return typeof value === 'boolean' ? value : String(value !== null && value !== void 0 ? value : '').toLowerCase() === 'true';
|
|
31
|
+
}
|
|
32
|
+
case 'xpath1': {
|
|
33
|
+
// boolean() is XPath 1.0's own conversion, so the engine applies the rules rather than this.
|
|
34
|
+
const value = engine.selectValue(`boolean(${expression})`, context, namespaces);
|
|
35
|
+
return value === true;
|
|
36
|
+
}
|
|
37
|
+
default:
|
|
38
|
+
throw new Docx4JException(`The boolean conversion mode "${mode}" needs an XPath 2.0 engine: `
|
|
39
|
+
+ "import { FontoXPathEngine } from '@docx4j/core-ts/xpath-fonto' (npm install fontoxpath) "
|
|
40
|
+
+ 'and set pkg.xpathEngine to it');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
15
43
|
// XPathResult constants, spelled out because `XPathResult` is not a global in Node.
|
|
16
44
|
const ANY_TYPE = 0;
|
|
17
45
|
const NUMBER_TYPE = 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docx4j/core-ts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "docx4j-core for TypeScript: Open Packaging (docx/pptx/xlsx zip and flat OPC), typed parts and relationships, style, numbering and font resolution over @docx4j/generated-objects-ts. Node, browsers, Word add-ins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"./office-js": {
|
|
34
34
|
"types": "./dist/office-js/index.d.mts",
|
|
35
35
|
"import": "./dist/office-js/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./xpath-fonto": {
|
|
38
|
+
"types": "./dist/model/customxml/xpath-fonto.d.mts",
|
|
39
|
+
"import": "./dist/model/customxml/xpath-fonto.mjs"
|
|
36
40
|
}
|
|
37
41
|
},
|
|
38
42
|
"files": [
|
|
@@ -79,13 +83,18 @@
|
|
|
79
83
|
"fflate": "^0.8.2"
|
|
80
84
|
},
|
|
81
85
|
"devDependencies": {
|
|
86
|
+
"fontoxpath": "^3.34.0",
|
|
82
87
|
"typescript": "~5.6.3",
|
|
83
88
|
"xpath": "^0.0.34"
|
|
84
89
|
},
|
|
85
90
|
"peerDependencies": {
|
|
91
|
+
"fontoxpath": "^3.30.0",
|
|
86
92
|
"xpath": "^0.0.34"
|
|
87
93
|
},
|
|
88
94
|
"peerDependenciesMeta": {
|
|
95
|
+
"fontoxpath": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
89
98
|
"xpath": {
|
|
90
99
|
"optional": true
|
|
91
100
|
}
|