This is the mail archive of the xsl-list@mulberrytech.com mailing list .


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: transformation of any XML to visualization of structure


At 05:47 AM 2/10/01 -0800, Tobi wrote:
>Hi all,
>
>does anyone know where to find XSLTs that take any
>XML-input and transform it to a visualization of it's
>structure in HTML, SVG, or any other format?

I was roused by this from an XSL-List slumber (hi everyone) to rework an 
effort from some months ago. The stylesheet appearing below will make an 
SVG graphic showing the tree structure of a document. It's been tested in 
IBM's SVGViewer, Adobe's plugin, and FOP.

I bet a better graphic designer could spiff it up ... I'm kind of feeling 
my way with SVG. But the basic logic should be useful. It makes generous 
use of global variables, named templates and a mode to encapsulate the 
processing and allow you to tweak it at various levels. Nicer versions 
would be nice to see.

Enjoy! --Wendell

================
<xsl:stylesheet version="1.0"
                 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- Targeting FOP, include the namespace declaration
      xmlns="http://www.w3.org/2000/svg" -->

<!-- Draws an SVG "tree structure" representing the XSLT/XPath
      infoset of an arbitrary input document.

      Distribute and alter freely.
      Please acknowledge your sources.
      (You could use this stylesheet by importing it into
       your own and overriding its variable settings.)

      Includes a named template from Jeni Tennison's and
      Mike Brown's "ASCII Art" Tree Viewing stylesheet.

      By Wendell Piez, Mulberry Technologies, February 2001

-->

<xsl:output method="xml" indent="yes" />

<!-- amend or comment this out if you want to see any whitespace-only text 
nodes -->
<xsl:strip-space elements="*"/>

<xsl:variable name="across-start" select="6"/>

<xsl:variable name="down-start" select="4"/>

<xsl:variable name="across-interval" select="14"/>

<xsl:variable name="text-allowance" select="360"/>
<!-- extra space to the right to allow content of text nodes some room -->

<xsl:variable name="down-interval" select="18"/>

<xsl:variable name="line-thickness" select="1"/>

<xsl:variable name="writing-bump-over" select="6"/>

<xsl:variable name="writing-bump-up" select="-3"/>

<xsl:variable name="background-color" select="'#FFDAB9'"/>

<xsl:variable name="tree-color" select="'#8B4513'"/>

<xsl:variable name="text-font-style" select="'font-size: 12; font-family: 
Courier'"/>

<xsl:variable name="text-color" select="'#8A2BE2'"/>

<xsl:variable name="element-font-style" select="'font-size: 14; 
font-family: serif'"/>

<xsl:variable name="element-color" select="'#006400'"/>

<xsl:variable name="attribute-font-style" select="'font-size: 9; 
font-family: sans-serif'"/>

<xsl:variable name="attribute-color" select="'#4682B4'"/>

<xsl:variable name="code-font-style" select="'font-size: 9; font-family: 
sans-serif'"/>

<xsl:variable name="PI-color" select="'#228B22'"/>

<xsl:variable name="comment-color" select="'#B22222'"/>

<xsl:variable name="element-dot-radius" select="2.5"/>

<xsl:variable name="text-box-width" select="3"/>

<xsl:variable name="text-box-height" select="5"/>

<xsl:variable name="deepest">
   <!-- returns the depth (in ancestors) of the deepest node(s) -->
   <xsl:for-each select="//node()[not(node())]">
     <xsl:sort select="count(ancestor-or-self::*)" order="descending" 
data-type="number"/>
     <xsl:if test="position()=1">
       <xsl:value-of select="count(ancestor-or-self::*)"/>
     </xsl:if>
   </xsl:for-each>
</xsl:variable>

<xsl:variable name="full-width" select="($across-interval * ($deepest +2)) 
+ (2 * $across-start) + $text-allowance"/>

<xsl:variable name="full-height" select="((count(//node()) + 2) * 
$down-interval) + (2 * $down-start)"/>

<xsl:variable name="apos">'</xsl:variable>

<!-- internal functions to calculate position for any node -->

<xsl:template name="get-x-coordinate">
   <xsl:param name="node" select="/*"/>
   <xsl:value-of select="(count($node/ancestor-or-self::node()) * 
$across-interval) + $across-start"/>
</xsl:template>

<xsl:template name="get-y-coordinate">
   <xsl:param name="node" select="/*"/>
   <xsl:value-of 
select="(count($node/preceding::node()|$node/ancestor-or-self::node()) * 
$down-interval) + $down-start"/>
</xsl:template>

<!-- -->

<!-- root template -->

<xsl:template match="/">
   <svg width="{$full-width}" height="{$full-height}" >
     <rect x="0" y="0" width="{$full-width}" height="{$full-height}" 
style="fill:{$background-color}"/>
     <g>
       <xsl:apply-templates select="." mode="label">
         <xsl:with-param name="self-x" select="$across-start + 
$across-interval"/>
         <xsl:with-param name="self-y" select="$down-start + $down-interval"/>
       </xsl:apply-templates>
       <xsl:apply-templates/>
     </g>
   </svg>
</xsl:template>

<!-- -->

<!-- template for any child node -->

<xsl:template match="node()">
   <xsl:variable name="parent-x">
     <xsl:call-template name="get-x-coordinate">
       <xsl:with-param name="node" select=".."/>
     </xsl:call-template>
   </xsl:variable>
   <xsl:variable name="parent-y">
     <xsl:call-template name="get-y-coordinate">
       <xsl:with-param name="node" select=".."/>
     </xsl:call-template>
   </xsl:variable>
   <xsl:variable name="self-x">
     <xsl:call-template name="get-x-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:variable>
   <xsl:variable name="self-y">
     <xsl:call-template name="get-y-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:variable>
   <xsl:call-template name="drawpath">
     <xsl:with-param name="fromX" select="$parent-x"/>
     <xsl:with-param name="fromY" select="$parent-y"/>
     <xsl:with-param name="toX" select="$self-x"/>
     <xsl:with-param name="toY" select="$self-y"/>
   </xsl:call-template>
   <!-- place a label on the node (each node type gets a different label) -->
   <xsl:apply-templates select="." mode="label">
     <xsl:with-param name="self-x" select="$self-x"/>
     <xsl:with-param name="self-y" select="$self-y"/>
   </xsl:apply-templates>
   <!-- descend to the next level down -->
   <xsl:apply-templates/>
</xsl:template>

<!-- label for each node type -->

<xsl:template match="*|/" mode="label">
   <xsl:param name="self-x">
     <xsl:call-template name="get-x-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:param>
   <xsl:param name="self-y">
     <xsl:call-template name="get-y-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:param>
   <xsl:if test="..">
     <!-- element nodes get a little dot -->
     <circle cx="{$self-x}" cy="{$self-y}" r="{$element-dot-radius}" 
style="stroke:none; fill:{$tree-color}"/>
   </xsl:if>
   <text x="{$self-x + $writing-bump-over}" y="{$self-y - $writing-bump-up}"
         style="{$element-font-style}; stroke:none; fill:{$element-color}">
     <xsl:if test="not(..)">
       <!-- the root node gets labeled with a / -->
       <xsl:text>/</xsl:text>
     </xsl:if>
     <xsl:value-of select="local-name()"/>
     <xsl:if test="@*">
       <!-- any attribute nodes are written out too -->
       <tspan style="{$attribute-font-style}; stroke:none; 
fill:{$attribute-color}">
         <xsl:for-each select="@*">
             <xsl:text>&#xA0;</xsl:text>
             <xsl:value-of select="local-name()"/>
             <xsl:text>="</xsl:text>
             <xsl:value-of select="."/>
             <xsl:text>"</xsl:text>
         </xsl:for-each>
       </tspan>
     </xsl:if>
   </text>
</xsl:template>

<xsl:template match="text()" mode="label">
   <xsl:param name="self-x">
     <xsl:call-template name="get-x-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:param>
   <xsl:param name="self-y">
     <xsl:call-template name="get-y-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:param>
   <!-- text nodes are marked with a little box -->
   <rect x="{$self-x - ($text-box-width div 2)}"  y="{$self-y - 
($text-box-height div 2)}"  width="{$text-box-width}" 
height="{$text-box-height}" style="stroke:{$tree-color}; fill:{$tree-color}"/>
   <text x="{$self-x + $writing-bump-over}" y="{$self-y - $writing-bump-up}"
         style="{$text-font-style}; stroke:none; fill:{$text-color}">
     <xsl:text>"</xsl:text>
     <xsl:value-of select="."/>
<!-- replace the value-of with this call
      if you want or need to escape whitespace characters
     <xsl:call-template name="escape-ws">
       <xsl:with-param name="text" select="." />
     </xsl:call-template>
-->
     <xsl:text>"</xsl:text>
   </text>
</xsl:template>

<xsl:template match="processing-instruction()" mode="label">
   <xsl:param name="self-x">
     <xsl:call-template name="get-x-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:param>
   <xsl:param name="self-y">
     <xsl:call-template name="get-y-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:param>
   <!-- PI nodes a marked with a little arrow -->
   <polygon points="{$self-x + ($text-box-width div 2)},{$self-y},{$self-x 
- ($text-box-width div 2)},{$self-y - $text-box-width},{$self-x - 
($text-box-width div 2)},{$self-y + $text-box-width}" 
style="fill:{$tree-color}"/>
   <text x="{$self-x + $writing-bump-over}" y="{$self-y - $writing-bump-up}"
         style="{$code-font-style}; stroke:none; fill:{$PI-color}">
     <xsl:text/>&lt;?<xsl:value-of select="concat(local-name(), ' ', 
.)"/>?&gt;<xsl:text/>
   </text>
</xsl:template>

<xsl:template match="comment()" mode="label">
   <xsl:param name="self-x">
     <xsl:call-template name="get-x-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:param>
   <xsl:param name="self-y">
     <xsl:call-template name="get-y-coordinate">
       <xsl:with-param name="node" select="."/>
     </xsl:call-template>
   </xsl:param>
   <!-- comment nodes are marked with a little triangle -->
   <polygon points="{$self-x - ($text-box-width div 2)},{$self-y},{$self-x 
+ ($text-box-width div 2)},{$self-y - $text-box-width},{$self-x + 
($text-box-width div 2)},{$self-y + $text-box-width}" 
style="fill:{$tree-color}"/>
   <text x="{$self-x + $writing-bump-over}" y="{$self-y - $writing-bump-up}"
         style="{$code-font-style}; stroke:none; fill:{$comment-color}">
     <xsl:text/>&lt;!-- <xsl:value-of select="."/> --&gt;<xsl:text/>
   </text>
</xsl:template>

<!-- template to draw the line from one node to the next ... feel free to 
enhance ... -->

<xsl:template name="drawpath">
   <xsl:param name="fromX" select="10"/>
   <xsl:param name="fromY" select="10"/>
   <xsl:param name="toX" select="20"/>
   <xsl:param name="toY" select="20"/>
   <g style="stroke:{$tree-color}; stroke-width:{$line-thickness}; 
stroke-linecap:round" >
     <line x1="{$fromX}"  y1="{$fromY}" x2="{$fromX}" y2="{$toY}" />
     <line x1="{$fromX}"  y1="{$toY}" x2="{$toX}" y2="{$toY}" />
   </g>
</xsl:template>


<!-- recursive template to escape backslashes, apostrophes, newlines and 
tabs -->
<!-- gratefully duplicated from Jeni Tennison's and Mike Brown's ASCII Art-
      Tree Viewing stylesheet. -->

<xsl:template name="escape-ws">
     <xsl:param name="text" />
     <xsl:choose>
         <xsl:when test="contains($text, '\')">
             <xsl:call-template name="escape-ws">
                 <xsl:with-param name="text" 
select="substring-before($text, '\')" />
             </xsl:call-template>
             <xsl:text>\\</xsl:text>
             <xsl:call-template name="escape-ws">
                 <xsl:with-param name="text" select="substring-after($text, 
'\')" />
             </xsl:call-template>
         </xsl:when>
         <xsl:when test="contains($text, $apos)">
             <xsl:call-template name="escape-ws">
                 <xsl:with-param name="text" 
select="substring-before($text, $apos)" />
             </xsl:call-template>
             <xsl:text>\'</xsl:text>
             <xsl:call-template name="escape-ws">
                 <xsl:with-param name="text" select="substring-after($text, 
$apos)" />
             </xsl:call-template>
         </xsl:when>
         <xsl:when test="contains($text, '&#xA;')">
             <xsl:call-template name="escape-ws">
                 <xsl:with-param name="text" 
select="substring-before($text, '&#xA;')" />
             </xsl:call-template>
             <xsl:text>\n</xsl:text>
             <xsl:call-template name="escape-ws">
                 <xsl:with-param name="text" select="substring-after($text, 
'&#xA;')" />
             </xsl:call-template>
         </xsl:when>
         <xsl:when test="contains($text, '&#x9;')">
             <xsl:value-of select="substring-before($text, '&#x9;')" />
             <xsl:text>\t</xsl:text>
             <xsl:call-template name="escape-ws">
                 <xsl:with-param name="text" select="substring-after($text, 
'&#x9;')" />
             </xsl:call-template>
         </xsl:when>
         <xsl:otherwise><xsl:value-of select="$text" /></xsl:otherwise>
     </xsl:choose>
</xsl:template>

</xsl:stylesheet>


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]