Visualizing XSLT in SVG
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.
Resources
|
| Titles Only | Titles Only | Newest First |
- A rework to XHTML+SMIL+VML
2003-07-10 16:02:47 William A Slabbekoorn
Great stuff,
I am quite interested in your last two remarks about improving the style sheet. Could you elaborate a bit on what you mean. (The first referering to a general <xsl:apply-templates /> I presume?).
Anyway, I think you will like this:
I have re-written your transformation sheet towards VML and XHTML+SMIL.
As VML is natively supported it blends nicely with XHTML and SMIL in one page transformation using MSXML3/4. Added some animation to show which templates are related by aaply/call templates etc.
Very interested about your ideas to enhance the XSLT sheets (SVG and VML)
adress of the demo page http://cybarber.ath.cx/VMLXSLTview.xslt
Cybarber
- A rework to XHTML+SMIL+VML
2003-07-13 13:47:21 William A Slabbekoorn
I have now added support of non-moded apply-templates and global parameters and variables as well.
http://cybarber.ath.cx/VMLXSLTview.xslt
Navigating to that local will show the very XSLT sheet in XHTML+SMIL+VML using MSXML3 (IE55/IE6).
Cybarber
- A rework to XHTML+SMIL+VML
- My ways of grappling w/ XSLT complexity
2003-06-08 09:58:36 Tom Moertel
In your call for comments, you asked, "Do you have any favorite ways to debug or understand XSLT sheets?" My favorite strategy is to reduce XSLT's complexity at its source.
To my mind, XSLT has two problems that work together to bog us down. First, XSLT lacks the expressiveness to handle elegantly many problems that are commonplace. Second, XSLT is essentially a programming language embedded within XML. While XML is great for expressing marked-up documents, it is makes a syntactically noisy and verbose carrier for a programming language.
The first problem forces us to write more XSLT than we ought to, and the second magnifies the bloating properties of that extra code. A nasty duo, they are.
Luckily for us, there are decent solutions to both of these problems. First, EXSLT (http://www.exslt.org/) is a community effort to extend XSLT with support for commonplace needs like string manipulation, date and time processing, and fragment-to-nodeset conversion. Most popular XSLT processing engines support EXSLT in one way or another, either via built-in support or via the use of EXSLT implementations written in vanilla XSLT. If you haven't used EXSLT, do check it out. It can save you a lot of code.
For the second problem, take a look at PXSL ("pixel") (http://community.moertel.com/pxsl/). It's an XML shorthand that was designed specifically for the purpose of representing data-centric documents and embedded programming languages like XSLT. (It even has built-in XSLT shortcuts.) It's simple, straightforward, and open source. For advanced users, it offers custom shortcuts and macros, but the streamlined syntax alone makes stylesheets much easier to work with. It does a good job at taking care of XSLT's second problem.
- My ways of grappling w/ XSLT complexity
2003-06-10 06:45:00 Chimezie Ogbuji
I wasn't aware of PXSL. It definately seems like a very useful tool for writing shorthand XML (and so is a perfect candidate for writing terse XSLT). Thanks for the link.
- My ways of grappling w/ XSLT complexity
