Menu

Visualizing XSLT in SVG

June 4, 2003

Chimezie Ogbuji

There are a number of visual tools employed by XML editors and viewers which make verbose XML documents easier to browse and manipulate. XML Spy is, for example, such a tool. This article will introduce how SVG can be used to create a visual representation of an XSLT stylesheet. This will be done through a XSLT stylesheet which will transform an arbitrary stylesheet into an SVG document.

Difficulty in browsing XSLT

XSLT has become a standard means of transforming XML documents. Even beyond data transformation, XSLT is robust enough as a processing language to implement complete application logic for most web-based software requirements.

As with any programming language, if the amount of logic necessary is large enough, then the resulting code can be cumbersome to read, even if care is taken to document and organize thoroughly. This is especially true with XSLT, where the application logic is often small and compact in contrast to the XML explicitly intended for the result tree. Consider the most common use for an XSLT stylesheet: transforming XML content into HTML. In this situation, depending on how the stylesheet designer decides to delegate templates, an appreciable portion of the XSLT elements that do the actual processing may be embedded within verbose HTML elements. As a result, a casual glance at the stylesheet by someone other than the original author may not be very enlightening. This is even more likely if the user has little or no knowledge of the structure of the source document or experience with XSLT.

In addition to the obvious entry point (identified by a template with a match attribute whose value is '/'), it is very difficult to determine and track the process flow of an XSLT transformation. The flow through an XSLT stylesheet is for the most part dependent on the structure of the input document being transformed, which may not even be known by the individual examining the stylesheet. Tracking process flow is especially important in XSLT when debugging irregularities. Because of the more complex way the process flow changes (compared to other languages where the flow mostly changes explicitly), several passes will probably be needed to follow this progression from beginning to end.

Visualizing a Stylesheet (process flow and components)

A diagram of an XSLT stylesheet can go a long way in allowing someone browsing an XSLT stylesheet to understand how the logical components are organized. This is especially true if an effort is made to address the two previously mentioned problems inherent in browsing XSLT: first, busy, unorganized logical components; second, buried, unpredictable process flow.

If the diagram separates the logical components (drawing them as individual, identifiable shapes), a user can quickly identify the templates and almost immediately (from template names and match attributes) get an idea of what the major components are and what their isolated tasks entail.

Similarly, if the diagram also identifies the process flow from template to template (where it can be determined), a user can very quickly digest process flow information that would ordinarily take considerably longer if browsed directly.

Automating the process

The fact that an XSLT stylesheet is an XML document means that automating the process of generating information about its components and process flow is easier than it might otherwise be. Treating the XSLT stylesheet as a source document, I will demonstrate how another stylesheet can be designed to isolate logical components, track process flow, and transform the original stylesheet into an SVG digram.

The most important logical construct in an XSLT stylesheet is the xsl:template element. These specify template rules to be applied on specific patterns of nodes in the source document. Since the rules within a template typically perform a common function, xsl:template is the perfect level of abstraction needed to effectively isolate the components of a stylesheet. It also can be very useful for such diagrams to include properties common to a template such as its mode attribute, name attribute, parameters, and match attribute.

Diagramming the Logical Organization of a Stylesheet

The stylesheet will represent templates as individual boxes with indications of the template properties identified above. Each template box will be of dimensions proportional to the total number of templates and the size of the final diagram. For the purposes of this article, a simple spacing algorithm will be employed. Whenever possible, the template boxes will be arranged in a square grid, and in instances when this is mathematically impossible (consider 5 template boxes) the diagram well be arranged so it's always longer than it is wide. Below are the primary equations for the spacing algorithm that was used for this stylesheet:

templateGridColumns = ceiling(sqrt(totalTemplates))

templateGridRows = ceiling(totalTemplates / templateGridColumns)

templateWidth = round(screenWidth / templateGridColumns)

templateHeight = round(screenHeight / templateGridRows)

The figure below demonstrates how a template will be marked with its identifying properties. Starting from the upper-left corner of the template box, the value of the match attribute is printed in red. Below that, the value of the mode attribute is printed in green. Finally, the name of the template (if available) is printed underneath the mode in blue.

Template box layout
Figure 1. Template box layout.

Diagramming the process flow of a stylesheet

The most important visual aid will be the indication of calls between stylesheet templates. The stylesheet will render process flows as lines connecting template boxes. The lines will travel from the center of the source template to the upper-left corner of the target template. Explicit template calls (via xsl:call-template) will be drawn as solid red lines, and xsl:apply-templates invocations (let's call them "context pushes") will be drawn as solid cyan lines. By watching for conditional blocks (xsl:choose and xsl:if), we can specify a different stoke style for the call lines buried within them. In this case, we will draw dashed lines to demonstrate a context push or explicit call that was buried within a conditional block.

The stylesheet will also specify parameters that were sent along with the template calls. xsl:with-param children of xsl:call-template elements will be matched to determine the names of the parameters sent along with the explicit template invocation. Call lines will be labeled with a comma-separated, parenthesized list of these parameters (in the event that any were sent).

Details

Three modes will be employed to represent the three different tasks performed on xsl:template elements matched in the source stylesheet:

  • layout -- Determines the absolute x,y coordinates of upper-left corner of the template.
  • draw -- Draws the template box as specified in the figure above (overriding this template mode is all that is necessary to specify a different diagram for templates).
  • links -- Draws the call lines and parameters passed from source to target.

Since the layout mode follows the previously specified spacing algorithm, we will ignore it for the sake of brevity. The draw mode is invoked nested within the layout mode in order to first setup the coordinate variables (x, y, etc.) specific to the template being rendered before actually drawing the template itself. Here is a snippet of the draw mode:

<xsl:template match="xsl:template" mode="draw">

  <xsl:param name="x"/>

  <xsl:param name="y"/>



  <rect xmlns="http://www.w3.org/2000/svg" x="{$x}" y="{$y}"

       fill="white" width="{$actualTemplateWidth}"

       height="{$actualTemplateHeight}" stroke="black"

       stroke-width="2"/>



  <text xmlns="http://www.w3.org/2000/svg" x="{$x+10}"

       y="{$y+10}" textLength="{$actualTemplateWidth}" 

       fill="red" font-family="Times">

    <xsl:value-of select="@match"/>

  </text>



  <text xmlns="http://www.w3.org/2000/svg" x="{$x + 10}" 

       y="{$y + 10 + ($actualTemplateHeight div 6)}" 

       textLength="{$actualTemplateWidth}" fill="green"

       font-family="Times">

    <xsl:value-of select="@mode"/>

  </text>



  <text xmlns="http://www.w3.org/2000/svg" x="{$x + 10}"

       y="{$y + 10 + ($actualTemplateHeight div 3)}"

       textLength="{$actualTemplateWidth}" fill="blue"

       font-weight="bold" font-family="Terminal">

    <xsl:value-of select="@name"/>

  </text>

</xsl:template>

The links mode is invoked last to ensure the call lines are drawn on top of everything else:

<xsl:template match="xsl:template" mode="links">

  <xsl:param name="x"/>

  <xsl:param name="y"/>

  <xsl:for-each select=".//xsl:call-template">

    <xsl:variable name="sentParams">(<xsl:for-each

         select="xsl:with-param">

        <xsl:value-of select="@name"/>

        <xsl:text>,</xsl:text>

      </xsl:for-each>)</xsl:variable>        

      .... snip ... 

        <xsl:call-template name="drawLines">

          <xsl:with-param name="drawColor" select="'red'"/>

            <xsl:with-param name="x" select="$x"/>

            <xsl:with-param name="y" select="$y"/>

            <xsl:with-param name="sentParams"

                 select="$sentParams"/>

        </xsl:call-template>

      ... snip ...

    </xsl:for-each>

  </xsl:for-each>

  <xsl:for-each select=".//xsl:apply-templates[@mode]">        

        ... snip (for every template with a matching mode)...

        <xsl:call-template name="drawLines">

          <xsl:with-param name="drawColor" select="'cyan'"/>

          <xsl:with-param name="x" select="$x"/>

          <xsl:with-param name="y" select="$y"/>

        </xsl:call-template>

      ... snip ...

    </xsl:for-each>

  </xsl:for-each>

</xsl:template>

The reader should notice the two for-each loops, one for explicit template calls and another for context pushes. Both loops call a drawLines template with a drawColor parameter which specifies what color to draw the call lines (red for explicit calls and cyan for context pushes). The drawLines template draws the call line with the appropriate color and parameters (as text along the drawn path). Below is a trimmed snippet of the drawLines template. Notice how the ancestor axis is used to determine if template calls or context pushes are buried within a conditional block. If they are, the call lines are drawn with a dashed stroke instead:

<xsl:template name="drawLines">

  <xsl:param name="drawColor"/>	

  <xsl:param name="x"/>

  <xsl:param name="y"/>

  <xsl:param name="sentParams"/>

  .. snip ..

  <xsl:variable name="lineDistance"

       select="exslt-math:sqrt(exslt-math:abs($centerX - 

          $targetX) + exslt-math:abs($centerY - $targetY))"/>

  <xsl:choose>

    <xsl:when test="ancestor::xsl:choose|ancestor::xsl:if">

      <line xmlns="http://www.w3.org/2000/svg" x1="{$centerX}"

          y1="{$centerY}" x2="{$targetX}" y2="{$targetY}"

          stroke-width="1" stroke-dasharray="3, 3, 5"

          stroke="{$drawColor}" fill="{$drawColor}"/>

    </xsl:when>

    <xsl:otherwise>

    ... snip ... 

    (draw same line, but with solid stroke) 

    </xsl:otherwise>

  </xsl:choose>

    .. snip ..

  <xsl:if test="normalize-space($sentParams)">

  ... snip (draw parameters) ...

  </xsl:if>

</xsl:template>

Finally, the root template sets up the top-level SVG document elements and invokes the layout mode twice (the first time to draw the templates and the second to draw the lines):

<xsl:template match="/">

  <svg xmlns="http://www.w3.org/2000/svg" 

       width="{$screenWidth}" height="{$screenHeight}">

    <g id="templates" 

        viewbox="0 0 {$screenWidth} {$screenHeight}">

      <xsl:for-each select="/xsl:stylesheet/xsl:template">

        <xsl:apply-templates mode="layout" select=".">

          <xsl:with-param name="templateNumber"

                select="position()"/>

          <xsl:with-param name="x" select="$x"/>

          <xsl:with-param name="y" select="$y"/>

        </xsl:apply-templates>

      </xsl:for-each>

      <xsl:for-each select="/xsl:stylesheet/xsl:template">

        <xsl:apply-templates mode="layout" select=".">

          <xsl:with-param name="templateNumber"

                select="position()"/>

          <xsl:with-param name="x" select="$x"/>

          <xsl:with-param name="y" select="$y"/>

          <xsl:with-param name="drawn" select='"yes"'/>

        </xsl:apply-templates>

      </xsl:for-each>

    </g>

  </svg>

</xsl:template>

The user is able to customize the size of the diagram and percentage of padding to use for the template grid by specifying them as top level parameters to the stylesheet.

Suggested improvements

The diagram below is the rendering of the SVG document that resulted from applying VisualizeStylesheet.xslt on itself:

Generated SVG
Figure 2. SVG generated from application of stylesheet upon itself.

Diagrams generated with this stylesheet should be sufficient for a quick, high-level reference of a stylesheets components. However, certain improvements can be made. Primarily, the stylesheet can attempt to capture all context pushes as call lines. Currently, it only draws call lines for context pushes identified semi-explicitly by matching modes.

The source document of the stylesheet (or any sufficiently complete instance of the source schema, if available) can be used by VisualizeStylesheet.xslt to determine the direction process flow shifts in mode-less context pushes. The select expressions of all templates with match attributes can only be evaluated against a node set consisting of the descendant axis from the context (at the point xsl:apply-templates is invoked). These additional but less certain call links will especially prove helpful in stylesheets where the implementation logic is more pattern-oriented than functional.

Resources