XSL in Roxen CMS 5.0

2008-10-03, 18:20 by Jonas Wallden in Development

Now that we have announced Roxen CMS 5.0 I'd like to describe some XSL changes that will arrive shortly. In this posting I'll highlight three areas: profiling, compatibility and performance.

Debugging and profiling

Starting in CMS 5.0 we are including a XSL profiler. It gives a complete trace of template rules that are invoked plus a lot of associated data such as passed parameters, selected node-sets and timing info.

To use these capabilities you need to start your Roxen with the run-time flag -DXSL_PROFILER because although the extra hooks are minimal in cost they still incur a slight overhead in XSL transformations even when not profiling.

For now the only interface to the profiler is the Tasks > Debug Information > Resolve Path... wizard in the administration interface. For those not familiar it's a tool that traces the execution of a single request in Roxen CMS, logging which modules and RXML tags that gets called along the way.

Here's part of the output when requesting /search.xml in a bare-bones Basic site (sorry for the narrow column which wrecks the formatting):

   10 ms |    <xsl:apply-templates> => node-set (7 elems: (#3, [4 ws chars]), (#4, <header-component>), (#13, [4 ws chars])...)
     0 ms |      <xsl:with-param> @name: content-width => tree fragment (root: OutputNode("584"))
     0 ms |        @select: $content-width => tree fragment (root: OutputNode("584"))
     3 ms |      <xsl:template> @match: *[contains(concat(name(), '|'), '-component|')]
     0 ms |        <xsl:if>
     0 ms |          @test: id => FALSE
     3 ms |        <xsl:apply-imports>
     3 ms |          <xsl:template> @match: header-component
     2 ms |            <xsl:call-template> @name: roxen-edit-box
     1 ms |              <xsl:with-param> @name: content => tree fragment (root: OutputNode(<h2>))
     1 ms |                <xsl:variable> @name: title => tree fragment (root: OutputNode("Search"))
     0 ms |                  <xsl:when>
     0 ms |                    @test: string-length(title) => FALSE
     1 ms |                  <xsl:value-of>
     1 ms |                    @select/{}: rxml:metadata()/title => "Search"
     0 ms |                <xsl:value-of>
     0 ms |                  @select/{}: $title => "Search"
     0 ms |                <xsl:value-of>
     0 ms |                  @select/{}: subtitle => ""
     0 ms |              <xsl:template> @name: roxen-edit-box
     0 ms |                <xsl:param> @name: content => ""
     0 ms |                <xsl:when>
     0 ms |                  @test: id and not(render-in-editor) => FALSE
     0 ms |                <xsl:copy-of>
     0 ms |                  @select: $content => tree fragment (root: OutputNode(<h2>))
     0 ms |            <xsl:call-template> @name: component-spacing
     0 ms |              <xsl:template> @name: component-spacing
     0 ms |                <xsl:param> @name: force => FALSE
     0 ms |                  @select: false() => FALSE
     0 ms |                <xsl:if>
     0 ms |                  @test: $force or following-sibling::*[contains(name(), '-component')] or following::*[contains(name(), '-component')] => TRUE

The values in the left margin shows the total time for that line including any nested calls. Here 10 ms is spent in the <xsl:apply-templates> call that resulted in 7 nodes being processed; the first match (excluding text nodes) is <header-component> which is seen right below in the trace. By reporting evaluation of select and test attributes on their own lines you can separate the time consumed in finding document nodes from the time spent in processing them.

Sometimes you may see multiple @select/{} lines grouped together for a single tag. One example is <div id="{$marker}" onclick="...{$marker}..." /> used in the Basic site. There isn't enough context to report which of the attributes that generate the corresponding evaluation but at least you will see a single <div> element as the parent of both XPath expressions.

All XPath result values are displayed after the => arrow so you can quickly see e.g. template parameters. Node-sets are presented with their node count and a brief listing of a few elements:

    <xsl:apply-templates> => node-set (7 elems: (#3, [4 ws chars]), (#4, <header-component>), (#13, [4 ws chars])...)

The #x identifier shows the node's position number in the document tree. Text nodes containing nothing but whitespace are abbreviated to [X ws chars].

Some omissions are made in the trace report. The <xsl:choose> statement isn't logged separately, only through its <xsl:when> children. For the sake of clarity we also hide all template pattern evaluation calls (meaning e.g. <xsl:template match="...">) even though they may potentially consume noticeable time. Finally, built-in fallback rules for copying or recursing are not logged either. However, in all cases the time spent in such code should be reflected at the next level up in the trace.

Compatibility

The XSL specification has always defined the visibility of local variables to be limited to the parent element which contains the <xsl:variable> tag. In other words:

 <p>
    <xsl:variable name="v" select="17" />
    v is <xsl:value-of select="$v" />
  </p>

works fine, but outside the <p> you can't access $v. In earlier CMS versions an unfortunate side-effect of the implementation was that $v could be seen outside its intended scope. In particular, it made the following incorrect code seemingly work:

  <xsl:choose>
    <xsl:when test="position() mod 2 = 0">
      <xsl:variable name="color" select="'#ccc'" />
    <xsl:when>
    <xsl:otherwise>
      <xsl:variable name="color" select="'#eee'" />
    <xsl:otherwise>
  </xsl:choose>
  <p style="background: {$color}">...</p>

Note the <xsl:variable> statements which both have scopes that don't extend beyond their parent <xsl:when> or <xsl:otherwise> elements. By the way, the right way to do this is to move the <xsl:choose> statement inside the <xsl:variable> element.

In CMS 5.0 the XSLT module now correctly identifies scope errors. However, since an old site may fail to run there's a compatibility flag in the Settings tab for the XSLTransform module where you can revert to the old behavior if you don't have a chance to fix all code at once.

Performance

Various internal fixes has been made to improve performance. Recursion-heavy code that send parameters is one area that should benefit from the changes.

The XSLT module also takes advantage of the new fine-grained dependency mechanism in the 5.0 cache layer. This allows the system to distinguish between execution of template code and dependencies on top-level XSL directives. When you modify a <xsl:template match="foo-component"> code section the cache is only invalidated for pages where this code contributed to the HTML output, not all pages that in some way get foo-comp.xsl imported indirectly through cms-common.xsl. I hope it will lead to significantly reduced cache invalidation for sites that see continuous template changes.

 

You need to log in to post comments.

 

1   Arjan van Staalduijnen

2008-10-08 13:48

Hmmmm... quite a few things mentioned here that I'm eager to try in 5.0 :-)

2   Erik Dahl

2008-10-08 23:41

I agree! This is a great enhancement.

Nov 25, 2017

Categories

Community Update (1)
Customers (0)
Development (10)
New sites (1)

Latest comments

I agree! This is a great enhancement.
Hmmmm... quite a few things mentioned here that I'm eager to try in 5.0 :-)