Because Serna uses dynamic XSLT implementation, memory consumption and CPU consumption aspects of stylesheet processing become important and may severely affect interactive response-time. However, there is a set of rules which can help minimize such impact.
General principles for efficient stylesheets: always narrow possibilities for template selection and avoid repeating construction of large node-sets.
When an element is being inserted into the document, the XSLT processor tries to find all templates which can potentially match this element (we call them applicable templates). The processor instantiates all applicable templates and selects one with the highest priority, which is used to construct the result.
Using named templates and modes greatly narrows the range of choices.
Using these axes in expressions somewhere close to the top of the document may cause exponential increases in CPU time and memory consumption, because the corresponding node-set will have a copy of a big portion of the document, which needs to be re-created for every element insertion/deletion event in the corresponding document area. Use these axes only when absolutely necessary, and close to the leaf nodes. This also applies to the // operator in pattern expressions.
For example:
<xsl:template match="item"> <xsl:text>Item sibling position:</xsl:text> <xsl:number value="count(preceding-sibling::*)"format="1"/> </xsl:template>
will result in creation of a full copy of the node-set, containing all item siblings, for each occurrence of item, therefore resulting in an n-items^2 memory consumption pattern and speed degradation. When possible, use alternative methods.
The correct way to do the above is:
<xsl:template match="item"> <xsl:text>Item sibling position:</xsl:text> <xsl:number value="position()"format="1"/> </xsl:template>
Predicates in patterns affect the way they instantiate. For example,
<xsl:template match="item[@attr =foo"]>
will instantiate pattern with node-set containing just one element item (using the self axis) and then checks attribute attr. But:
<xsl:template match="item[5]">
will instantiate pattern with node-set which contains all items on the current level (all left and right item siblings), and then checks if the fifth item exists. Note that this also can cause exponential memory/CPU consumption growth effects.
Operator " I " really means " sort left operand in document-order, sort right operand in document-order, merge the result," so it can be expensive. Serna has built-in optimization for cases when there are multiple axis expressions with the same axis, e.g. "foo|bar|blah", which is safe.
Do not mix this up with " | " operator in match patterns, where it means alternative, not union. Using alternate match patterns is safe, and is generally a better alternative than using select with the XPath union operator in apply-templates.
Certain expressions need to be re-evaluated not only when their operands change, but also when their position in parent context (size or context node position) changes. Context update changes are propagated down the instance tree until they meet an instance of apply templates (branches with no context-dependent expressions are also cut off from context update propagation).
Expression is context-dependent when it has any context-dependent function on the top level of the expression (not within the predicates). Examples:
last() - 1 is a context-size dependent expression.
foo[last() - 1] is not context-dependent, because last() is evaluated within the context of newly constructed node-set containing foo elements.
Context-dependent functions are position() and last().
Predicates are evaluated from left to right (constructing a new node-set for each step of the expression), so it is a good idea to write the strictest predicates (which filter out more nodes) first.
XPath standard states that XPath expression X=Y when X and Y are node sets does not just compares these node-sets, but checks if there is any node in X which appears in Y, therefore potentially requiring n^2/2 CPU time.
Use the XSLT idioms noted below for simple node selections:
For selecting child element nodes FOO, BAR, or BLAH, use expression: FOO|BAR|BLAH or *[self::FOO or self::BAR or self::BLAH].
For selecting child element nodes except FOO, BAR or BLAH, use expression: *[not(self::FOO or self::BAR or self::BLAH)].
Use grouping: e.g. instead of use sect/(title|subtitle|othertitle).
This is so because the Serna XSLT processor can optimize expressions which deal with the groups of node tests with the same axis.
Define global parameters via xsl:param or xsl:variable .
Re-use templates by passing them parameters using xsl:with-param. However, avoid xsl:choose/xsl:if wherever possible.
Use a modularized approach to the stylesheet design: define templates for different levels of the document in different files, and then import or include them. Because of import precedence support, it is possible to define a very flexible stylesheet structure, when whole blocks can be redefined from the hub-file.
For example, Docbook documents usually have book, article,or chapter as root elements. Root-level templates for these elements should reuse other templates (and possibly each other).
The limitation that a single template cannot correspond to multiple modes (another annoying XSLT standard glitch) can be bypassed with xsl:call-template.
xsl:number automatically supports format string trimming, so the numbering in the above-mentioned case is not a problem (if with level="multiple" there are actually less countable levels than specified in the format string, only the relevant portion of the format string is used).
Make sure you do not create infinite recursion in template calls. Such calls will inevitably crash the application.