@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.
@@ -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.2",
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
  }