The adventures of XSLT 2.0 in Browserland

See also:

Introduction

The purpose of my presentation at XML Amsterdam was to explore what might be done  with Saxon-CE in the domain of forms applications.

I won’t repeat here what I said during the presentation but focus on a few key points and give as much pointers as possible to explore if you want to go further.

For those of you who have missed my presentation or would like to hear it again, the next best thing is the screencast of my last rehearsal before the event:

The screencast of the last rehearsal of my presentation at XML Amsterdam

The presentation itself is powered by Saxon-CE and the best way if you want to try it by yourself is to download or clone it on our GitLab server.

An alternative is to browse it directly from this server. Note however that serving raw resources through GitLab is not very efficient and that the response times will be slow.

You may notice differences between the screencast, what I have presented, the demo showed by Geert Josten at the DemoJam and the latest version on GitLab.

A look at the network of changes should help you to understand what has happened! The version that I have presented is tagged as xmlamsterdam2013. The version used for the screencast was the previous one, before adding a page with metrics. I have wanted to play it safe and the features needed to implement Geert’s calculator have been developed separately in the grtjn branch which has been merged after the conference into the master branch…

Impedance mismatches

A key point to manipulate XML documents based on user interaction is the ability to store these documents in the browser between user actions and I have spent height slides, between slide 13 “Storing instance in an XML hostile environment” and slide 20 “Storing XML in the global (window) object” to explore different options.

It’s time to explain the issues we have to face to be able to store instances as JavaScript properties.

While Saxon-CE is fine within browsers and does a good job of acting as a good citizen speaking JavaScript almost as it if was its native language, there are still a number of glitches that reminds us that its isn’t really the case…

Among them, many castings which are implicit in XSLT don’t work when accessing JavaScript object properties or methods.

In XSLT, we use to write things such as:

    <xsl:template name="init">
      <xsl:variable name="element" as="element()">
        <element>foo</element>
      </xsl:variable>
      <div><xsl:value-of select="concat('Element value: ', $element)"/></div>
    </xsl:template>

In the <xsl:value-of/> instruction, the second parameter to the concat() function is an element node which as been implicitly casted into a string.

As a naive user, I was thinking that I could do the same when calling JavaScript methods, for instance:

      <xsl:variable name="element" as="element()">
        <element>foo</element>
      </xsl:variable>
      <div><xsl:value-of select="js:alert($element)"/></div>

But the browser strongly disagreed:

SaxonCE.XSLT20Processor 20:25:07.774
SEVERE: XPathException in invokeTransform: Cannot convert class client.net.sf.saxon.ce.tree.linked.ElementImpl to Javascript object

This message comes to remind you that two type of incompatible objects live within Saxon-CE and that native Java classes cannot always be converted into JavaScript objects!

In this simple case we can explicitly cast the element to a simple type which will be compatible with JavaScript object system:

      <xsl:variable name="element" as="element()">
        <element>foo</element>
      </xsl:variable>
      <div><xsl:value-of select="js:alert(string($element))"/></div>

That’s more difficult if you really need to pass an XML node as a JavaScript parameter or property. For instance if I want to save this element as a property, this will raise the same kind of errors:

      <xsl:variable name="element" as="element()">
        <element>foo</element>
      </xsl:variable>
      <div><ixsl:set-property name="element" select="$element"/></div>

Here I need to convert the element node into a JavaScript object and I would even say that ideally this conversion should be implicit.

The bad news is that Saxon-CE does not provide a function to perform this conversion.

The other bad news is that if you thought you could use Saxon-CE JavaScript js:Saxon.serializeXML() and js:Saxon.parseXML() to perform that task by writing:

      <xsl:variable name="element" as="element()">
        <element>foo</element>
      </xsl:variable>
      <div><ixsl:set-property name="element" select="js:Saxon.parseXML(js:Saxon.serializeXML($element))"/></div>

you’re out of luck: js:Saxon.serializeXML() is a JavaScript wrapper to a native JavaScript function that won’t accept your XML node as an argument.

Michael Kay has implemented an ixsl:serialize-xml() function in branch ce1.1plus but unfortunately this function works fine with Saxon’s tiny tree nodes but silently fails when its parameter is a JavaScript DOM object. Furthermore, ixsl:serialize-xml() does not always preserve comments and PIs.

This conversion is one of the basis of most of the things I have shown during my presentation and I ended up writing a “d:serializeXML()” function, based on Evan Lenz’ xml-to-string.xsl and wrap this function in a js:Saxon.parseXML() call to provide the d:convert-to-jsdom() function that I needed:

      <xsl:variable name="element" as="element()">
        <element>foo</element>
      </xsl:variable>
      <div><ixsl:set-property name="element" select="d:convert-to-jsdom($element)"/></div>

This is probably a most inefficient way of performing the conversion. A slightly more efficient way could be to write an XSLT implementation of this function that would use JavaScript methods to create the DOM tree from within XSLT templates rather than serializing to parse again.

At the end of the day, I think that Saxon-CE should really provide a built-in method to perform the conversion and that this conversion should be implicit!

See also:

One transformation is not enough!

 When you love you don’t count the cost

Saxon-CE analyses the page to find the first script element with @type='application/xslt+xml' and @language='xslt2.0' in the HTML page to create an XSLT transformer on which it calls the updateHTMLDocument() method.

This a great idea and that’s perfect to provide an easy way to run a transformation acting as a set of event handlers on the page!

However, why should us be limited to a single transformation?

In my presentation I have found out that I had several disjoint set of features to implement on each slide and in such case I much prefer to write a transformation for each set of features.

The bad news is that Saxon CE won’t do that automatically for you.

The good news is that you can do that using the JavaScript API provided by Saxon-CE. And since you can call JavaScript methods in XSLT you can even do that in XSLT.

That’s what I did in my presentation: the first transformation which is automatically invoked by Saxon-CE is called boot-saxon.xsl. This transformation matches the following scripts linking to XSLT 2.0 transformations, creates an XSLT transformer and invokes the updateHTMLDocument() method…

If you look at this transformation you’ll see that in addition it can also invoke dynamic transformations (transformations which are created by transforming the web page into XSLT). This is a feature I use in my proof of concept of an XForms implementation.

Multiple transformations are a great feature and I think they would really deserve to become a standard feature in Saxon-CE.

Dynamic transformations are probably much more a niche thing and I am not sure they should be integrated to the product.

See also:

The beauty of XSLT as an event handler self-modifying document

updateHTMLDocument() is a misnomer

Beside bringing XSLT 2.0 to the browser, I think that the main innovation of Saxon-CE is the way it uses XSLT to define event handlers which update the current page based on interaction events.

In slide 26, “A dream come true”, I am using this method on an XSLT transformation run against an XML fragment and as you can see it seems to be working pretty well!

Why would we need to run this method on anything else than the HTML page?

This was one of the things I have explained in detail during my presentation: I think that the new paradigm introduced by Saxon-CE to use XSLT “transformations” as even handlers able to update document fragments is really powerful and has use cases well beyond “typical” Saxon-CE applications.

The purpose of this slide is to show how nice it would be to run a client side MVC application implemented as two separate transformations communicating through events:

  • a first one acting on the page, reacting to user interactions
  • a second one acting on an instance and controlling its updates.

I see no reason why this shouldn’t be extended to other use cases.

For instance, such a feature would open interesting perspective in XML databases where XSLT transformations could be used to define event handlers on XML nodes…

To come back to Saxon-CE, I think that both the name of the method and the documentation are misleading:

  • updateHTMLDocument() could be renamed updateDocument()
  • The description of the method’s $target parameter should read “The Document object to update” instead of “The HTML Document object to update”.

Events seem (too) strongly limited

This proposal to generalize the usage of the updateHTMLDocument() method assumes that events can be used on XML documents.

Unfortunately the usage of events in Saxon-CE seem to be strictly limited.

The documentation differentiates two different types of events:

  • User input events: Event handlers for user input are written in the form of template rules. The match pattern of the template rule must match the element that receives the event, and the mode name reflects the type of event, for example ixsl:onclick.
  • Client system events: Saxon-CE also handles events raised by objects such as window that live outside the DOM. Event handlers for such objects are written in the form of template rules. The match pattern is different from that for conventional templates because there is no node to match. Instead, the pattern must be an ixsl function (e.g. ixsl:window() ) that returns the object whose event is to be handled.

Unfortunately both types of events suffer important restrictions:

  1. User input events seem limited to a subset of standard HTML events as listed for instance by MDN. This limitation isn’t enforced by Saxon-CE itself but the fact is that most events are just ignored. This includes not only any custom event but events such as the input event. If you define a template with a mode matching an event which is not supported (such as ixsl:oninput), Saxon-CE will silently ignore this definition and won’t define any event handler.
  2. Furthermore, when using updateHTMLDocument() with an XML document as a target Saxon-CE ignores any event handler that you define through ixsl:onXXX modes.
  3. Client system events on the contrary do support any type of event. However I have never been able to use them with any JavaScript object except the global window object!
  4. Custom events sent to the window object using standard dispatch methods do not seem to work and it is safer to directly call the event handler than to dispatch events.

A consequence of the first limitation is that I haven’t found any way to implement the equivalent of XForms incremental mode. This should be trivial to implement using input events, I have checked that these events are fired synchronously when  an input is changed but since Saxon-CE doesn’t install any event handler for them there seems to be no way to catch them.

Another consequence is that as you may have noticed that in hello-world-2xslt.xsl we define an event handler on a change event for the output:

    <!-- The output needs to be updated -->
    <xsl:template match="div[@id='output']" mode="ixsl:onchange">
        <xsl:message>The output needs to be updated</xsl:message>
        <xsl:variable name="instance" select="ixsl:get(ixsl:window(), 'instance')"/>
        <xsl:result-document href="#output" method="ixsl:replace-content">
            <xsl:text>Hello </xsl:text>
            <xsl:value-of select="$instance/data/person-given-name"/>
            <xsl:text>. We hope you like Saxon CE!</xsl:text>
        </xsl:result-document>
    </xsl:template>

You may wonder how a change event can be sent to an HTML <div/>. In fact this event is sent as a custom event by the transformation implementing the model and it would have been much less confusing to use a custom name but because custom events on DOM nodes are ignored by Saxon-CE I had to hijack an existing HTML event and have chosen one that should never be fired to avoid and conflict with user interaction.

At that point you probably wonder how slide 26 can work if we can’t define event handlers for XML nodes (this is the second restriction listed above).

I had to find a workaround and ended up simulating proper event handling in functions defined in events.xsl.

The most controversial pieces in this library are probably:

  <xsl:function name="d:new-custom-event">
    <xsl:param name="name" as="xs:string"/>
    <xsl:variable name="js-statement" as="element()">
      <root statement="new CustomEvent ( '{$name}',{{ detail: {{}} }})"/>
    </xsl:variable>
    <xsl:sequence select="ixsl:eval(string($js-statement/@statement))"/>
  </xsl:function>

  <xsl:function name="d:dispatch-event-to-instance">
    <xsl:param name="target"/>
    <xsl:param name="event"/>
    <ixsl:set-property object="$event" name="detail.target" select="$target"/>
    <ixsl:set-property name="dummy" select="ixsl:call(ixsl:window(), 'onEventWorkaround', $event)"/>
  </xsl:function>

Instead of creating an event with the type defined as a parameter, d:new-custom-event() creates an event and stores the type as a property.

Instead of really dispatching an event to a target, d:dispatch-event-to-instance() stores the target as a property of the event and performs a direct call to the onEventWorkaround() event handler on the window object.

This event handler is defined as:

  <xsl:template match="ixsl:window()" mode="ixsl:onEventWorkaround">
    <xsl:message>Got en event</xsl:message>
    <xsl:variable name="event" select="ixsl:event()"/>
    <xsl:choose>
      <xsl:when test="ixsl:get($event, 'type') = 'ModelUpdate'">
        <!--<xsl:apply-templates select="ixsl:get(event, 'detail.target')" mode="d:onModelUpdate">-->
        <xsl:apply-templates
          select="ixsl:get(ixsl:window(), 'instance')//*[d:path(.) = d:path(ixsl:get($event, 'detail.target'))]"
          mode="d:onModelUpdate">
          <xsl:with-param name="event" select="$event"/>
        </xsl:apply-templates>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

This single event handler serves as a channel for events sent to the XML instance. To simulate the standard behavior of event handling in Saxon-CE, it applies the templates on the target passed as an event property. The biggest downside of this workaround is probably that this isn’t really extensible and that new <xsl:when/> need to be added for each supported event!

In find these four issues really problematic and think they deserve to be further analyzed and fixed!

See also:

Side effect

This point is briefly mentioned in slide 23, “Side effect”.

XSLT has been designed to be free of side effect and to develop this kind of user interface we rely very heavily on side effect.

Among other side effects, we often set properties on JavaScript objects.

In hello-world-mvc.xsl for instance (used in slide 21, “Complete example (when all we have is a hammer…)”) we write:

        <!-- Store the new value -->
        <ixsl:set-property object="ixsl:window()" name="instance"
            select="d:convert-to-jsdom($instance)"/>
        <!-- Rely on the instance to write the output -->
        <xsl:result-document href="#output" method="ixsl:replace-content">
            <xsl:text>Hello </xsl:text>
            <xsl:value-of select="$instance/data/person-given-name"/>
            <xsl:text>. We hope you like Saxon CE!</xsl:text>
        </xsl:result-document>

XSLT being side effect free, there is nothing in the recommendation that insures that the <xsl:result-document/> will be executed after the <ixsl:property/>.

It happens to work but we need to realize that we are here on the edges of what should be done with XSLT and, of course, hope that Saxon-CE won’t break our assumption that in most of the cases XSLT instructions will be executed in document order!

 

Share and Enjoy:
  • Identi.ca
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Twitter
  • Add to favorites
This entry was posted in English, XML and tagged , , , , . Bookmark the permalink.

4 Responses to The adventures of XSLT 2.0 in Browserland

  1. Tony Graham says:

    +1 to the beauty of XSLT as an event handler. I’ve previously looked at adapting Saxon-CE event handlers to decision making inside a FO processor (http://inasmuch.as/2013/04/17/saxon-ce-events-xsl-fo/), which was then discussed by the Print and Page Layout CG at W3C (http://lists.w3.org/Archives/Public/public-ppl/2013Apr/0004.html) though we haven’t got as far as implementing anything.

    XSLT did everything in the now-defunct Xacerbate server invented by Mark Howe (http://archive.xmlprague.cz/2009/sessions.html#xcruciate, http://archive.xmlprague.cz/2009/presentations/Mark-Howe-Imagining-building-and-using-an-XSLT-virtual-machine.pdf, and http://archive.xmlprague.cz/2009/presentations/XMLPrague_2009_proceedings.pdf, pages 189-206).

    • Interesting stuff!

      I am wondering how difficult it would be to implement a similar mechanism in eXist-db using triggers… That doesn’t seem so hard but of course the devil is probably in the detail.

      Another interesting thing would be to run Saxon-CE in Node.js to experiment this server side. Running Saxon-CE in Node.js has been discussed a number of times already. I haven’t tried it but there is even an NPM package claiming to do so but AFAIK not under the angle of using XSLT as an event handler.

      Eric

  2. Todd Gochenour says:

    Erik, thank you for posting a great overview of the research you’ve done with Saxon-CE. I’ve been doing my own research over the holidays and today got to the place where I needed to update the model and one Google search later here I am watching your presentation and reading your notes. My current framework utilizes XSL 1.0 in the browser to perform transformations on the XML model in phases, such as security filtering, an analysis phase, rendering the view and validating the document before save. This framework uses standard Javascript DOM API to perform updates using Sarissa library to normalize APIs across browsers).

    I find your idea about performing these updates as a transformation using Saxon-CE events to be intriguing. I started the day realizing that the document object returned by Saxon.requestXML() is not a classic Javascript DOM tree and so the familiar method selectSingleNode() isn’t available. I’m writing the controller in Javascript, so I have variables to the XML document and I can invoke transformations using a separate XSL template to perform the update.

    I haven’t tried this, yet, so I’m not sure it will work. What’s interesting about Saxon-CE is that after the initial XSLT20Processor.updateHTMLDocument() is performed, the engine keeps running in the background waiting to handle events by performing transformations using the same XSL template identified initially. It also uses the same XML model initially. I doubt I can perform an identity transformation xdoc = XSLT20Processor.transformToDocument(xdoc) and expect this background engine to know that ixsl:source() has been updated without calling XSLT20Processor.updateHTMLDocument() to re-render the entire page. That’s not very efficient.

    I’m using contentEditable spans and the focus/blur events are not caught by Saxon-CE. I have JQuery code catching these events, but the XML variable I’ve been given is a stub and so I can’t update the model directly. So now I will investigate whether I can write a transformation that updates the model correctly so that subsequent view transformations will see the update.

    Eric, your presentation has provided good grist for the grinder, so to speak. Thanks for the post.

    • Todd,

      Thanks for your comment!

      It looks like you are hitting “impedance mismatches” between native JavaScript objects and events and Saxon-CE and this is where I have found most of the issues as well…

      Don’t hesitate to share the link here if you publish the result of your researches!

      Eric

Leave a Reply

Your email address will not be published. Required fields are marked *

Enter your OpenID as your website to log and skip name and email validation and moderation!

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>