Description" /> Description" /> Description"/>

How to get variable in xml file using saxonjs and nodejs

100 views Asked by At

I have a xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE agent SYSTEM "http://www.someUrl.com">
<myData>
   <service>
      <description>Description</description>
   </service>
   <courier>
      <listener port="55556"
                />
      <mainService
      name="Some Name"
      port="55555"
      />
   </courier>
</myData>

and I want to get the value of the listener port variable using nodejs. According to the docu for SaxonJs SaxonJS.XPath.evaluate is the method I need. It requires a query and a document node object. When I convert my xml file into dom object I get the error message : "Uncaught TypeError TypeError: Cannot read properties of undefined (reading 'length')"

const saxonJs = require('saxon-js');
const fs      = require('node:fs');
const jsdom = require("jsdom");

var xmlFile   = fs.readFileSync(xmlFilePath, 'utf8');

var doc = new jsdom.JSDOM(xml);
var xpathQuery  = '//listener/@port';
var result      = saxonJs.XPath.evaluate(xpathQuery, doc);
3

There are 3 answers

0
ggorlen On

Do you need to use XPath in particular? Since you have JSDOM, I'd just query using a CSS selector:

import {JSDOM} from "jsdom"; // ^24.0.0

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE agent SYSTEM "http://www.someUrl.com">
<myData>
   <service>
      <description>Description</description>
   </service>
   <courier>
      <listener port="55556"
                />
      <mainService
      name="Some Name"
      port="55555"
      />
   </courier>
</myData>`;

const dom = new JSDOM(xml, {contentType: "application/xml"});
const {document} = dom.window;
const port = document.querySelector("listener").getAttribute("port");
console.log(port); // => 55556

You can use JSDOM.fromFile to read from disk without using fs (although that's fine too).

If you don't mind querying with CSS, Cheerio provides an even cleaner option:

const cheerio = require("cheerio"); // ^1.0.0-rc.12

const xml = `...`;
console.log(cheerio.load(xml, {xml: true})("listener").attr("port"));

If you really want to use XPath, you can do it in JSDOM (note the [@port] change to select by attribute on the listener element):

const port = document
  .evaluate(
    "//listener[@port]",
    document,
    null,
    9, // FIRST_ORDERED_NODE_TYPE
  )
  .singleNodeValue
  .getAttribute("port");

or selecting the attribute string value directly:

const port = document
  .evaluate(
    "//listener/@port",
    document,
    null,
    2, // STRING_TYPE
  )
  .stringValue;

Note that using both JSDOM and Saxon is probably unnecessary, since Saxon can parse the XML without support from JSDOM. Pick one or the other.

I'm not familiar with Saxon, but at the time of writing, JSDOM has 22,000,000 weekly downloads and 831 questions on SO, while saxon-js has 20,000 weekly downloads and 61 questions on SO. My instinct is to avoid the dependency on saxon-js if possible, since small packages tend to become unmaintained, and are harder to find support for on Stack Overflow and GitHub if you run into problems.

On the other hand, apparently JSDOM XPath has "quite a few issues", and JSDOM is (probably) a much more complex library, so you may have a good reason for pursuing Saxon if it's a better tool for your particular use case (i.e. you need robust XML support, don't care about the whole DOM API, and don't mind the support/maintenance cost of using a less popular library).

8
Martin Honnen On

To give you an example for SaxonJS with Node.js:

const SaxonJS = require("saxon-js");

SaxonJS.getResource({ file: 'input-sample.xml', type: 'xml' })
.then(doc => { console.log(SaxonJS.XPath.evaluate('//listener/@port/xs:integer(.)', doc)) });

Of course you can also try to select the @port attribute node and output its value with e.g.

const SaxonJS = require("saxon-js");

SaxonJS.getResource({ file: 'saxon-js-input-sample1.xml', type: 'xml' })
.then(doc => { var port = SaxonJS.XPath.evaluate('//listener/@port', doc); if (port !=null) console.log(port.value) });

Or in the ESM module world as

import SaxonJS from 'saxon-js';

const doc = await SaxonJS.getResource({ file: 'saxon-js-input-sample1.xml', type: 'xml' });

var port = SaxonJS.XPath.evaluate('//listener/@port', doc); 

if (port !=null) 
  console.log(port.value);
0
Michael Kay On

SaxonJS doesn't support all DOM implementations. On the browser it only works with the native browser implementation, in node.js it only works with its own DOM implementation which is xmldom modified with a few bug fixes.

If you want to take advantage of the XPath 3.1 capabilities in SaxonJS, then you need to use the SaxonJS embedded DOM implementation. On the other hand, if you only need XPath 1.0, then most DOM implementations offer this, though not always with a high level of conformance or performance.