From 6c30391f3243d5be282f488132b815ce39f41f66 Mon Sep 17 00:00:00 2001 From: Leonel Sanches da Silva <53848829+leonelsanchesdasilva@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:59:50 -0800 Subject: [PATCH 1/2] Implementing `json-to-xml()` support in XSLT. --- README.md | 136 +++++----- src/dom/functions.ts | 2 +- src/dom/util.ts | 2 +- src/dom/xmltoken.ts | 2 +- src/xpath/index.ts | 2 +- src/xpath/lib | 2 +- src/xpath/match-resolver.ts | 2 +- src/xpath/tokens.ts | 2 +- src/xpath/xpath.ts | 112 +++++++- src/xslt/xslt.ts | 2 +- tests/dom.test.tsx | 2 +- tests/json-to-xml.test.tsx | 383 ++++++++++++++++++++++++++++ tests/xml/xmltoken.test.tsx | 2 +- tests/xslt/xslt-validation.test.tsx | 2 +- tests/xslt/xslt.test.tsx | 2 +- 15 files changed, 573 insertions(+), 82 deletions(-) create mode 100644 tests/json-to-xml.test.tsx diff --git a/README.md b/README.md index c91e84d..ae03073 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ const xslt = new Xslt(); const xPath = xslt.xPath; ``` -Or ou can import it like this: +Or you can import it like this: ```js import { XPath } from 'xslt-processor' @@ -70,6 +70,8 @@ import { XPath } from 'xslt-processor' const xPath = new XPath(); ``` +`XPath` class is an external dependency, [living in its own repository](https://github.com/DesignLiquido/xpath). + If you write pre-2015 JS code, make adjustments as needed. ### `Xslt` class options @@ -145,76 +147,11 @@ console.log(parsed.root.users.user); // ["Alice", "Bob"] You can simply add a tag like this: ```html - + ``` All the exports will live under `globalThis.XsltProcessor` and `window.XsltProcessor`. [See a usage example here](https://github.com/DesignLiquido/xslt-processor/blob/main/interactive-tests/xslt.html). -### Breaking Changes - -#### Version 2 - -Until version 2.3.1, use like the example below: - -```js -import { Xslt, XmlParser } from 'xslt-processor' - -// xmlString: string of xml file contents -// xsltString: string of xslt file contents -// outXmlString: output xml string. -const xslt = new Xslt(); -const xmlParser = new XmlParser(); -const outXmlString = xslt.xsltProcess( // Not async. - xmlParser.xmlParse(xmlString), - xmlParser.xmlParse(xsltString) -); -``` - -Version 3 received `` which relies on Fetch API, which is asynchronous. Version 2 doesn't support ``. - -If using Node.js older than version v17.5.0, please use version 3.2.3, that uses `node-fetch` package. Versions 3.3.0 onward require at least Node.js version v17.5.0, since they use native `fetch()` function. - -#### Version 1 - -Until version 1.2.8, use like the example below: - -```js -import { Xslt, xmlParse } from 'xslt-processor' - -// xmlString: string of xml file contents -// xsltString: string of xslt file contents -// outXmlString: output xml string. -const xslt = new Xslt(); -const outXmlString = xslt.xsltProcess( - xmlParse(xmlString), - xmlParse(xsltString) -); -``` - -#### Version 0 - -Until version 0.11.7, use like the example below: - -```js -import { xsltProcess, xmlParse } from 'xslt-processor' - -// xmlString: string of xml file contents -// xsltString: string of xslt file contents -// outXmlString: output xml string. -const outXmlString = xsltProcess( - xmlParse(xmlString), - xmlParse(xsltString) -); -``` - -and to access the XPath parser: - -```js -import { xpathParse } from 'xslt-processor' -``` - -These functions are part of `Xslt` and `XPath` classes, respectively, at version 1.x onward. - ## Introduction XSLT-processor contains an implementation of XSLT in JavaScript. Because XSLT uses XPath, it also contains an implementation of XPath that can be used @@ -312,6 +249,71 @@ Use `` to preserve whitespace in specific elements, overridi 3. `xsl:strip-space` applies to remaining matches 4. By default (no declarations), whitespace is preserved +### Breaking Changes + +#### Version 2 + +Until version 2.3.1, use like the example below: + +```js +import { Xslt, XmlParser } from 'xslt-processor' + +// xmlString: string of xml file contents +// xsltString: string of xslt file contents +// outXmlString: output xml string. +const xslt = new Xslt(); +const xmlParser = new XmlParser(); +const outXmlString = xslt.xsltProcess( // Not async. + xmlParser.xmlParse(xmlString), + xmlParser.xmlParse(xsltString) +); +``` + +Version 3 received `` which relies on Fetch API, which is asynchronous. Version 2 doesn't support ``. + +If using Node.js older than version v17.5.0, please use version 3.2.3, that uses `node-fetch` package. Versions 3.3.0 onward require at least Node.js version v17.5.0, since they use native `fetch()` function. + +#### Version 1 + +Until version 1.2.8, use like the example below: + +```js +import { Xslt, xmlParse } from 'xslt-processor' + +// xmlString: string of xml file contents +// xsltString: string of xslt file contents +// outXmlString: output xml string. +const xslt = new Xslt(); +const outXmlString = xslt.xsltProcess( + xmlParse(xmlString), + xmlParse(xsltString) +); +``` + +#### Version 0 + +Until version 0.11.7, use like the example below: + +```js +import { xsltProcess, xmlParse } from 'xslt-processor' + +// xmlString: string of xml file contents +// xsltString: string of xslt file contents +// outXmlString: output xml string. +const outXmlString = xsltProcess( + xmlParse(xmlString), + xmlParse(xsltString) +); +``` + +and to access the XPath parser: + +```js +import { xpathParse } from 'xslt-processor' +``` + +These functions are part of `Xslt` and `XPath` classes, respectively, at version 1.x onward. + ## References - XPath Specification: http://www.w3.org/TR/1999/REC-xpath-19991116 diff --git a/src/dom/functions.ts b/src/dom/functions.ts index c3be6ad..8f77f60 100644 --- a/src/dom/functions.ts +++ b/src/dom/functions.ts @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Copyright 2018 Johannes Wilm // Copyright 2005 Google Inc. // All Rights Reserved diff --git a/src/dom/util.ts b/src/dom/util.ts index 34ba658..c1df578 100644 --- a/src/dom/util.ts +++ b/src/dom/util.ts @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Copyright 2018 Johannes Wilm // Copyright 2005 Google // diff --git a/src/dom/xmltoken.ts b/src/dom/xmltoken.ts index 179b27d..1f4bf10 100644 --- a/src/dom/xmltoken.ts +++ b/src/dom/xmltoken.ts @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Copyright 2018 Johannes Wilm // Copyright 2006 Google Inc. // All Rights Reserved diff --git a/src/xpath/index.ts b/src/xpath/index.ts index 06f86f7..de28019 100644 --- a/src/xpath/index.ts +++ b/src/xpath/index.ts @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // XPath implementation exports // Main XPath class diff --git a/src/xpath/lib b/src/xpath/lib index eee689c..baf6329 160000 --- a/src/xpath/lib +++ b/src/xpath/lib @@ -1 +1 @@ -Subproject commit eee689c487ff2a67d50fc166541959dd6611711f +Subproject commit baf632939603e298c579655d00959e29fc66ca13 diff --git a/src/xpath/match-resolver.ts b/src/xpath/match-resolver.ts index bf3d6c3..1972ced 100644 --- a/src/xpath/match-resolver.ts +++ b/src/xpath/match-resolver.ts @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Match resolver that works with the new XPath implementation. import { XNode } from '../dom'; diff --git a/src/xpath/tokens.ts b/src/xpath/tokens.ts index 1e05e02..10b7688 100644 --- a/src/xpath/tokens.ts +++ b/src/xpath/tokens.ts @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // XPath tokens and axis constants // The axes of XPath expressions. diff --git a/src/xpath/xpath.ts b/src/xpath/xpath.ts index 9f61599..7f8eca0 100644 --- a/src/xpath/xpath.ts +++ b/src/xpath/xpath.ts @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // XPath adapter that uses the new lexer/parser implementation // while maintaining backward compatibility with the existing XSLT API. @@ -8,6 +8,7 @@ import { XPathParser } from './lib/src/parser'; import { XPathExpression, XPathLocationPath, XPathUnionExpression } from './lib/src/expressions'; import { XPathContext, XPathResult, createContext } from './lib/src/context'; import { XPathNode } from './lib/src/node'; +import { JsonToXmlConverter } from './lib/src/expressions/json-to-xml-converter'; import { ExprContext } from './expr-context'; import { NodeValue, StringValue, NumberValue, BooleanValue, NodeSetValue } from './values'; @@ -134,6 +135,7 @@ class NodeConverter { variables: this.convertVariables(exprContext), functions: this.createCustomFunctions(exprContext), namespaces: exprContext.knownNamespaces, + xsltVersion: exprContext.xsltVersion, }); } @@ -202,8 +204,14 @@ class NodeConverter { */ xPathNodeToXNode(xpathNode: XPathNode): XNode | null { if (!xpathNode) return null; - // The nodes are already XNodes, just cast back - return xpathNode as unknown as XNode; + + // Check if this is already an XNode (from native parsing) + if (xpathNode.constructor && xpathNode.constructor.name === 'XNode') { + return xpathNode as unknown as XNode; + } + + // Otherwise, convert XPathNode interface (from json-to-xml or xpath/lib) to XNode + return this.convertXPathNodeToXNode(xpathNode); } /** @@ -304,9 +312,107 @@ class NodeConverter { return this.xmlToJson(node); }; + // json-to-xml() function - XSLT 3.0 specific + functions['json-to-xml'] = (jsonText: any) => { + // Check XSLT version - only supported in 3.0 + if (exprContext.xsltVersion === '1.0') { + throw new Error('json-to-xml() is not supported in XSLT 1.0. Use version="3.0" in your stylesheet.'); + } + + // Handle node set or single value + const jsonStr = Array.isArray(jsonText) ? jsonText[0] : jsonText; + if (!jsonStr) { + return null; + } + + // Convert JSON string to XML document node using xpath lib converter + const converter = new JsonToXmlConverter(); + const xpathNode = converter.convert(String(jsonStr)); + + if (!xpathNode) { + return null; + } + + // Get owner document from context + const ownerDoc = exprContext.nodeList && exprContext.nodeList.length > 0 + ? exprContext.nodeList[0].ownerDocument + : null; + + // Convert XPathNode interface tree to actual XNode objects + const convertedNode = this.convertXPathNodeToXNode(xpathNode, ownerDoc); + + // Return as array for consistency with xpath processor + return convertedNode ? [convertedNode] : null; + }; + return functions; } + /** + * Convert an XPathNode interface tree to actual XNode objects. + * This is needed to convert json-to-xml() output to XSLT-compatible nodes. + */ + private convertXPathNodeToXNode(xpathNode: XPathNode, ownerDoc?: any): XNode | null { + if (!xpathNode) { + return null; + } + + const { XNode: XNodeClass } = require('../dom'); + const { DOM_DOCUMENT_NODE, DOM_TEXT_NODE, DOM_ELEMENT_NODE } = require('../constants'); + + let node: XNode; + + if (xpathNode.nodeType === DOM_DOCUMENT_NODE) { + // For document nodes, extract and return the root element + if (xpathNode.childNodes && xpathNode.childNodes.length > 0) { + const rootChild = xpathNode.childNodes[0] as any; + node = this.convertXPathNodeToXNode(rootChild, ownerDoc); + return node; + } + return null; + } else if (xpathNode.nodeType === DOM_TEXT_NODE) { + // Create a text node + const textContent = xpathNode.textContent || ''; + node = new XNodeClass( + DOM_TEXT_NODE, + '#text', + textContent, + ownerDoc + ); + } else { + // Element node (DOM_ELEMENT_NODE) + node = new XNodeClass( + DOM_ELEMENT_NODE, + xpathNode.nodeName || 'element', + '', + ownerDoc + ); + + // Copy namespace URI if present + if (xpathNode.namespaceUri) { + node.namespaceUri = xpathNode.namespaceUri; + } + + // Recursively convert child nodes + if (xpathNode.childNodes && xpathNode.childNodes.length > 0) { + for (let i = 0; i < xpathNode.childNodes.length; i++) { + const childXPathNode = xpathNode.childNodes[i] as any; + const childXNode = this.convertXPathNodeToXNode(childXPathNode, ownerDoc); + if (childXNode) { + childXNode.parentNode = node; + node.childNodes.push(childXNode); + } + } + if (node.childNodes.length > 0) { + node.firstChild = node.childNodes[0]; + node.lastChild = node.childNodes[node.childNodes.length - 1]; + } + } + } + + return node; + } + /** * Convert an XML node to a JSON string representation. * This is a simplified implementation of XSLT 3.0's xml-to-json(). diff --git a/src/xslt/xslt.ts b/src/xslt/xslt.ts index 6c6692e..325e055 100644 --- a/src/xslt/xslt.ts +++ b/src/xslt/xslt.ts @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Copyright 2018 Johannes Wilm // Copyright 2005 Google Inc. // All Rights Reserved diff --git a/tests/dom.test.tsx b/tests/dom.test.tsx index 24c0d61..d2fab6e 100644 --- a/tests/dom.test.tsx +++ b/tests/dom.test.tsx @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Copyright 2018 Johannes Wilm // Copyright 2005 Google Inc. // All Rights Reserved diff --git a/tests/json-to-xml.test.tsx b/tests/json-to-xml.test.tsx new file mode 100644 index 0000000..1b120de --- /dev/null +++ b/tests/json-to-xml.test.tsx @@ -0,0 +1,383 @@ +/* eslint-disable no-useless-escape */ +import assert from 'assert'; + +import { Xslt } from '../src/xslt'; +import { XmlParser } from '../src/dom'; + +describe('json-to-xml', () => { + it('json-to-xml() should throw error in XSLT 1.0', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + + await assert.rejects( + async () => await xsltClass.xsltProcess(xml, xslt), + { + message: /json-to-xml\(\) is not supported in XSLT 1\.0/ + } + ); + }); + + it('json-to-xml() should convert simple string JSON in XSLT 3.0', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain a root element with text content "hello" + assert(outXmlString.includes('hello')); + }); + + it('json-to-xml() should convert simple number JSON', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain the number 42 + assert(outXmlString.includes('42')); + }); + + it('json-to-xml() should convert boolean true', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain "true" + assert(outXmlString.includes('true')); + }); + + it('json-to-xml() should convert boolean false', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain "false" + assert(outXmlString.includes('false')); + }); + + it('json-to-xml() should convert null JSON', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain an empty root element + assert(outXmlString.includes(' { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain name and age elements + assert(outXmlString.includes('name')); + assert(outXmlString.includes('John')); + assert(outXmlString.includes('age')); + assert(outXmlString.includes('30')); + }); + + it('json-to-xml() should convert nested objects', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain nested person element + assert(outXmlString.includes('person')); + assert(outXmlString.includes('name')); + assert(outXmlString.includes('John')); + }); + + it('json-to-xml() should handle object with null value', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain value element (empty) + assert(outXmlString.includes('value')); + }); + + it('json-to-xml() should convert simple array', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain item elements + assert(outXmlString.includes('item')); + assert(outXmlString.includes('1')); + assert(outXmlString.includes('2')); + assert(outXmlString.includes('3')); + }); + + it('json-to-xml() should convert array of objects', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain multiple item elements with id + assert(outXmlString.includes('item')); + assert(outXmlString.includes('id')); + assert(outXmlString.includes('1')); + assert(outXmlString.includes('2')); + }); + + it('json-to-xml() should convert empty array', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain root element (empty) + assert(outXmlString.includes(' { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should contain items and item elements + assert(outXmlString.includes('items')); + assert(outXmlString.includes('item')); + assert(outXmlString.includes('1')); + }); + + it('json-to-xml() should sanitize property names starting with numbers', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Should convert property name to valid XML element (prefixed with underscore) + assert(outXmlString.includes('prop') || outXmlString.includes('_')); + }); + + it('json-to-xml() should handle empty and whitespace JSON strings', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + yes + + + yes + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Empty/whitespace JSON should return empty/null result + assert(outXmlString.includes('notFound')); + }); + + it('json-to-xml() should handle invalid JSON gracefully', async () => { + const xmlString = ``; + + const xsltString = ` + + + + + yes + + + yes + + + + `; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + + // Invalid JSON should return null/empty result + assert(outXmlString.includes('notFound')); + }); +}); diff --git a/tests/xml/xmltoken.test.tsx b/tests/xml/xmltoken.test.tsx index aaec736..1e4852a 100644 --- a/tests/xml/xmltoken.test.tsx +++ b/tests/xml/xmltoken.test.tsx @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Copyright 2018 Johannes Wilm // Copyright 2006, Google Inc. // All Rights Reserved diff --git a/tests/xslt/xslt-validation.test.tsx b/tests/xslt/xslt-validation.test.tsx index 3f04219..ee7250c 100644 --- a/tests/xslt/xslt-validation.test.tsx +++ b/tests/xslt/xslt-validation.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-undef */ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Tests for XSLT stylesheet/transform element validation import assert from 'assert'; diff --git a/tests/xslt/xslt.test.tsx b/tests/xslt/xslt.test.tsx index 9c71f8f..e98d1c3 100644 --- a/tests/xslt/xslt.test.tsx +++ b/tests/xslt/xslt.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-undef */ -// Copyright 2023-2024 Design Liquido +// Copyright 2023-2026 Design Liquido // Copyright 2018 Johannes Wilm // Copyright 2006, Google Inc. // All Rights Reserved. From 4958378b69ba57431b50be01a5c37e422f6c968c Mon Sep 17 00:00:00 2001 From: Leonel Sanches da Silva <53848829+leonelsanchesdasilva@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:40:04 -0800 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/xpath/xpath.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xpath/xpath.ts b/src/xpath/xpath.ts index 7f8eca0..74e3514 100644 --- a/src/xpath/xpath.ts +++ b/src/xpath/xpath.ts @@ -206,7 +206,7 @@ class NodeConverter { if (!xpathNode) return null; // Check if this is already an XNode (from native parsing) - if (xpathNode.constructor && xpathNode.constructor.name === 'XNode') { + if (xpathNode instanceof XNode) { return xpathNode as unknown as XNode; }