skip to Main Content

We have an ongoing project where we are porting over an older C# system that used custom functions to augment the XSLT processing, and we were planning on writing it in Node.js/saxon-js.

It seems from the documentation that event though the higher order functions feature is available to JavaScript SaxonJS, that might not be the case for the Node.js subset of it.

Is there an alternative to that, still in the realm of Node.js / server-side JavaScript runtime?

2

Answers


  1. First of all, in XSLT 3 with XPath 3.1 there is a rich built-in function library https://www.w3.org/TR/xpath-functions-31/, furthermore xsl:function since XSLT 2 allows you to implement more functions in XSLT/XPath itself (https://www.w3.org/TR/xslt-30/#stylesheet-functions).

    As for SaxonJS (tested with the current 2.5 release) and Node.js to call into JavaScript, the following is an example that works for me:

    const SaxonJS = require("saxon-js");
    
    globalThis.test1 = function() {
      return new Date().toGMTString();
    }
    
    const xslt1 = `<xsl:stylesheet  
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:js="http://saxonica.com/ns/globalJS"
      exclude-result-prefixes="#all"
      expand-text="yes">
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="/" name="xsl:initial-template">
        <test>{js:test1()}</test>
        <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
      </xsl:template>
      
    </xsl:stylesheet>`;
    
    console.log(SaxonJS.XPath.evaluate(`
      transform(
        map {
         'stylesheet-text' : $xslt,
         'delivery-format' : 'serialized'
        }
      )?output`,
      null,
      { params : { xslt: xslt1 } }
    ));
    

    Output e.g.

    <?xml version="1.0" encoding="UTF-8"?><test>Sat, 19 Aug 2023 14:35:00 GMT</test><!--Run with SaxonJS 2.5 Node.js-->
    

    Documentation https://www.saxonica.com/saxon-js/documentation2/index.html#!development/global and https://www.saxonica.com/saxon-js/documentation2/index.html#!xdm/conversions, although there the globalThis use is not mentioned; I believe that is necessary for Node as there, contrary to client-side JavaScript, a global function or var statement does not create a global function or variable.

    As for processing nodes, I would think it should be save to access the value with e.g. data() before passing to JavaScript, as in

    const SaxonJS = require("saxon-js");
    
    globalThis.test1 = function(date) {
      return new Date(date);
    }
    
    const xml1 = `<root>
      <item>
        <name>item 1</name>
        <date>Sat Aug 19 2023 17:20:07 GMT+0200</date>
      </item>
    </root>`;
    
    const xslt1 = `<xsl:stylesheet  
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:js="http://saxonica.com/ns/globalJS"
      exclude-result-prefixes="#all"
      expand-text="yes">
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="/" name="xsl:initial-template">
        <xsl:copy>
          <xsl:apply-templates/>
          <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="date">
        <xsl:copy>{js:test1(data())}</xsl:copy>
      </xsl:template>
      
    </xsl:stylesheet>`;
    
    console.log(SaxonJS.XPath.evaluate(`
      transform(
        map {
         'stylesheet-text' : $xslt,
         'source-node' : parse-xml($xml),
         'delivery-format' : 'serialized'
        }
      )?output`,
      null,
      { params : { xslt: xslt1, xml: xml1 } }
    ));
    

    I can’t tell for sure how the DOM library used by SaxonJS under Node.js and with the (clojure?) compilation exposes the usual browser know/W3C/WHATWG defined DOM properties and methods; you might need to wait until someone from Saxonica answers that separately or you can consider to ask follow-up questions with more specific details.

    As simple textContent property of an element node seems to work here:

    const SaxonJS = require("saxon-js");
    
    globalThis.test1 = function(dateNode) {
      return new Date(dateNode.textContent);
    }
    
    const xml1 = `<root>
      <item>
        <name>item 1</name>
        <date>Sat Aug 19 2023 17:20:07 GMT+0200</date>
      </item>
    </root>`;
    
    const xslt1 = `<xsl:stylesheet  
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:js="http://saxonica.com/ns/globalJS"
      exclude-result-prefixes="#all"
      expand-text="yes">
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="/" name="xsl:initial-template">
        <xsl:copy>
          <xsl:apply-templates/>
          <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="date">
        <xsl:copy>{js:test1(.)}</xsl:copy>
      </xsl:template>
      
    </xsl:stylesheet>`;
    
    console.log(SaxonJS.XPath.evaluate(`
      transform(
        map {
         'stylesheet-text' : $xslt,
         'source-node' : parse-xml($xml),
         'delivery-format' : 'serialized'
        }
      )?output`,
      null,
      { params : { xslt: xslt1, xml: xml1 } }
    ));
    
    Login or Signup to reply.
  2. Your question is about extension functions, not about higher-order functions.

    Yes, you can write extension functions in Javascript to invoke from a Saxon-JS XSLT stylesheet running under Node.js.

    Now the question becomes, what exactly are you trying to do, how are you trying to do it, and how is it failing?

    As Martin says, if you are moving from Microsoft’s XSLT 1.0 processor the first thing to do is to check whether you really need extension functions at all, because many things that needed extension functions in XSLT 1.0 can be done natively in XSLT 3.0.

    If you do need to use extension functions, then it’s a bit more complicated than in the browser because there is no simple "global object" to hang the functions on. Instead it’s probably best to supply an object whose methods you want to call as an explicit stylesheet parameter.

    Martin raised some questions about the DOM support in Node.js. SaxonJS has its own DOM implementation, forked some years ago from https://github.com/xmldom/xmldom, and nodes will be passed to your extension functions as Node objects from this library. The public methods of these objects should (if we got it right) be protected from any name-mangling by the Closure compiler. However, if you want to manipulate nodes, writing your functions in XSLT using xsl:function is usually a better option than writing them in Javascript.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search