Visualizing XSLT in SVG
by Chimezie Ogbuji
June 04, 2003
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.
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:
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.