XSLT Considerations

In this section I'll try to draw out some observations about the XSLT implementation.

Like most XSLT code, it has been developed incrementally: rules are added as the need for them is discovered. This is one of the strengths of XSLT as an implementation language for this kind of task: the program can grow very organically, with little need for structural refactoring. At the same time, uncontrolled growth can easily result in a lack of structure. How many modes should there be, and how do we decide? How should the code be split into modules? How should template rule priorities be allocated?

Again, like most XSLT applications, it's not just template rules: there are also quite a few functions. And as in other programming languages, the set of functions you end up with, and their internal complexity and external API, can grow rather arbitrarily.

It's worth looking a little bit at the nature of the XML we're dealing with. Here's a sample:

<member nodeType="MethodDeclaration">
 <body nodeType="BlockStmt">
  <statements>
   <statement nodeType="ReturnStmt">
    <expression nodeType="BinaryExpr" 
                operator="PLUS">
     <left nodeType="MethodCallExpr" 
           RETURN="double"
           RESOLVED_TYPE="net.sf.saxon.expr.Expression">
      <name nodeType="SimpleName" identifier="getCost"/>
      <scope nodeType="MethodCallExpr" 
             RETURN="net.sf.saxon.expr.Expression"
             DECLARING_TYPE="net.sf.saxon.expr.BinaryExpression">
        <name nodeType="SimpleName" identifier="getLhsExpression"/>
      </scope>
     </left>
     <right nodeType="BinaryExpr" operator="DIVIDE">
      <left nodeType="MethodCallExpr" RETURN="double"
            RESOLVED_TYPE="net.sf.saxon.expr.Expression">
       <name nodeType="SimpleName" identifier="getCost"/>
        <scope nodeType="MethodCallExpr"
               RETURN="net.sf.saxon.expr.Expression"
               DECLARING_TYPE="net.sf.saxon.expr.BinaryExpression">
         <name nodeType="SimpleName" 
               identifier="getRhsExpression"/>
        </scope>
      </left>
      <right nodeType="IntegerLiteralExpr" 
             value="2"/>
     </right>
    </expression>
   </statement>
  </statements>
 </body>
 <type nodeType="PrimitiveType" 
       type="DOUBLE" 
       RESOLVED_TYPE="double"/>
 <modifiers>
  <modifier nodeType="Modifier" 
            keyword="PUBLIC"/>
 </modifiers>
 <annotations>
  <annotation nodeType="MarkerAnnotationExpr">
   <name nodeType="Name" identifier="Override"/>
  </annotation>
 </annotations>
</member>

This represents the Java code

@Override
public double getCost() {
    return getLhsExpression().getCost() 
           + getRhsExpression().getCost() / 2;
}

It's interesting to look at the values used (a) for the element name (e.g. body, left, right, expression, statement), and (b) for the nodeType attribute (e.g. ReturnStmt, BinaryExpr, SimpleName). Generally, the nodeType attribute says what kind of thing the element represents, and the element name indicates what role it plays relative to the parent. (Reminiscent of SGML architectural forms, perhaps?)

As an aside, the same dichotomy is present in the design of Saxon's SEF file, which represents a compiled stylesheet, but there we do it the other way around: if an integer literal is used as the right hand side of an addition, the JavaParser format expresses this as <right nodeType="IntegerLiteral">, whereas the SEF format expresses it as <IntegerLiteral role="right">. Of course, neither design is intrinsically better (though the SEF choice works better with XSD validation, since XSD likes the content model of an element to depend only on the element name, not the value of one of its attributes). But the choice does mean that most of our template rules in the transpiler are matching on the nodeType attribute, not on the element name, and this perhaps makes the rules a bit more complicated.

Performance hasn't been a concern. I'm pleased to be able to report that of the various phases of processing, the phases written in XSLT are an order of magnitude faster than the phase written in Java; which means that there's no point worrying about speeding the XSLT up. This is despite the fact that (as the above example demonstrates) the XML representation of the code is about 10 times the size of the Java representation.

(Actually, the Java code is 29Mb, the XML is 120Mb, and the generated C# is 18Mb. The C# is smaller than the Java mainly because we drop all comments, and also because the Java total includes modules we don't (yet) convert, for example a lot of code dealing with SAX parsers, localisation, and optional extras such as the XQJ API and SQL extension functions).

But I would like to think that one reason performance hasn't been a concern is that the code was sensibly written. We've got about 200 template rules here, most of them with quite complicated match patterns, and we wouldn't want to be evaluating every match pattern for every element that's processed. In fact, a lot of the time we're doing three levels of matching:

So it's not a flat set of hundreds of rules; we've used modes (and the microsyntax) to create a hierarchic decision tree. This both improves performance, and keeps the rules simpler and more manageable. It also makes debugging considerably easier: as with any XSLT stylesheet, working out which rules are firing to handle each input element can be difficult, but the splitting of rules into modes certainly helps.

(A little known Saxon trick here is the saxon:trace attribute on xsl:mode, which allows tracing of template rule selection on a per-mode basis).