Viewing XML
1. | Visualizing XML trees with XSLT and SVG |
On southard.net wizard Jeff Southard demos his very nice XSLTs visualizing XML trees in SVG. If you don't have it, install the Adobe SVG Viewer first http://www.adobe.com/svg/viewer/install/main.html for Win+Mac, http://www.adobe.com/svg/viewer/install/old.html for Linux. Mouse around the SVGs to see the node values. Here are the sourcefiles. | |
2. | XML serializer implemented in XSLT |
I have written a stylesheet to present any XML in preformatted HTML, seexmlportfolio.com Oliver add: I think David Carlisle was the first who had a simple verbatim XML formatter, used as part of the Schematron report implementation [1]. Myself, I developed a stylesheet which allows coloured output which can be styled via a CSS stylesheet [2]. It seems you are considering some more circumstances than me ("]>" for example) and your stylesheet can be parameterized in a fairly sophisticated way. [1] http://www.ascc.net/xml/schematron/1.5/report1-5/schematron-report.html [2] http://www.informatik.hu-berlin.de/~obecker/XSLT/#xmlverbatim [3] http://xsltsl.sourceforge.net/ | |
3. | Stylesheet to view XML code in browser |
Ken Holman, a frequent contributor to this list, has come up with a generic stylesheet called SHOWTREE that will do what you want. It's at: http://www.cranesoftwrights.com/resources/showtree/index.htm From the description at that page: "The stylesheet will report the node structure and content of an input document, noting the ordinal positions in the hierarchy of each component of the ancestry. The stylesheet exposes root, element, attribute, text, comment, and pi nodes. This stylesheet does not expose the namespace axis due to limitations acknowledged in the current implementation of XT." There are versions of it to support different XSL processors -- at least MSIE and XT. As Ken mentions on the above page, there's another generic tool written by Mike Brown, called the Fancy XML Tree Viewer, at: http://www.skew.org/xml/ It's more graphically-oriented than Ken's; drawback is that it isn't as up-to-date with the spec. In either case -- unless you're using IE5 -- to "see" the generated tree you'll have to feed the XML/XSL combination into a pre-processor like XT or LotusXSL, rather than directly to the browser. | |
4. | Viewing xml files |
Here's an XSLT 1.0-REC Compliant version of the IE5 Default Stylesheet. I've tested it with XT, OracleXSLT, Xalan, and Saxon. <!-- | | XSLT REC Compliant Version of IE5 Default Stylesheet | | Original version by Jonathan Marsh (jmarsh@microsoft.com) | http://msdn.microsoft.com/xml/samples/defaultss/defaultss.xsl | | Conversion to XSLT 1.0 REC Syntax by Steve Muench (smuench@oracle.com) | +--> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output indent="no" method="html"/> <xsl:template match="/"> <HTML> <HEAD> <SCRIPT> <xsl:comment><![CDATA[ function f(e){ if (e.className=="ci") { if (e.children(0).innerText.indexOf("\n")>0) fix(e,"cb"); } if (e.className=="di") { if (e.children(0).innerText.indexOf("\n")>0) fix(e,"db"); } e.id=""; } function fix(e,cl){ e.className=cl; e.style.display="block"; j=e.parentElement.children(0); j.className="c"; k=j.children(0); k.style.visibility="visible"; k.href="#"; } function ch(e) { mark=e.children(0).children(0); if (mark.innerText=="+") { mark.innerText="-"; for (var i=1;i<e.children.length;i++) { e.children(i).style.display="block"; } } else if (mark.innerText=="-") { mark.innerText="+"; for (var i=1;i<e.children.length;i++) { e.children(i).style.display="none"; } } } function ch2(e) { mark=e.children(0).children(0); contents=e.children(1); if (mark.innerText=="+") { mark.innerText="-"; if (contents.className=="db"||contents.className=="cb") { contents.style.display="block"; } else { contents.style.display="inline"; } } else if (mark.innerText=="-") { mark.innerText="+"; contents.style.display="none"; } } function cl() { e=window.event.srcElement; if (e.className!="c") { e=e.parentElement; if (e.className!="c") { return; } } e=e.parentElement; if (e.className=="e") { ch(e); } if (e.className=="k") { ch2(e); } } function ex(){} function h(){window.status=" ";} document.onclick=cl; ]]> </xsl:comment> </SCRIPT> <STYLE> BODY {font:x-small 'Verdana'; margin-right:1.5em} .c {cursor:hand} .b {color:red; font-family:'Courier New'; font-weight:bold; text-decoration:none} .e {margin-left:1em; text-indent:-1em; margin-right:1em} .k {margin-left:1em; text-indent:-1em; margin-right:1em} .t {color:#990000} .xt {color:#990099} .ns {color:red} .dt {color:green} .m {color:blue} .tx {font-weight:bold} .db {text-indent:0px; margin-left:1em; margin-top:0px; margin-bottom:0px;padding-left:.3em; border-left:1px solid #CCCCCC; font:small Courier} .di {font:small Courier} .d {color:blue} .pi {color:blue} .cb {text-indent:0px; margin-left:1em; margin-top:0px; margin-bottom:0px;padding-left:.3em; font:small Courier; color:#888888} .ci {font:small Courier; color:#888888} PRE {margin:0px; display:inline} </STYLE> </HEAD> <BODY class="st"> <xsl:apply-templates/> </BODY> </HTML> </xsl:template> <xsl:template match="processing-instruction()"> <DIV class="e"> <SPAN class="b"> <xsl:call-template name="entity-ref"> <xsl:with-param name="name">nbsp</xsl:with-param> </xsl:call-template> </SPAN> <SPAN class="m"> <xsl:text><?</xsl:text> </SPAN> <SPAN class="pi"> <xsl:value-of select="name(.)"/> <xsl:value-of select="."/> </SPAN> <SPAN class="m"> <xsl:text>?></xsl:text> </SPAN> </DIV> </xsl:template> <xsl:template match="processing-instruction('xml')"> <DIV class="e"> <SPAN class="b"> <xsl:call-template name="entity-ref"> <xsl:with-param name="name">nbsp</xsl:with-param> </xsl:call-template> </SPAN> <SPAN class="m"> <xsl:text><?</xsl:text> </SPAN> <SPAN class="pi"> <xsl:text>xml </xsl:text> <xsl:for-each select="@*"> <xsl:value-of select="name(.)"/> <xsl:text>="</xsl:text> <xsl:value-of select="."/> <xsl:text>" </xsl:text> </xsl:for-each> </SPAN> <SPAN class="m"> <xsl:text>?></xsl:text> </SPAN> </DIV> </xsl:template> <xsl:template match="@*"> <SPAN> <xsl:attribute name="class"> <xsl:if test="xsl:*/@*"> <xsl:text>x</xsl:text> </xsl:if> <xsl:text>t</xsl:text> </xsl:attribute> <xsl:value-of select="name(.)"/> </SPAN> <SPAN class="m">="</SPAN> <B> <xsl:value-of select="."/> </B> <SPAN class="m">"</SPAN> </xsl:template> <xsl:template match="text()"> <DIV class="e"> <SPAN class="b"> </SPAN> <SPAN class="tx"> <xsl:value-of select="."/> </SPAN> </DIV> </xsl:template> <xsl:template match="comment()"> <DIV class="k"> <SPAN> <A STYLE="visibility:hidden" class="b" onclick="return false" onfocus="h()">-</A> <SPAN class="m"> <xsl:text><!--</xsl:text> </SPAN> </SPAN> <SPAN class="ci" id="clean"> <PRE> <xsl:value-of select="."/> </PRE> </SPAN> <SPAN class="b"> <xsl:call-template name="entity-ref"> <xsl:with-param name="name">nbsp</xsl:with-param> </xsl:call-template> </SPAN> <SPAN class="m"> <xsl:text>--></xsl:text> </SPAN> <SCRIPT>f(clean);</SCRIPT> </DIV> </xsl:template> <xsl:template match="*"> <DIV class="e"> <DIV STYLE="margin-left:1em;text-indent:-2em"> <SPAN class="b"> <xsl:call-template name="entity-ref"> <xsl:with-param name="name">nbsp</xsl:with-param> </xsl:call-template> </SPAN> <SPAN class="m"><</SPAN> <SPAN> <xsl:attribute name="class"> <xsl:if test="xsl:*"> <xsl:text>x</xsl:text> </xsl:if> <xsl:text>t</xsl:text> </xsl:attribute> <xsl:value-of select="name(.)"/> <xsl:if test="@*"> <xsl:text> </xsl:text> </xsl:if> </SPAN> <xsl:apply-templates select="@*"/> <SPAN class="m"> <xsl:text>/></xsl:text> </SPAN> </DIV> </DIV> </xsl:template> <xsl:template match="*[node()]"> <DIV class="e"> <DIV class="c"> <A class="b" href="#" onclick="return false" onfocus="h()">-</A> <SPAN class="m"><</SPAN> <SPAN> <xsl:attribute name="class"> <xsl:if test="xsl:*"> <xsl:text>x</xsl:text> </xsl:if> <xsl:text>t</xsl:text> </xsl:attribute> <xsl:value-of select="name(.)"/> <xsl:if test="@*"> <xsl:text> </xsl:text> </xsl:if> </SPAN> <xsl:apply-templates select="@*"/> <SPAN class="m"> <xsl:text>></xsl:text> </SPAN> </DIV> <DIV> <xsl:apply-templates/> <DIV> <SPAN class="b"> <xsl:call-template name="entity-ref"> <xsl:with-param name="name">nbsp</xsl:with-param> </xsl:call-template> </SPAN> <SPAN class="m"> <xsl:text></</xsl:text> </SPAN> <SPAN> <xsl:attribute name="class"> <xsl:if test="xsl:*"> <xsl:text>x</xsl:text> </xsl:if> <xsl:text>t</xsl:text> </xsl:attribute> <xsl:value-of select="name(.)"/> </SPAN> <SPAN class="m"> <xsl:text>></xsl:text> </SPAN> </DIV> </DIV> </DIV> </xsl:template> <xsl:template match="*[text() and not (comment() or processing-instruction())]"> <DIV class="e"> <DIV STYLE="margin-left:1em;text-indent:-2em"> <SPAN class="b"> <xsl:call-template name="entity-ref"> <xsl:with-param name="name">nbsp</xsl:with-param> </xsl:call-template> </SPAN> <SPAN class="m"> <xsl:text><</xsl:text> </SPAN> <SPAN> <xsl:attribute name="class"> <xsl:if test="xsl:*"> <xsl:text>x</xsl:text> </xsl:if> <xsl:text>t</xsl:text> </xsl:attribute> <xsl:value-of select="name(.)"/> <xsl:if test="@*"> <xsl:text> </xsl:text> </xsl:if> </SPAN> <xsl:apply-templates select="@*"/> <SPAN class="m"> <xsl:text>></xsl:text> </SPAN> <SPAN class="tx"> <xsl:value-of select="."/> </SPAN> <SPAN class="m"></</SPAN> <SPAN> <xsl:attribute name="class"> <xsl:if test="xsl:*"> <xsl:text>x</xsl:text> </xsl:if> <xsl:text>t</xsl:text> </xsl:attribute> <xsl:value-of select="name(.)"/> </SPAN> <SPAN class="m"> <xsl:text>></xsl:text> </SPAN> </DIV> </DIV> </xsl:template> <xsl:template match="*[*]" priority="20"> <DIV class="e"> <DIV STYLE="margin-left:1em;text-indent:-2em" class="c"> <A class="b" href="#" onclick="return false" onfocus="h()">-</A> <SPAN class="m"><</SPAN> <SPAN> <xsl:attribute name="class"> <xsl:if test="xsl:*"> <xsl:text>x</xsl:text> </xsl:if> <xsl:text>t</xsl:text> </xsl:attribute> <xsl:value-of select="name(.)"/> <xsl:if test="@*"> <xsl:text> </xsl:text> </xsl:if> </SPAN> <xsl:apply-templates select="@*"/> <SPAN class="m"> <xsl:text>></xsl:text> </SPAN> </DIV> <DIV> <xsl:apply-templates/> <DIV> <SPAN class="b"> <xsl:call-template name="entity-ref"> <xsl:with-param name="name">nbsp</xsl:with-param> </xsl:call-template> </SPAN> <SPAN class="m"> <xsl:text></</xsl:text> </SPAN> <SPAN> <xsl:attribute name="class"> <xsl:if test="xsl:*"> <xsl:text>x</xsl:text> </xsl:if> <xsl:text>t</xsl:text> </xsl:attribute> <xsl:value-of select="name(.)"/> </SPAN> <SPAN class="m"> <xsl:text>></xsl:text> </SPAN> </DIV> </DIV> </DIV> </xsl:template> <xsl:template name="entity-ref"> <xsl:param name="name"/> <xsl:text disable-output-escaping="yes">&</xsl:text> <xsl:value-of select="$name"/> <xsl:text>;</xsl:text> </xsl:template> </xsl:stylesheet> | |
5. | XML Treeviewer |
At my web site I have put up an ASCII XML Tree Viewer. Description: Fancy XML Tree Viewer shows the node structure of an XML document in the form of colorful HTML tables and bulleted lists. There are different ways of representing what's in an XML document. This particular model is what is used by XSLT and is prescribed by Section 5 of the XPath recommendation. This was a learning exercise for me and will hopefully help others better understand node tree hierarchies. I welcome any feedback and constructive criticisms on how to make it look or work better. I am aware of two possible problems: attributes look the same as child nodes, and namespace nodes may not be handled properly, since I develop with XT, which doesn't yet support the namespace axis. Mike reports (October 2001) an updated (very posh) xml tree view stylesheet. Same url as above, new and improved though! For an ASCII art version Description: ASCII XML Tree Viewer shows the node structure of an XML document in the form of plain text 'ASCII art'. There are different ways of representing what's in an XML document. This particular model is what is used by XSLT and is prescribed by Section 5 of the XPath recommendation. ASCII XML Tree Viewer was written by Jeni Tennison and Mike J. Brown. (Thanks Jeni!) Requirements: The ASCII XML Tree Viewer is an XSL document that must be applied to a source tree with an XSLT processor. I have tested it with Michael Kay's SAXON. Wendell Piez later added: If you're looking for one that makes SVG output, I posted one to the list a couple of months ago There's a really nifty one by Tony Graham that renders the tree into HTML tables: you can find that at the archive, (except it may need a bit of tweaking since it was done before XSLT was finalized). | |
6. | XML visualization of structure in SVG |
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. Based on a stylesheet that Wendell Piez did, updated to remove errors (probably in DaveP's markup) by Thomas B Passin <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- 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. Revised July 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:param name="across-start" select="6"/> <!-- a left margin --> <xsl:param name="down-start" select="4"/> <!-- a top margin --> <xsl:param name="across-interval" select="14"/> <!-- size of each horizontal step --> <xsl:param name="text-allowance" select="360"/> <!-- extra space to the right to allow content of text nodes some room --> <xsl:param name="max-text-length" select="32"/> <!-- maximum number of characters to allow to show of a text node --> <xsl:param name="down-interval" select="18"/> <!-- size of each vertical step --> <xsl:param name="line-thickness" select="1"/> <xsl:param name="writing-bump-over" select="6"/> <xsl:param name="writing-bump-up" select="-3"/> <!-- allow for positioning of node labels --> <!-- the following are parameters for styling: --> <xsl:param name="background-color" select="'lemonchiffon'"/> <xsl:param name="tree-color" select="'#8B4513'"/> <xsl:param name="text-font-style" select="'font-size: 12; font-family: Courier'"/> <xsl:param name="text-color" select="'#8A2BE2'"/> <xsl:param name="element-font-style" select="'font-size: 14; font-family: serif'"/> <xsl:param name="element-color" select="'#006400'"/> <xsl:param name="attribute-font-style" select="'font-size: 9; font-family: sans-serif'"/> <xsl:param name="attribute-color" select="'#4682B4'"/> <xsl:param name="code-font-style" select="'font-size: 9; font-family: sans-serif'"/> <xsl:param name="PI-color" select="'#228B22'"/> <xsl:param name="comment-color" select="'#B22222'"/> <!-- the following allow adjustments to the symbols representing nodes: --> <xsl:param name="element-dot-radius" select="2.5"/> <xsl:param name="text-box-width" select="3"/> <xsl:param name="text-box-height" select="5"/> <xsl:variable name="path-style" select="concat('stroke:',$tree-color,';fill:none;stroke-width: ',$line-thickn ess,';stroke-linecap:round')"/> <!-- calculates the depth of the deepest ancestor (so we have an idea of how many levels deep the tree will be): a 'max' trick --> <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> <!-- for the initial viewbox --> <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}" > <defs> <rect id="text-box" x="0" y="{0 - ($text-box-height div 2)}" width="{$text-box-width}" height="{$text-box-height}" style="stroke:{$tree-color}; fill:{$tree-color}"/> <circle id="element-dot" r="{$element-dot-radius}" style="stroke:none; fill:{$tree-color}"/> <polygon id="pi-marker" points="{($text-box-width div 2)} 0, {0 - ($text-box-width div 2)} {0 - $text-box-width}, {0 - ($text-box-width div 2)} {$text-box-width}" style="fill:{$tree-color}"/> <polygon id="comment-marker" points="{0 - ($text-box-width div 2)} 0, {($text-box-width div 2)} {0 - $text-box-width}, {($text-box-width div 2)} {$text-box-width}" style="fill:{$tree-color}"/> </defs> <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()"> <!-- since we're going to draw a line from the parent to the node, we first need to ask "do you know where your parents are?" --> <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> <!-- then we need to know where we ourselves are at --> <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> <!-- now we can draw the line --> <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:param name="self-y"/> <xsl:if test=".."> <!-- element nodes get a little dot --> <use xlink:href="#element-dot" transform="translate({$self-x} {$self-y})"/> </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> </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:param name="self-y"/> <xsl:variable name="text" select="normalize-space(.)"/> <!-- a quick and dirty way to avoid problems with line breaks --> <!-- replace the select attribute with this call if you want to use a fancier way to escape whitespace characters: <xsl:call-template name="escape-ws" <xsl:with-param name="text" select="." / </xsl:call-template --> <use xlink:href="#text-box" transform="translate({$self-x} {$self-y})"/> <!-- text nodes are marked with a little box --> <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="substring($text,1,$max-text-length)"/> <!-- truncate the text node to $max-text-length --> <xsl:if test="string-length($text) > $max-text-length"> <!-- add an ellipsis if necessary --> <xsl:text>...</xsl:text> </xsl:if> <xsl:text>"</xsl:text> </text> </xsl:template> <xsl:template match="processing-instruction()" mode="label"> <xsl:param name="self-x"/> <xsl:param name="self-y"/> <!-- PI nodes a marked with a little arrow --> <use xlink:href="#pi-marker" transform="translate({$self-x} {$self-y})"/> <text x="{$self-x + $writing-bump-over}" y="{$self-y - $writing-bump-up}" style="{$code-font-style}; stroke:none; fill:{$PI-color}"> <xsl:text><?</xsl:text> <xsl:value-of select="concat(local-name(), ' ', .)"/> <xsl:text>></xsl:text> </text> </xsl:template> <xsl:template match="comment()" mode="label"> <xsl:param name="self-x"/> <xsl:param name="self-y"/> <!-- comment nodes are marked with a triangle --> <use xlink:href="#comment-marker" transform="translate({$self-x} {$self-y})"/> <text x="{$self-x + $writing-bump-over}" y="{$self-y - $writing-bump-up}" style="{$code-font-style}; stroke:none; fill:{$comment-color}"> <xsl:text><!-- </xsl:text> <xsl:value-of select="."/> <xsl:text >--></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"/> <path style="{$path-style}" d="M {$fromX} {$fromY} V {$toY} H {$toX}" /> </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, '
')"> <xsl:call-template name="escape-ws"> <xsl:with-param name="text" select="substring-before($text, '
')" /> </xsl:call-template> <xsl:text>\n</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, '	')"> <xsl:value-of select="substring-before($text, '	')" /> <xsl:text>\t</xsl:text> <xsl:call-template name="escape-ws"> <xsl:with-param name="text" select="substring-after($text, '	')" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$text" /> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> | |
7. | Verbatim output. Copy input XML to escaped output |
Below is a version of verbid.xsl that demonstrates the use of a repeatable-id feature. By this I mean repeatable across xslt processors and across runs, assuming the same xsl source. It may be over-engineered for schematron in that it handles things like attributes, comments, processing instructions and text which are not normally the context of schematron rules. It may be under-engineered as a general xpath function in that I coded but haven't yet tested the support for namespace nodes. <!-- Copyright 1999-2008 David Carlisle NAG Ltd davidc@nag.co.uk Free use granted under GPL or MPL. Render XML in an HTML pre element. USAGE ===== include this stylesheet into your main stylesheet via <xsl:import href="verb.xsl"/> Then a typical usage might be to render an <example> element twice, first as literal XML, then secondly styled as normal. One would use, in your main stylesheet: <xsl:template match="example"> <pre> <xsl:apply-templates mode="verb"/> </pre> <xsl:apply-templates/> </xsl:template> This would put the contents of the example element (but not `<example>') in the HTML pre element. If you want the verbatim mode to include the current, `example' element, modify the above to say <xsl:apply-templates mode="verb" select="."/> --> <!-- verb mode --> <!-- Does not really give verbatim copy of the file as that information not present in the parsed document, but should give something that renders in HTML as a well formed XML document that would parse to give same XML tree as the original --> <!-- non empty elements and other nodes. --> <?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template mode="verb" match="*[*]|*[text()]|*[comment()]|*[processing-instruction()]"> <xsl:variable name="repeatable-id"> <xsl:apply-templates mode="schematron-get-full-path" select="."/> </xsl:variable> <a name="{$repeatable-id}"/> <xsl:value-of select="concat('<',name(.))"/> <xsl:apply-templates mode="verb" select="@*"/> <xsl:text>></xsl:text> <xsl:apply-templates mode="verb"/> <xsl:value-of select="concat('</',name(.),'>')"/> </xsl:template> <!-- empty elements --> <xsl:template mode="verb" match="*"> <xsl:variable name="repeatable-id"> <xsl:apply-templates mode="schematron-get-full-path" select="."/> </xsl:variable> <a name="{$repeatable-id}"/> <xsl:value-of select="concat('<',name(.))"/> <xsl:apply-templates mode="verb" select="@*"/> <xsl:text>/></xsl:text> </xsl:template> <!-- attributes Output always surrounds attribute value by " so we need to make sure no literal " appear in the value --> <xsl:template mode="verb" match="@*"> <xsl:variable name="repeatable-id"> <xsl:apply-templates mode="schematron-get-full-path" select="."/> </xsl:variable> <a name="{$repeatable-id}"/> <xsl:value-of select="concat(' ',name(.),'=')"/> <xsl:text>"</xsl:text> <xsl:call-template name="string-replace"> <xsl:with-param name="from" select="'"'"/> <xsl:with-param name="to" select="'&quot;'"/> <xsl:with-param name="string" select="."/> </xsl:call-template> <xsl:text>"</xsl:text> </xsl:template> <!-- pis --> <xsl:template mode="verb" match="processing-instruction()"> <xsl:variable name="repeatable-id"> <xsl:apply-templates mode="schematron-get-full-path" select="."/> </xsl:variable> <a name="{$repeatable-id}"/> <xsl:value-of select="concat('<?',name(.),' ',.,'?>')"/> </xsl:template> <!-- only works if parser passes on comment nodes --> <xsl:template mode="verb" match="comment()"> <xsl:variable name="repeatable-id"> <xsl:apply-templates mode="schematron-get-full-path" select="."/> </xsl:variable> <a name="{$repeatable-id}"/> <xsl:value-of select="concat('<!--',.,'-->')"/> </xsl:template> <!-- text elements need to replace & and < by entity references do > as well, just for balance --> <xsl:template mode="verb" match="text()"> <xsl:variable name="repeatable-id"> <xsl:apply-templates mode="schematron-get-full-path" select="."/> </xsl:variable> <a name="{$repeatable-id}"/> <xsl:call-template name="string-replace"> <xsl:with-param name="to" select="'&gt;'"/> <xsl:with-param name="from" select="'>'"/> <xsl:with-param name="string"> <xsl:call-template name="string-replace"> <xsl:with-param name="to" select="'&lt;'"/> <xsl:with-param name="from" select="'<'"/> <xsl:with-param name="string"> <xsl:call-template name="string-replace"> <xsl:with-param name="to" select="'&amp;'"/> <xsl:with-param name="from" select="'&'"/> <xsl:with-param name="string" select="."/> </xsl:call-template> </xsl:with-param> </xsl:call-template> </xsl:with-param> </xsl:call-template> </xsl:template> <!-- end verb mode --> <!-- replace all occurences of the character(s) `from' by the string `to' in the string `string'.--> <xsl:template name="string-replace"> <xsl:param name="string"/> <xsl:param name="from"/> <xsl:param name="to"/> <xsl:choose> <xsl:when test="contains($string,$from)"> <xsl:value-of select="substring-before($string,$from)"/> <xsl:value-of select="$to"/> <xsl:call-template name="string-replace"> <xsl:with-param name="string" select="substring-after($string,$from)"/> <xsl:with-param name="from" select="$from"/> <xsl:with-param name="to" select="$to"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$string"/> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- repeatable-id maker --> <xsl:template match="/" mode="schematron-get-full-path"/> <xsl:template match="text()" mode="schematron-get-full-path"> <xsl:apply-templates select="parent::*" mode="schematron-get-full-path"/> <xsl:value-of select="concat('+text()[', 1+ count(preceding-sibling::text()), ']')"/> </xsl:template> <xsl:template match="comment()" mode="schematron-get-full-path"> <xsl:apply-templates select="parent::*" mode="schematron-get-full-path"/> <xsl:value-of select="concat('+comment()[', 1+ count(preceding-sibling::comment()), ']')"/> </xsl:template> <xsl:template match="processing-instruction()" mode="schematron-get-full-path"> <xsl:apply-templates select="parent::*" mode="schematron-get-full-path"/> <xsl:value-of select="concat('+processing-instruction()[', 1+ count(preceding-sibling::processing-instruction()), ']')"/> </xsl:template> <xsl:template match="@*" mode="schematron-get-full-path"> <xsl:apply-templates select="parent::*" mode="schematron-get-full-path"/> <xsl:value-of select="concat('+@', name())"/> </xsl:template> <xsl:template match="*" mode="schematron-get-full-path" priority="-0.5"> <xsl:apply-templates select="parent::*" mode="schematron-get-full-path"/> <xsl:text>+</xsl:text> <xsl:choose> <xsl:when test="count(. | ../namespace::*) = count(../namespace::*)"> <xsl:value-of select="concat('+namespace::[',1+count(namespace::*),']')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="concat('+',name(),'[',1+ count(preceding-sibling::*[name()=name(current())]),']')"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> | |
8. | Pretty printing xml in html |
I was having a dickens of a time getting namespace declarations to appear in appropriate places (i.e. where and only where necessary). I was thinking I'd have to build up association lists of namespaces needed and namespaces currently in scope, and pass them to recursive template calls... then pondering how to construct and parse such lists as string, and not wanting to have to mess with escaping spaces within URI's... Or should I use the node-set() extension to build association lists, thus sacrificing some portability, but saving myself a lot of headache and making things more efficient? Finally I realized (duh!) I could use the namespace nodes provided in the source tree! I didn't need to keep my own lists of what had already been declared. I.e. for each source tree element being serialized, I could just produce a namespace declaration for each namespace node (of the current element) that wasn't a duplicate of one of the current element's parent's namespace nodes. So, for anyone who wants to serialize XML to HTML using XSL, my solution is below. (If you want you could make indent-increment and ns-decl-extra-indent into parameters of the first template, instead of variables.) <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" indent="yes" /> <!-- escape-xml mode: serialize XML tree to text, with indent Based very loosely on templates by Wendell Piez --> <xsl:variable name="nl"><xsl:text> </xsl:text>< /xsl:variable> <xsl:variable name="indent-increment" select="' '" /> <xsl:variable name="ns-decl-extra-indent" select="' '" /> <xsl:template match="*" mode="escape-xml"> <xsl:param name="indent-string" select="$indent-increment" /> <xsl:param name="is-top" select="'true'" /> <!-- true if this is the top of the tree being serialized --> <xsl:param name="exclude-prefixes" select="''" /> <!-- ns-prefixes to avoid declaring --> <xsl:value-of select="$indent-string" /> <xsl:call-template name="write-starttag"> <xsl:with-param name="is-top" select="$is-top" /> <xsl:with-param name="indent-string" select="$indent-string" /> <xsl:with-param name="exclude-prefixes" select="$exclude-prefixes" /> </xsl:call-template> <xsl:if test="*"><xsl:value-of select="$nl" /></xsl:if> <xsl:apply-templates mode="escape-xml"> <xsl:with-param name="indent-string" select="concat($indent-string, $indent-increment)" /> <xsl:with-param name="is-top" select="'false'" /> </xsl:apply-templates> <xsl:if test="*"><xsl:value-of select="$indent-string" /></xsl:if> <xsl:if test="*|text()|comment()|processing-instruction()"><xsl:call-template name="write-endtag" /></xsl:if> <xsl:value-of select="$nl" /> </xsl:template> <xsl:template name="write-starttag"> <xsl:param name="is-top" select="'false'" /> <xsl:param name="exclude-prefixes" select="''" /> <!-- ns-prefixes to avoid declaring --> <xsl:param name="indent-string" select="''" /> <xsl:text><</xsl:text> <xsl:value-of select="name()"/> <xsl:for-each select="@*"> <xsl:call-template name="write-attribute"/> </xsl:for-each> <xsl:call-template name="write-namespace-declarations"> <xsl:with-param name="is-top" select="$is-top" /> <xsl:with-param name="exclude-prefixes" select="$exclude-prefixes" /> <xsl:with-param name="indent-string" select="$indent-string" /> </xsl:call-template> <xsl:if test="not(*|text()|comment()|processing-instruction())"> /</xsl:if> <xsl:text>></xsl:text> </xsl:template> <xsl:template name="write-endtag"> <xsl:text></</xsl:text> <xsl:value-of select="name()"/> <xsl:text>></xsl:text> </xsl:template> <xsl:template name="write-attribute"> <xsl:text> </xsl:text> <xsl:value-of select="name()"/> <xsl:text>="</xsl:text> <xsl:value-of select="."/> <xsl:text>"</xsl:text> </xsl:template> <!-- Output namespace declarations for the current element. --> <!-- Assumption: if an attribute in the source tree uses a particular namespace, its parent element will have a namespace node for that namespace (because the declaration for the namespace must be on the parent element or one of its ancestors). --> <xsl:template name="write-namespace-declarations"> <xsl:param name="is-top" select="'false'" /> <xsl:param name="indent-string" select="''" /> <xsl:param name="exclude-prefixes" select="''" /> <xsl:variable name="current" select="." /> <xsl:variable name="parent-nss" select="../namespace::*" /> <xsl:for-each select="namespace::*"> <xsl:variable name="ns-prefix" select="name()" /> <xsl:variable name="ns-uri" select="string(.)" /> <xsl:if test="not(contains(concat(' ', $exclude-prefixes, ' xml '), concat(' ', $ns-prefix, ''))) and ($is-top = 'true' or not($parent-nss[name() = $ns-prefix and string(.) = $ns-uri])) "> <!-- This namespace node doesn't exist on the parent, at least not with that URI, so we need to add a declaration. --> <!-- We could add the test and ($ns-prefix = '' or ($current//.|$current//@*)[substring-before(name(), ':') = $ns-prefix]) i.e. "and it's used by this element or some descendant (or descendant-attribute) thereof:" Only problem with the above test is that sometimes namespace declarations are needed even though they're not used by a descendant element or attribute: e.g. if the input is a stylesheet, prefixes have to be declared if they're used in XPath expressions [which are in attribute values]. We could have problems in this area with regard to xsp-request. --> <xsl:value-of select="concat($nl, $indent-string, $ns-decl-extra-indent)" /> <xsl:choose> <xsl:when test="$ns-prefix = ''"> <xsl:value-of select="concat('xmlns="', $ns-uri, '"')" /> </xsl:when> <xsl:otherwise> <xsl:value-of select="concat('xmlns:', $ns-prefix, '="', $ns-uri, '"')" /> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:for-each> </xsl:template> </xsl:stylesheet> |