Search code examples
node.jstypescriptxpathsaxon-js

How to handle XPath expressions with saxon-js in Node


I have written the following class in Typescript:

import * as saxon from 'saxon-js';

export class XsltXpath {

    private static readonly SINGLETON: XsltXpath = new XsltXpath();

    static transform(styleSheetFile: string, sourceFile: string): any {
        return XsltXpath.SINGLETON.transformViaXsl(styleSheetFile, sourceFile);
    }

    static pick(sourceFile: string, xpathQuery: string): any {
        return XsltXpath.SINGLETON.pickViaXpath(sourceFile, xpathQuery);
    }

    private async transformViaXsl(styleSheetFile: string, sourceFile: string): Promise<any> {

        const output: { principalResult: any; } = await saxon.transform({
            stylesheetFileName: styleSheetFile,
            sourceFileName: sourceFile,
            destination: 'serialized'
        }, 'async');

        return output.principalResult;
    }

    private pickViaXpath(sourceFile: string, xpathQuery: string): any {
        const doc = saxon.getResource({
            file: sourceFile,
            type: "xml"
        });
        const result = saxon.XPath.evaluate(xpathQuery, doc);
        const output = saxon.serialize(result, { method: 'xml', indent: true, 'omit-xml-declaration': true });
        console.log(output);
        return output;
    }
}

...and I´m using it like this:

const output: any = await XsltXpath.transform('./testdata/stylesheet.sef.json', './testdata/data.xml');
    console.log('OUTPUT: ', output);
    res.send(output);

OR

    const output: any = await XsltXpath.pick('./testdata/data.xml', 'catalog/cd//artist');
    console.log('OUTPUT: ', output);
    res.send(output);

The "transform"-Method works fine, but when I use the "pick"-Method, I always get this error:

message: 'Context item for child axis is not a node - supplied:HashTrie map{}',
  name: 'XError',
  code: 'XPTY0020'

My XPath-Expression is valid on this kind of testdata, so I assume that I have to provide the expression in another way:

<catalog>
  <cd>
    <title>Empire Burlesque</title>
    <artist>Bob Dylan</artist>
    <country>USA</country>
    <company>Columbia</company>
    <price>10.90</price>
    <year>1985</year>
  </cd>
...
</catalog>

What am I doing wrong, thx.


Solution

  • SaxonJS.getResource() is asynchronous and returns a Promise; I think you have supplied this Promise to SaxonJS.XPath.Evaluate(), which is treating it as a general Javascript object.

    You need something like

    saxon.getResource({
                file: sourceFile,
                type: "xml"
            })
    .then((doc) => saxon.XPath.evaluate(xpathQuery, doc))
    

    Of course if you don't want the whole thing to be asynchronous, you don't have to use saxon.getResource() - doc is just a DOM document node, which you can create any way you like.