Project

Profile

Help

Using Saxon-JS in a (pure) functional way

Added by John Lumley over 7 years ago

Matthias Hogerheijde wrote:

At EBPI we're trying to do some input-validation for some html-forms. These validations are ultimately defined within an @XBRL@ taxonomy (in layman's terms: a bunch of @xsd@, @xml@ & @xpath@ definitions) and we'd like to be able to automatically convert the validation logic to something that the browser would be able to understand. (Instead of re-implementing the validation logic in native JavaScript.)

Cue Saxon-JS! Using some black-magic we'd be able to convert those XBRL validations into XSLT and have Saxon-JS process the things we want it to.

In the browser - however - we're (currently) using React to render the UI. As far as we can tell Saxon-JS directly ties into the dom-tree of the browser and directly manipulates the browser’s dom-tree. This poses a bit of a problem for us, since we would like to generate the XSLT without any knowledge of what the specific input-elements are going to be. Secondly it seems that Saxon-JS is initialised globally once, and we have a 1 page monolith JavaScript app with multiple pages being rendered by React (while still being on the same browser page). Also; we would like to have more control over when validations run, instead of having the Saxon-JS initialiser defining the event-listeners for us.

From the documentation (http://www.saxonica.com/saxon-js/documentation/index.html#!api/transform) I understand that we could use

{ destination: "application" }

and a call-back to at least get the result of the transformation and not have it manipulate the dom. This does not solve the issue for us that we would like to have control over setting the event-listeners; apart from the note that this approach is discouraged.

For our use-case it would be really neat if we could use Saxon-JS in a more functional form; where we could give a XSLT a certain input and get a certain output that React in its turn could use to modify the browser-dom, without any side-effects. For us, it would be even better if we could have Saxon-JS run XPath as a (pure) function, where we can give it its inputs and get an output-value from it.

A second benefit of being able to use Saxon-JS in a pure functional form, would be that we'd be able to run Saxon-JS within a Web Worker.

My question(s) boils down to:

  1. Is it currently possible to use Saxon-JS in a functional way; or am I correct in thinking that it is closely tied to the browser-dom currently?
  2. Am I correct in thinking that calling SaxonJS.transform() should be called only once per page-load? 3a) if not 1), would Saxonica be willing to support the use case as described above? 3b) If 3a) is there anything we could do to help?

Kind Regards, Matthias


Replies (15)

Please register to reply

RE: Using Saxon-JS in a (pure) functional way - Added by John Lumley over 7 years ago

For our use-case it would be really neat if we could use Saxon-JS in a more functional form; where we could give a XSLT a certain input and get a certain output that React in its turn could use to modify the browser-dom, without any side-effects. For us, it would be even better if we could have Saxon-JS run XPath as a (pure) function, where we can give it its inputs and get an output-value from it.

  1. Is it currently possible to use Saxon-JS in a functional way; or am I correct in thinking that it is closely tied to the browser-dom currently?

As it happens, we've been working on an implementation of dynamic XPath execution within Saxon-JS (@xsl:evaluate@) which might give some possibilities. (This is sufficiently advanced that we use it to run QT3 tests in the browser, so there is some hope).

Any nodes within the result will be taken from any input tree, which doesn't have to be the browser dom, but can be the result of some external XSLT or perhaps a @doc()@ call. (XPath of course cannot create nodes of any form, other than copies via @copy-of()@.) Note however that the internal Saxon-JS node navigation runtime mechanism is currently tied closely the Javascript @Node@ class, as is the implementation of the @doc()@ function.

  1. Am I correct in thinking that calling SaxonJS.transform() should be called only once per page-load?

Not something we've yet tested directly - will need to think about it.

RE: Using Saxon-JS in a (pure) functional way - Added by Michael Kay over 7 years ago

To add a couple of points:

(a) we certainly envisage in the design of the product that it should be possible to do an XML-to-XML transformation without touching the HTML DOM for the containing page. But there has probably not been much (any?) testing of this scenario, and there may be parts of the jigsaw that aren't yet in place.

(b) it ought to be possible to run Saxon-JS without it listening for events; but I'm not sure this is possible at the moment. Something to investigate.

(c) it should be possible to run Saxon-JS more than once within a page load, on the same or different stylesheets, We've designed it so all the per-transformation data is in data structures local to the transformation (the Context object), rather than setting anything in global variable space. But design is one thing and implementation is another, and it's entirely possible that there are bugs we have overlooked in the implementation, as again, this hasn't been a major focus for testing.

RE: Using Saxon-JS in a (pure) functional way - Added by Matthias Hogerheijde over 7 years ago

Thanks for these insights! This makes me very hopeful about us being able to use Saxon-JS in our use-case.

@John: To clarify: we're not looking for XPath parsing in the browser. For us it's perfectly fine to pre-process all the XPath expressions we have into the SEF format. Actually: we need to pre-process the XBRL before we can send it along to the browser, and as long as we're pre-processing any way,… we can just as easy convert it to SEF.

I don't know yet how much time our planning allowes us, but It would be interesting to try some of the things you mention.

a) For now we could try provide Saxon-JS with a tailor made dom and try to use @{ destination: "application" }@ to capture the result. b) Good to know! Especially in combination with c) c) That would help us out, since we have multiple places where we'd want to have small validation functions that can run with their own context. If we succeed in doing some testing, we'll post the results.

RE: Using Saxon-JS in a (pure) functional way - Added by Matthias Hogerheijde over 7 years ago

I'd like to share some examples of our current Spikes/PoCs with you, to show how we got some things working and maybe get some insights from you about if we're "doing it right".

Am I allowed to use something like JSFiddle or GitHub to publicly share this including SaxonJS.js? Or would you prefer some samples that do not include the SaxonJS.js library and - thus - need some tinkering to get it in a working state.

RE: Using Saxon-JS in a (pure) functional way - Added by Michael Kay over 7 years ago

You're allowed to put Saxon-JS,js on any web site where it's needed to deliver Saxon-JS-enabled content.

You're not supposed to promote such a site as a place to download Saxon-JS. That's mainly because download sites need to be kept up to date with patches etc.

RE: Using Saxon-JS in a (pure) functional way - Added by Johan Walters over 7 years ago

One feature we would like to see is being able to repeatedly iniate calls to the stylesheet, without (expensive) re-initalization of the whole stylesheet for each call.

Moreover, we would like to be able to pass non-global parameters to templates, with similar sementics as xsl:call-template/ and .

In order to achieve the first goal, SaxonJS could export an initialized context object, e.g. after the first tranform or as the result of a separate 'inititilize()' function call. This object could be passed to subsequent calls to transform() or perhaps some function 'callTemplate(parameters)'.

An illustration of how we experimented with SaxonJS so far, repeatedly calling the transform() function: https://jsfiddle.net/r05oem52/4/

When making a slight modification to SaxonJS.js (where applyStyleSheet() exposes the config object as return value of the initialization object), we are able to impement callTemplate() by ourselves: http://jsbin.com/pirizudori/1/edit?html,js,console,output (non-runnable example) Here the context object is obtained from the first call to transform(). Function callTemplate() acts as a dynamic builder for SEF instructions. (It is quite naïve when it comes to using proper types at this point).

A scenario using event listeners seems awkward for our use case; that would mean setting up some global variables, triggering some event to get a template running to pick up that global data etc.

Furthermore, we are experimenting with webworkers (http://jsbin.com/cafulasuqe/edit?html,js,console,output). It correctly detects not being run inside the browser, but fails when we try to use stylesheetText as source document, since method parseXMLFromString() is not available.

RE: Using Saxon-JS in a (pure) functional way - Added by Michael Kay over 7 years ago

Thanks for the feedback.

I think that the answer to the first point is that we should simply avoid the costs of indexing templates, variables, and functions) if it has already been done. This probably means saving most of what is currently in "context.fixed" as a property of the document node of the stylesheet (SEF) tree. I don't think this should need API changes.

Binding values for local parameters to the initial template is something supported by the XSLT 3.0 specification and something we should handle in the Saxon-JS API in due course.

We've been generally structuring the code to anticipate the need to run in non-browser environments without actually doing the work to make this possible. Our initial focus will be on the browser until we get that to a definitive release, then we'll start looking at server-side running.

RE: Using Saxon-JS in a (pure) functional way - Added by Michael Kay over 7 years ago

I would add that dynamic creation of SEF trees worries me a lot. We've written Saxon-JS to assume that the SEF tree is valid, and if people are going to hit us with invalid SEF trees we could be in trouble; I also don't want to get into the position where we can't change the SEF tree format between releases. We're going to try and defend against some of the potential problems with checksumming, though of course that can be defeated.

RE: Using Saxon-JS in a (pure) functional way - Added by Anonymous about 7 years ago

Johan Walter's solution for preventing the re-initalization of the whole stylesheet would be an improvement for the performance of our project (same project, same company) but requires modifying the SaxonJS.js source code. Will the initialized context be exposed for use in a next version of Saxon-JS, or will there be another way to cache / re-use the initialized context?

RE: Using Saxon-JS in a (pure) functional way - Added by Michael Kay about 7 years ago

I've been thinking on similar lines. Perhaps a function SaxonJS.prime(stylesheet) which returns var R where:

R.mode[modename](selection, params) does apply-templates invocation supplying an initial match selection and a set of parameter values for the selected template rule

R.template[templatename](contextItem, params) does call-template invocation supplying an initial context item and a set of parameter values for hte selected named template

R.functionfunctionname does call-function invocation supplying parameters for the named function.

RE: Using Saxon-JS in a (pure) functional way - Added by Jan-Paul van der Velden about 7 years ago

Are there concrete plans to enhance to performance of Saxon-JS? Especially for this use case?

RE: Using Saxon-JS in a (pure) functional way - Added by Michael Kay about 7 years ago

There have been quite a few contributors to this thread and I'm not sure everyone is talking about exactly the same thing. Also, no-one has provided any concrete code or performance measurements. It would be useful therefore if you were start a new thread, explaining what you want to do, analysing the current performance, and explaining where you think this can be improved.

Performance improvements with Saxon have always been use-case driven, so we need to start with a workload that we can measure and that needs improvement rather than with hypothetical discussions.

RE: Using Saxon-JS in a (pure) functional way - Added by Jan-Paul van der Velden about 7 years ago

We will provide a description and measurements for our use case..

RE: Using Saxon-JS in a (pure) functional way - Added by Will Thompson about 7 years ago

Just chiming in to note that I am also interested in functional client-side XSLT to use as part of a React.js web application. Our use case sounds similar to Matthias'. The XSLT would need to be run repeatedly for any change to the content of a form by a user, and each time the XSLT output would be supplied to React (to update the DOM).

RE: Using Saxon-JS in a (pure) functional way - Added by Wybe Minnebo about 6 years ago

It's not using Saxxon or an XSLT syntax, but I've open-sourced a Javascript module that works both client-side and server-side. It outputs any node that matches a given XPath test to a React component instance (or something else).

I've published and documented some example use here: https://www.npmjs.com/package/xml-renderer

    (1-15/15)

    Please register to reply