Difference between revisions of "XSLT"
Line 48: | Line 48: | ||
<xsl:variable name="APOS">'</xsl:variable> | <xsl:variable name="APOS">'</xsl:variable> | ||
<xsl:strip-space elements="*"/> | <xsl:strip-space elements="*"/> | ||
− | <xsl:preserve-space | + | <xsl:preserve-space elements="line"/> |
<xsl:template match="/"> | <xsl:template match="/"> |
Revision as of 08:24, 16 May 2014
Utilizing XSLT to its fullest will probably require some paradigm shifting into functional programming. Particularly smart transforms will require recursion.
Contents
Identity transform / Strip matching elements
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="expression-returning-unwanted-elements"/>
</xsl:stylesheet>
Case transformation
XSLT has no built-in toUpper/toLower-style functions. The most commonly suggested solution seems to be to co-opt the built-in transform()
function, making explicit your tables.
<xsl:transform ...>
<xsl:variable name="upper">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>
<xsl:variable name="lower">abcdefghijklmnopqrstuvwxyz</xsl:variable>
...
<xsl:value-of select="translate(.,$upper,$lower)"/>
...
This obviously doesn't handle the occurrence of international characters. For that, try XSLT case transformation tables.
Text indentation toy
This transform handles a document with top, levels, and lines (root = top, top ( (level|line)* ), level ( (level|line)* )) and indents lines to the appropriate depth for the level. It also has an optional abs attribute for a level to interrupt the normal scheme (for example, abs="0" would make sense for embedded POD doc).
The indent and indent-depth templates demonstrate the use of templates in terms of the current state rather than parameters.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:variable name="TAB" select="'	'"/>
<xsl:variable name="ENDL" select="' '"/>
<xsl:variable name="BL" select="' '"/>
<xsl:variable name="APOS">'</xsl:variable>
<xsl:strip-space elements="*"/>
<xsl:preserve-space elements="line"/>
<xsl:template match="/">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="level">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="line">
<xsl:call-template name="indent"/>
<xsl:apply-templates/>
<xsl:value-of select="$ENDL"/>
</xsl:template>
<xsl:template name="repeat">
<xsl:param name="str"/>
<xsl:param name="count" select="0"/>
<xsl:if test="$count > 0">
<xsl:value-of select="$str"/>
<xsl:call-template name="repeat">
<xsl:with-param name="str" select="$str"/>
<xsl:with-param name="count" select="$count - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="indent">
<xsl:variable name="depth">
<xsl:call-template name="indent-depth"/>
</xsl:variable>
<xsl:call-template name="repeat">
<xsl:with-param name="str" select="$TAB"/>
<xsl:with-param name="count" select="number($depth)"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="indent-depth">
<xsl:variable name="own-value">
<xsl:choose>
<xsl:when test="self::level">1</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="parent-value">
<xsl:choose>
<xsl:when test="parent::top">0</xsl:when>
<xsl:when test="parent::level[@abs]"><xsl:value-of select="@abs"/></xsl:when>
<xsl:when test="parent::*">
<xsl:for-each select="parent::*">
<xsl:call-template name="indent-depth"/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="number($own-value) + number($parent-value)"/>
</xsl:template>
</xsl:stylesheet>
Unorganized notes
Inline link
INLINE LINK
<?xml-stylesheet type="text/xsl" href="sheetFoo.xsl"?>
Boilerplate
BOILERPLATE
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<html>
<!-- ... -->
</html>
</xsl:template>
</xsl:stylesheet>
value-of
VALUE
XPath context is from <xsl:template/> or <xsl:for-each/>.
XPath for current context is "."
<xsl:value-of select="path/to/node"/>
for-each
FOREACH
XPath filters apply (eg "node[subnode='value']").
<xsl:sort/> is optional.
<xsl:for-each select="xpath/expression">
<!-- executes this for every <expression/> child of a <xpath/> -->
<xsl:sort select="foo"/><!-- sort by the <foo/> subnode -->
<li>
<!-- The value of the node <foo/> under this <expression/> -->
<xsl:value-of select="foo"/>
</li>
</xsl:for-each>
if
IF
<xsl:if test="condition">
condition was true
</xsl:if>
choose/when/otherwise (If-Then-Else)
IF-THEN-ELSE
<xsl:choose>
<xsl:when test="ifThis">
ifThis was true
</xsl:when>
<xsl:when test="elseifThis">
ifThis was not true
elseifThis was true
</xsl:when>
<xsl:otherwise>
ifThis and elseifThis were both not true
</xsl:otherwise>
</xsl:choose>
Conditions like childname < 0 childname > 0
template (subroutines)
SUBROUTINES
It's hard to tell how the scoping works, but so far it seems that the template of match="/" is the one that is rendered outmost.
<xsl:template match="/">
<html>
<body>
<h1>Welcome to my Nightmare</h1>
<ul><xsl:apply-templates/></ul>
</body>
</html>
</xsl:template>
<xsl:template match="cd">
<li>
<xsl:apply-templates select="title"/>
<xsl:apply-templates select="artist"/>
</li>
</xsl:template>
<xsl:template match="title">
<b><xsl:value-of select="."/></b>
</xsl:template>
<xsl:template match="artist">
(<xsl:value-of select="."/>)
</xsl:template>
output (output format control)
OUTPUT CONTROL
<xsl:output method="text" indent="no" media-type="application/json"/>
method is:
- xml
- html
- text
media-type is the MIME type of the output.
- SVG : image/svg+xml
See also http://www.w3schools.com/xsl/el_output.asp
more
<xsl:template name="foo">
<xsl:param name="s"/>
<!-- ... -->
</xsl:template>
<xsl:call-template name="foo">
<xsl:with-param name="s" select="XPathReturnValueForS"/>
</xsl:call-template>
A post-process-based means of preventing certain empty tags from collapsing
<?xml version="1.0" encoding="UTF-8"?>
<!--
NONCE=`uuidgen` &&
xsltproc -%-stringparam nonce "$NONCE" ex.xsl test.xml |
perl -n -e 's{<!-%-'$NONCE'-%->}{}sg; print'
-->
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:param name="nonce">REMOVE-ME</xsl:param>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="style">
<style>
<xsl:if test="not(@type)">
<xsl:attribute name="type">text/css</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="@*|node()"/>
</style>
</xsl:template>
<xsl:template match="script">
<script>
<xsl:if test="not(@type)">
<xsl:attribute name="type">text/javascript</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="@*|node()"/>
<xsl:if test="not(node())">
<xsl:comment><xsl:value-of select="$nonce"/></xsl:comment>
</xsl:if>
</script>
</xsl:template>
</xsl:transform>
Tools
XSLT Processors
Xalan-J
java org.apache.xalan.xslt.Process -XSL sheet.xsl -IN input.xml
For XSLT 1.0.
See also [1].
xsltproc (LibXSLT)
xsltproc sheet.xsl input.xml
For XSLT 1.0.
See also xsltproc.
Saxon
java net.sf.saxon.Transform -xsl:sheet.xsl -s:input.xml [ -o:output.xml ]
For XSLT 2.0. The open-source variant, Saxon-HE, is intentionally broken to leave out a lot of the XSLT 2.0 functionality, but there is enough left for it to remain significantly more useful than XSLT 1.0.
Don't try to use Saxon on an XSLT 1.0 sheet; it does complain and support for EXSLT is not compiled in by default. Stick with Xalan for 1.0.
XSLT Documentation
See also
- xml2json-xslt, some really crafty XSLT work