Skip to content

Commit d0c1264

Browse files
2 parents 4e7ad90 + 1e9b871 commit d0c1264

3 files changed

Lines changed: 60 additions & 2 deletions

File tree

src/expressions/arithmetic-expression.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import { XPathContext } from '../context';
2222
import { XPathExpression } from './expression';
23+
import { getStringValueFromNode } from './node-utils';
2324

2425
export type ArithmeticOperator = '+' | '-' | '*' | 'div' | 'idiv' | 'mod';
2526

@@ -114,6 +115,9 @@ export class XPathArithmeticExpression extends XPathExpression {
114115

115116
// Single atomic value
116117
if (!Array.isArray(value)) {
118+
// Raw DOM node — extract its string value for numeric conversion
119+
const text = getStringValueFromNode(value);
120+
if (text !== null) return text;
117121
return value;
118122
}
119123

src/expressions/node-utils.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Utility for extracting string values from DOM nodes (XNode / XPathNode objects).
3+
*
4+
* XNode element nodes store `nodeValue` as the literal string `"null"` because
5+
* their constructor coerces the value via template literals. Actual text content
6+
* lives in child text nodes (nodeType 3). This helper traverses the child tree
7+
* to produce the correct string value.
8+
*/
9+
10+
/**
11+
* Extract the string value from a DOM node object.
12+
*
13+
* Returns `null` if `node` is not a recognizable node object (e.g. a plain
14+
* number, boolean, or string), so callers can fall through to other logic.
15+
*/
16+
export function getStringValueFromNode(node: any): string | null {
17+
if (node === null || node === undefined) return null;
18+
if (typeof node !== 'object') return null;
19+
if (typeof node.nodeType !== 'number') return null;
20+
21+
// Text node or attribute node — value is stored directly in nodeValue
22+
if (node.nodeType === 3 || node.nodeType === 2) {
23+
const v = node.nodeValue;
24+
return (v !== null && v !== undefined && v !== 'null') ? String(v) : '';
25+
}
26+
27+
// Element node (or document node) — text content is in descendant text nodes
28+
if (node.nodeType === 1 || node.nodeType === 9) {
29+
const children: any[] = node.childNodes;
30+
if (!children || children.length === 0) return '';
31+
32+
let text = '';
33+
for (const child of children) {
34+
if (child.nodeType === 3) {
35+
// Text node
36+
const v = child.nodeValue;
37+
if (v !== null && v !== undefined && v !== 'null') {
38+
text += String(v);
39+
}
40+
} else if (child.nodeType === 1) {
41+
// Recurse into child elements
42+
text += getStringValueFromNode(child) ?? '';
43+
}
44+
// Skip attribute nodes (nodeType 2) and others
45+
}
46+
return text;
47+
}
48+
49+
return null;
50+
}

src/expressions/unary-expression.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import { XPathContext } from '../context';
1616
import { XPathExpression } from './expression';
17+
import { getStringValueFromNode } from './node-utils';
1718

1819
/**
1920
* UnaryExpression - Unary plus and minus operations
@@ -71,6 +72,9 @@ export class XPathUnaryExpression extends XPathExpression {
7172

7273
// Single atomic value
7374
if (!Array.isArray(value)) {
75+
// Raw DOM node — extract its string value for numeric conversion
76+
const text = getStringValueFromNode(value);
77+
if (text !== null) return text;
7478
return value;
7579
}
7680

@@ -79,8 +83,8 @@ export class XPathUnaryExpression extends XPathExpression {
7983
return null; // Empty sequence
8084
}
8185

82-
// Multiple items - use first
83-
return value[0];
86+
// Multiple items - use first (recursive to handle nested node objects)
87+
return this.atomize(value[0]);
8488
}
8589

8690
/**

0 commit comments

Comments
 (0)