Miscellaneous Techniques
1. Counting no of times if condition returns true in xsl:for-each loop
The following question was posted on the newsgroup, microsoft.public.xsl.
I'm using a xsl:if inside a for-each. I want to track the number of times the if test attribute returns true. How can this be done?
Solution to this problem is
Writing logic to count within for-each may not be necessary. You
can write the suitable XPath for select attribute of xsl:for-each (using
predicates).
For e.g. xsl:for-each select="node-set[][].." Each predicate will act like a if
condition.. And a suitable node-set will be available for processing.
I feel the above approach may solve the problem.
If you really need to count no of times if within a for-each returns true, here
is one approach:
<xsl:variable name="times">
<xsl:for-each select="node-set-expr">
<xsl:if test="some-condition">
1
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string-length($times) - string-length(translate($times,'1',''))"
/>
The approach used is:
Enclose for-each in a variable. If the condition within for-each is true, output
1 (or any other character).
Now count the no of 1s in the variable, which is the answer.
2. Searching for the longest path in a tree
The following question was asked on XSL-List.
Given this XML file:
<?xml version="1.0" encoding="UTF-8"?>
<corpus>
<phrase id="syntax_7"> <!-- Diskurs-Baum -->
<phrase id="syntax_5"> <!-- Satz 1 -->
<phrase id="syntax_1" cat="NP" func="PD" case="NOM">
<tok id="tok_1" lemma="dies" pos="PDS">Dies</tok>
<tok id="tok_2" lemma="sein" pos="VAFIN">ist</tok>
</phrase>
<phrase id="syntax_2" cat="NP" func="SB" case="NOM">
<tok id="tok_3" lemma="eine" pos="ART">ein</tok>
<tok id="tok_4" lemma="Beispielsatz" pos="NN">Beispielsatz</tok>
</phrase>
<tok id="tok_5" pos="$.">.</tok>
</phrase>
<phrase id="syntax_5"> <!-- Satz 2 -->
<tok id="tok_6" lemma="und" pos="KON">Und</tok>
<tok id="tok_7" lemma="hier" pos="ADV">hier</tok>
<tok id="tok_8" lemma="folgen" pos="VVFIN">folgt</tok>
<phrase id="syntax_3" cat="NP" func="IOBJ" case="DAT">
<tok id="tok_9" lemma="er" pos="PPER">ihm</tok>
</phrase>
<tok id="tok_10" lemma="zum Beispiel" pos="ADV">z.B.</tok>
<tok id="tok_11" lemma="noch" pos="ADV">noch</tok>
<phrase id="syntax_4" cat="NP" func="SB" case="NOM">
<tok id="tok_12" lemma="eine" pos="ART">ein</tok>
<tok id="tok_13" lemma="weiter" pos="ADJA">weiterer</tok>
<tok id="tok_14" lemma="Beispielsatz" pos="NN">Beispielsatz</tok>
</phrase>
<tok id="tok_15" pos="$.">.</tok>
</phrase>
</phrase>
</corpus>
I want to have the maximal depth of the tree. In the example XML it is 4.
The stylesheet for this problem is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:call-template name="max-depth">
<xsl:with-param name="node-set" select="//*" />
</xsl:call-template>
</xsl:template>
<xsl:template name="max-depth">
<xsl:param name="node-set" />
<xsl:for-each select="$node-set">
<xsl:sort select="count(ancestor::*)" order="descending"
data-type="number" />
<xsl:if test="position() = 1">
<xsl:value-of select="count(ancestor::*)" />
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
3. Testing two XML documents for equality
I thought.., can we write a XSLT 1.0 stylesheet (without using extension functions) to compare two XML documents?
I presented this technique at XSL-List:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text" />
<!-- parameter for "ignoring white-space only text nodes" during
comparison -->
<!-- if iws='y', "white-space only text nodes" will not be considered
during comparison -->
<xsl:param name="iws" />
<xsl:variable name="doc1" select="document('file1.xml')" />
<xsl:variable name="doc2" select="document('file2.xml')" />
<xsl:template match="/">
<!-- store hash of 1st document into a variable; it is
concatenation of name and values of all nodes -->
<xsl:variable name="one">
<xsl:for-each select="$doc1//@*">
<xsl:sort select="name()" />
<xsl:variable name="expr">
<xsl:call-template
name="constructXPathExpr">
<xsl:with-param name="node" select=".." />
<xsl:with-param name="xpath" select="name(..)" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($expr,'/@',name(),':',.)"
/>:<xsl:value-of select="count(../ancestor-or-self::node() | ../preceding::node())"
/>
</xsl:for-each>
<xsl:choose>
<xsl:when test="$iws='y'">
<xsl:for-each
select="$doc1//node()[not(normalize-space(self::text()) = '')]">
<xsl:variable
name="expr">
<xsl:call-template name="constructXPathExpr">
<xsl:with-param name="node" select="ancestor-or-self::*[1]" />
<xsl:with-param name="xpath" select="name(ancestor-or-self::*[1])" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of
select="concat($expr,'/',name(),':',.)" />:<xsl:value-of select="count(ancestor-or-self::node()
| preceding::node())" />
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each
select="$doc1//node()">
<xsl:variable
name="expr">
<xsl:call-template name="constructXPathExpr">
<xsl:with-param name="node"
select="ancestor-or-self::*[1]" />
<xsl:with-param name="xpath"
select="name(ancestor-or-self::*[1])" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of
select="concat($expr,'/',name(),':',.)" />:<xsl:value-of select="count(ancestor-or-self::node()
| preceding::node())" />
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- store hash of 2nd document into a variable; it is
concatenation of name and values of all nodes -->
<xsl:variable name="two">
<xsl:for-each select="$doc2//@*">
<xsl:sort select="name()" />
<xsl:variable name="expr">
<xsl:call-template
name="constructXPathExpr">
<xsl:with-param name="node" select=".." />
<xsl:with-param name="xpath" select="name(..)" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($expr,'/@',name(),':',.)"
/>:<xsl:value-of select="count(../ancestor-or-self::node() | ../preceding::node())"
/>
</xsl:for-each>
<xsl:choose>
<xsl:when test="$iws='y'">
<xsl:for-each
select="$doc2//node()[not(normalize-space(self::text()) = '')]">
<xsl:variable name="expr">
<xsl:call-template name="constructXPathExpr">
<xsl:with-param name="node"
select="ancestor-or-self::*[1]" />
<xsl:with-param name="xpath"
select="name(ancestor-or-self::*[1])" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of
select="concat($expr,'/',name(),':',.)" />:<xsl:value-of select="count(ancestor-or-self::node()
| preceding::node())" />
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each
select="$doc2//node()">
<xsl:variable name="expr">
<xsl:call-template name="constructXPathExpr">
<xsl:with-param name="node"
select="ancestor-or-self::*[1]" />
<xsl:with-param name="xpath"
select="name(ancestor-or-self::*[1])" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of
select="concat($expr,'/',name(),':',.)" />:<xsl:value-of select="count(ancestor-or-self::node()
| preceding::node())" />
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="$one = $two">
Equal
</xsl:when>
<xsl:otherwise>
Not equal
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to construct an XPath expression, for a given element node
-->
<xsl:template name="constructXPathExpr">
<xsl:param name="node" />
<xsl:param name="xpath" />
<xsl:choose>
<xsl:when test="$node/parent::*">
<xsl:call-template name="constructXPathExpr">
<xsl:with-param
name="node" select="$node/parent::*" />
<xsl:with-param
name="xpath" select="concat(name($node/parent::*),'/',$xpath)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('/',$xpath)"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
(Thanks to David Carlisle who suggested many improvements)
XSLT 2.0 has a function deep-equal, which can be used to perform the similar task (Thanks to Michael Kay for suggesting this).
Here is a XSLT 2.0 stylesheet for this problem:
<?xml version="1.0"
encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:variable name="doc1" select="document('file1.xml')" />
<xsl:variable name="doc2" select="document('file2.xml')" />
<xsl:template match="/">
<xsl:choose>
<xsl:when test="deep-equal($doc1,
$doc2)">
Equal
</xsl:when>
<xsl:otherwise>
Not equal
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
4. Work with different trees
The following question was asked on microsoft.public.xsl Newsgroup.
I have input xml like this:
<root>
<part1>
<a>
<b>AA</b><b>BB</b>
</a>
<a>
<b>EE</b><b>FF</b><b>GG</b>
</a>
</part1>
<part2>
<w>
<ss>a</ss><ss>b</ss><ss>c</ss><ss>d</ss>
</w>
<w>
<ss>e</ss><ss>f</ss><ss>g</ss>
</w>
</part2>
</root>
I need such output:
<part12>
<res>
<r>a AA</r>
<r>b BB</r>
...
<r>h HH</r>
<res>
<part12>
I need to concatenate values from different trees. i.e. how to get the value of corresponding element from another tree.
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/root">
<part12>
<res>
<!-- merging
part1/a[1]/b and part2/w[1]/ss -->
<xsl:choose>
<xsl:when test="count(part1/a[1]/b) > count(part2/w[1]/ss)">
<xsl:for-each select="part2/w[1]/ss">
<xsl:variable name="pos" select="position()" />
<r><xsl:value-of select="." /><xsl:text> </xsl:text><xsl:value-of
select="../../../part1/a[1]/b[$pos]" /></r>
</xsl:for-each>
<xsl:for-each select="part1/a[1]/b[position() > count(part2/w[1]/ss)]">
<r><xsl:value-of select="." /></r>
</xsl:for-each>
</xsl:when>
<xsl:when test="count(part1/a[1]/b) < count(part2/w[1]/ss)">
<xsl:for-each select="part1/a[1]/b">
<xsl:variable name="pos" select="position()" />
<r><xsl:value-of select="../../../part2/w[1]/ss[$pos]" /><xsl:text> </xsl:text><xsl:value-of
select="." /></r>
</xsl:for-each>
<xsl:for-each select="part2/w[1]/ss[position() >
count(../../../part1/a[1]/b)]">
<r><xsl:value-of select="." /></r>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="part1/a[1]/b">
<xsl:variable name="pos" select="position()" />
<r><xsl:value-of select="../../../part2/w[1]/ss[$pos]" /><xsl:text> </xsl:text><xsl:value-of
select="." /></r>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
<!-- merging
part1/a[2]/b and part2/w[2]/ss -->
<xsl:choose>
<xsl:when test="count(part1/a[2]/b) > count(part2/w[2]/ss)">
<xsl:for-each select="part2/w[2]/ss">
<xsl:variable name="pos" select="position()" />
<r><xsl:value-of select="." /><xsl:text> </xsl:text><xsl:value-of
select="../../../part1/a[2]/b[$pos]" /></r>
</xsl:for-each>
<xsl:for-each select="part1/a[2]/b[position() > count(part2/w[2]/ss)]">
<r><xsl:value-of select="." /></r>
</xsl:for-each>
</xsl:when>
<xsl:when test="count(part1/a[2]/b) < count(part2/w[2]/ss)">
<xsl:for-each select="part1/a[2]/b">
<xsl:variable name="pos" select="position()" />
<r><xsl:value-of select="../../../part2/w[2]/ss[$pos]" /><xsl:text> </xsl:text><xsl:value-of
select="." /></r>
</xsl:for-each>
<xsl:for-each select="part2/w[2]/ss[position() >
count(../../../part1/a[2]/b)]">
<r><xsl:value-of select="." /></r>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="part1/a[2]/b">
<xsl:variable name="pos" select="position()" />
<r><xsl:value-of select="../../../part2/w[2]/ss[$pos]" /><xsl:text> </xsl:text><xsl:value-of
select="." /></r>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</res>
</part12>
</xsl:template>
</xsl:stylesheet>
5.
Counting related nodes
The following question was asked on XSL-List.
Given the xml:
<vs>
<ve pos="1"></ve>
<ve pos="1.1"></ve>
<ve pos="1.1.1"></ve>
<ve pos="1.1.1.1"></ve>
<ve pos="1.1.1.2"></ve>
<ve pos="1.1.1.3"></ve>
<ve pos"1.2"></ve>
<ve pos="1.2.1"></ve>
<ve pos="1.2.1.1"></ve>
<ve pos="2"></ve>
<ve pos="2.1"></ve>
<ve pos="2.1.1"></ve>
<ve pos="2.1.1.1"></ve>
</vs>
And given that I am starting on a node with pos =
1 (or 2 or 3....), how do I count the nodes which have position with 3 dots only (e.g. pos = 1.1.2.1?) and start with the current node position.
The stylesheet for this problem is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:variable name="startpos" select="'1'" />
<xsl:template match="/vs">
<xsl:value-of select="count(ve[@pos=$startpos]/(self::ve |
following-sibling::ve)[(string-length(@pos) - string-length(translate(@pos,'.','')))
= 3])" />
</xsl:template>
</xsl:stylesheet>
6.
Filtering data sets
The following question was asked on XSL-List.
I am trying to filter out data sets that do not have any data in
the column numbers specified in "header" of the xml. The header specifies that
columns 1, 2, 3 and 7 are crucial in this particular instance and must not be
empty. The values differ from time to time (I cannot hardcode the numbers in the
xsl), but the element names in "header" (AAA, BBB etc.) are static for this type
of data).
I need to test if the column numbers specified in the header are non-empty for
each each row. I have tried to assign the values of the header data to variables
and do something like: for each row, if test not(column[$_aaa]='') create the
element. This, however, I cannot get to work.
The stylesheet for this problem is:
Here is a generic solution I wrote. There is a downside though; it relies on
node-set function..
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="/root">
<root>
<xsl:variable name="rtf">
<xsl:for-each select="row">
<xsl:variable
name="curr_row" select="."/>
<xsl:variable
name="x">
<xsl:for-each select="column">
<xsl:if test="position() = /root/header/*">
<xsl:if test="normalize-space(.) = ''">
1
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if
test="not(contains($x, '1'))">
<success>
<xsl:for-each select="/root/header/*">
<xsl:element name="{name()}">
<xsl:value-of
select="$curr_row/column[position() = current()]"/>
</xsl:element>
</xsl:for-each>
</success>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<flow>
<total_in><xsl:value-of select="count(row)" /></total_in>
<total_out><xsl:value-of select="count(row) -
count(msxsl:node-set($rtf)/*)" /></total_out>
</flow>
<xsl:copy-of select="msxsl:node-set($rtf)/*"/>
</root>
</xsl:template>
</xsl:stylesheet>
7. Modifying XML document using information from another XML document
The following question was asked on XSL-List.
I've an input doc containing a number of modify-attr elements:
<modify>
<modify-attr attr-name = "abc"/>
<modify-attr attr-name = "123"/>
<modify-attr attr-name = "789"/>
</modify>
I have another document containing a list of prohibited attributes:
<exclude-list>
<exclude-attr>123</exclude-attr>
</exclude-list>
I want to iterate over my input doc and copy all those modify-attr elements that
are NOT in the exclude list to the output.
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="exclude" select="document('exclude.xml')" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[@attr-name = $exclude/exclude-list/exclude-attr]" />
</xsl:stylesheet>
This is a modified identity stylesheet. The 2nd template excludes those
elements, whose attr-name attribute has value matching that in exclude file.
Jay Bryant provided the following answer:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="exclude" select="document('excludedoc.xml')"/>
<xsl:template match="modify">
<modify>
<xsl:apply-templates/>
</modify>
</xsl:template>
<xsl:template match="modify-attr">
<xsl:variable name="name" select="@attr-name"/>
<xsl:if test="not($exclude/exclude-list/exclude-attr[.=$name])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
David Carlisle provided this suggestion:
<xsl:template match="modify">
<xsl:copy-of select="modify-attr[not(@attr-name=document('excludedoc')/exclude-list/exclude-attr)]"/>
</xsl:template>
8. Stripping out non-numbers
The following question was asked on XSL-List.
I need to strip out non-numerical values from a string. Here is a sample input value: TUV0062. And what I want is : 0062 (or just 62)
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text" />
<xsl:variable name="includechars" select="'0123456789'" />
<xsl:template match="/">
<xsl:variable name="teststring" select="'TUV0062'" />
<xsl:value-of select="translate($teststring,
translate($teststring,$includechars,''), '')" />
</xsl:template>
</xsl:stylesheet>
G. Ken Holman provided this answer:
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="target" select="'TUV0062'"/>
<xsl:value-of select="number(
translate($target,
translate($target,'0123456789',''),''))"/>
</xsl:template>
</xsl:stylesheet>
Dimitre Novatchev said:
This is the "double-translate" technique first proposed on this list (to the
best of my knowledge) by Mike Kay.
The FXSL template/function
str-filter
has been shown to perform better than the double-translate technique on large
strings:
http://www.biglist.com/lists/xsl-list/archives/200203/msg01524.html
9. Finding duplicates on
multiple elements
The following question was asked on microsoft.public.xsl Newsgroup.
What I need is something that will use values from two elements
to determine if the values are duplicates. Consider following XML:
<DOCUMENTS>
<DOCUMENT>
<PATH>/abc/</PATH>
<FILENAME>xyz.htm</PATH>
</DOCUMENT>
<DOCUMENT>
<PATH>/abc/</PATH>
<FILENAME>abc.htm</PATH>
</DOCUMENT>
<DOCUMENT>
<PATH>/efg/</PATH>
<FILENAME>xyz.htm</FILENAME>
</DOCUMENT>
<DOCUMENT>
<PATH>/abc/</PATH>
<FILENAME>xyz.htm</FILENAME>
</DOCUMENT>
</DOCUMENTS>
Now I would like the output to be:
/abc/xyz.htm has a duplicate
/abc/abc.htm has no duplicate
/efg/xyz.htm has no duplicate
/abc/xyz.htm has no duplicate
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text" />
<xsl:key name="by-path-and-filename" match="DOCUMENT" use="concat(PATH,' ',
FILENAME)" />
<xsl:template match="/DOCUMENTS">
<xsl:for-each select="DOCUMENT">
<xsl:if test="count(key('by-path-and-filename', concat(PATH,' ',
FILENAME))) > 1">
<xsl:value-of select="concat(PATH, FILENAME)" /> has a
duplicate<xsl:text>
</xsl:text>
</xsl:if>
<xsl:if test="count(key('by-path-and-filename', concat(PATH,' ',
FILENAME))) = 1">
<xsl:value-of select="concat(PATH, FILENAME)" /> has no
duplicate<xsl:text>
</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
An interesting question was asked (by the person who posted this question).
Would it not be more effiecient to use a choose when otherwise in place of two
if's which call functions?
I reasoned..
I feel you are right. Using "choose when otherwise" will be more efficient
than two if's.. as it will avoid two calculations of count(key..
10. Strip non Alpha-numeric characters
The following question was asked on XSL-List.
I was wondering if there is any way possible of stripping any non-alphanumeric characters from an attribute. ie keep anything that is A-Z/0-9 and strip all other characters like ",*-+. etc etc?
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="allowedchars"
select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() |
@*" />
</xsl:copy>
</xsl:template>
<xsl:template match="@*[string-length(translate(., $allowedchars, '')) > 0]">
<xsl:attribute name="{name()}">
<xsl:value-of select="translate(.,
translate(., $allowedchars, ''), '')" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is a modified identity stylesheet.
For e.g., when the above XSLT is applied to XML:
<?xml version="1.0"?>
<root>
<a x="123ABC+-" u="MNO" />
<b y="ABC12" v="PQR+-" />
<c z="+-1" w="WX" />
</root>
The output produced is:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a x="123ABC" u="MNO"/>
<b y="ABC12" v="PQR"/>
<c z="1" w="WX"/>
</root>
11. XSL Sort and Copy
The following question was asked on microsoft.public.xsl Newsgroup.
I am trying to copy an xml document into another, sorted by the
attribute of one of the child nodes. The xml is:
<?xml version="1.0" encoding="UTF-8"?>
<WFC>
<Response Status="Success" Timeout="1800" PersonKey="13" Object="System"
Action="Logon" PersonNumber="ALL ACCOUNTS LI" Username="All Accounts">
</Response>
<Response Status="Success" Action="RunQuery">
<Result FullName="A" PersonNumber="2709"/>
<Result FullName="D" PersonNumber="4054"/>
<Result FullName="B" PersonNumber="4108"/>
</Response>
<Response Status="Success" Object="System" Action="Logoff">
</Response>
</WFC>
I want the exact structure to be copied to another xml doc, however sorted by
FullName attribute value of the Result Node.
The stylesheet for this problem is:
(by modified the identity stylesheet)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Response">
<Response>
<xsl:copy-of select="@*" />
<xsl:for-each select="Result">
<xsl:sort select="@FullName" />
<xsl:copy-of select="." />
</xsl:for-each>
</Response>
</xsl:template>
</xsl:stylesheet>
12.
Transforming Flat XML to Hierarchical XML
The following question was asked on XSL-List.
I have an XML which is flat:
<?xml version="1.0" encoding="utf-8" ?>
<ns>
<RECSETNAME>
<Record>
<keyfieldValue>HEADR</keyfieldValue>
<fieldValue>CDJOB</fieldValue>
<fieldValue>TRA</fieldValue>
</Record>
<Record>
<keyfieldValue>TRANS</keyfieldValue>
<fieldValue>DATA</fieldValue>
<fieldValue>EXCHG</fieldValue>
<fieldValue>EXCH</fieldValue>
</Record>
<Record>
<keyfieldValue>MTPNT</keyfieldValue>
<fieldValue>74842606</fieldValue>
</Record>
<Record>
<keyfieldValue>ADDRS</keyfieldValue>
<fieldValue>MTRPT</fieldValue>
<fieldValue>BRITISH TELECOM</fieldValue>
</Record>
<Record>
<keyfieldValue>ASSET</keyfieldValue>
<fieldValue>INSTL</fieldValue>
<fieldValue>METER</fieldValue>
</Record>
<Record>
<keyfieldValue>METER</keyfieldValue>
<fieldValue>T</fieldValue>
</Record>
</RECSETNAME>
</ns>
All the data is stored under the record structure. Based on the keyFieldValue I
have to generate the target node.
My Target XML should look like:
<HEADR>
<fieldValue>CDJOB</fieldValue>
<fieldValue>TRA</fieldValue>
<TRANS>
<fieldValue>DATA</fieldValue>
<fieldValue>EXCHG</fieldValue>
<fieldValue>EXCH</fieldValue>
<MTPNT>
<fieldValue>74842606</fieldValue>
<ADDRS>
<fieldValue>MTRPT</fieldValue>
<fieldValue>BRITISH
TELECOM</fieldValue>
</ADDRS>
<ASSET>
<fieldValue>INSTL</fieldValue>
<fieldValue>568</fieldValue>
<METER>
<fieldValue>T</fieldValue>
</METER>
</ASSET>
<MTPNT>
</TRANS>
</HEADR>
The stylesheet for this problem is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<xsl:call-template name="xyz">
<xsl:with-param name="x" select="//Record[1]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="xyz">
<xsl:param name="x" />
<xsl:if test="$x">
<xsl:element name="{$x/keyfieldValue}">
<xsl:for-each select="$x/fieldValue">
<fieldValue><xsl:value-of select="."
/></fieldValue>
</xsl:for-each>
<xsl:call-template name="xyz">
<xsl:with-param name="x"
select="$x/following-sibling::Record[1]" />
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
A changed requirement is.. If the XML file is:
<ns>
<RECSETNAME>
<Record>
<keyfieldValue>HEADR</keyfieldValue>
<fieldValue>CDJOB</fieldValue>
<fieldValue>TRA</fieldValue>
</Record>
<Record>
<keyfieldValue>TRANS</keyfieldValue>
<fieldValue>DATA</fieldValue>
<fieldValue>EXCHG</fieldValue>
<fieldValue>EXCH</fieldValue>
</Record>
<Record>
<keyfieldValue>MTPNT</keyfieldValue>
<fieldValue>74842606</fieldValue>
</Record>
<Record>
<keyfieldValue>ADDRS</keyfieldValue>
<fieldValue>MTRPT</fieldValue>
<fieldValue>BRITISH TELECOM</fieldValue>
</Record>
<Record>
<keyfieldValue>ASSET</keyfieldValue>
<fieldValue>INSTL</fieldValue>
<fieldValue>METER</fieldValue>
</Record>
<Record>
<keyfieldValue>METER</keyfieldValue>
<fieldValue>T</fieldValue>
</Record>
<Record>
<keyfieldValue>APPOINTMENT</keyfieldValue>
<fieldValue>T</fieldValue>
</Record>
<Record>
<keyfieldValue>NAME</keyfieldValue>
<fieldValue>TEST</fieldValue>
<fieldValue>BRITISH TELECOM</fieldValue>
</Record>
<Record>
<keyfieldValue>ADDRS</keyfieldValue>
<fieldValue>NAME</fieldValue>
<fieldValue>BRITISH TELECOM</fieldValue>
</Record>
</RECSETNAME>
</ns>
(now two additional elements are at the end)
The desired output is:
<?xml version="1.0" encoding="UTF-8"?>
<HEADR>
<fieldValue>CDJOB</fieldValue>
<fieldValue>TRA</fieldValue>
<TRANS>
<fieldValue>DATA</fieldValue>
<fieldValue>EXCHG</fieldValue>
<fieldValue>EXCH</fieldValue>
<MTPNT>
<fieldValue>74842606</fieldValue>
<ADDRS>
<fieldValue>MTRPT</fieldValue>
<fieldValue>BRITISH
TELECOM</fieldValue>
<ASSET>
<fieldValue>INSTL</fieldValue>
<fieldValue>METER</fieldValue>
<METER>
<fieldValue>T</fieldValue>
</METER>
</ASSET>
</ADDRS>
</MTPNT>
<APPOINTMENT>
<keyfieldValue>APPOINTMENT</keyfieldValue>
<fieldValue>T</fieldValue>
</APPOINTMENT>
<NAME>
<keyfieldValue>NAME</keyfieldValue>
<fieldValue>TEST</fieldValue>
<fieldValue>BRITISH TELECOM</fieldValue>
<ADDRESS>
<keyfieldValue>ADDRS</keyfieldValue>
<fieldValue>NAME</fieldValue>
<fieldValue>BRITISH TELECOM</fieldValue>
</ADDRESS>
</NAME>
</TRANS>
</HEADR>
The stylesheet for this problem is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<xsl:element name="{//Record[1]/keyfieldValue}">
<xsl:copy-of select="//Record[1]/*[not(self::keyfieldValue)]"
/>
<xsl:element name="{//Record[2]/keyfieldValue}">
<xsl:copy-of select="//Record[2]/*[not(self::keyfieldValue)]"
/>
<xsl:call-template name="xyz">
<xsl:with-param name="x"
select="//Record[3]" />
</xsl:call-template>
<APPOINTMENT>
<xsl:copy-of select="//Record[keyfieldValue
= 'APPOINTMENT']/*" />
</APPOINTMENT>
<xsl:for-each select="//Record[keyfieldValue =
'NAME']">
<NAME>
<xsl:copy-of select="*"
/>
<ADDRESS>
<xsl:copy-of
select="following-sibling::Record[1]/*" />
</ADDRESS>
</NAME>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template name="xyz">
<xsl:param name="x" />
<xsl:if test="$x and not($x/keyfieldValue = 'APPOINTMENT') and not($x/keyfieldValue
= 'NAME') and not(($x/keyfieldValue = 'ADDRS') and
($x/preceding-sibling::Record[1]/keyfieldValue = 'NAME'))">
<xsl:element name="{$x/keyfieldValue}">
<xsl:copy-of select="$x/fieldValue" />
<xsl:call-template name="xyz">
<xsl:with-param name="x"
select="$x/following-sibling::Record[1]" />
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I mentioned.. I have slightly changed the syntax (as compared to my previous
stylesheet).. I am now using <xsl:copy-of instead of xsl:for-each (for syntactic convenience)..
13. Excluding some descendant elements
The following question was asked on XSL-List.
My XML doc has this basic structure:
<a>
<b>
<c>
<!-- This is the section of interest -->
</c>
</b>
</a>
The <c> element can contain any combination of elements <d> through <z>. Elements <y> and <z> have special uses.
For instance, given this source:
<a><b><c>
<d>
<z>
<g/>
</d>
<q>
<r>
<y/>
<z/>
</r>
<y/>
<q>
<y>
</c></b></a>
I want the selection to contain this:
<a><b><c>
<d>
<g/>
</d>
<q>
<r>
<y/>
</r>
<q>
</c></b></a>
(<z> elements are gone, only the first <y> element remains)
I wish to handle <y> and <z> appearing at any depth in the descendant tree.
The stylesheet for this problem is:
(a variation of identity transform)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<!-- include only 1st y , and exclude all z -->
<xsl:template match="y[ancestor::y or preceding::y] | z" />
</xsl:stylesheet>
Michael Kay provided following
explanation:
I think you are a little confused between the terms
"node-set" and "result-tree-fragment". (Not surprising, most people are).
You select a node-set using
<xsl:variable name="n" select="--- some path expression ---"/>
The result is a set of nodes - I actually prefer to think of it as a set of
*references* to nodes. These are original nodes in the source document, and they
retain their original position in the source document, which means for example
that you can process each node in the set to ask how many ancestors it has.
You create a result tree fragment (or in 2.0 terminology a temporary tree) using
<xsl:variable name="n">
-- some instructions ---
</xsl:variable>
The result is a new document. (The term "fragment" comes from DOM, and means a
document that isn't constrained to have a single element at the top level.) The
nodes in this tree are newly constructed nodes; they may be
copies of nodes in the source tree (or not) but they have separate identity and
have lost their relationships to other nodes in the source tree.
The example that you've given suggests that you do actually want to create a new
tree that is a selective copy of the original tree. The way to do this is to
walk the original tree applying templates. If a node is to be
copied into the new tree, you apply the identity template, if it is to be
removed, you apply an empty template, and of course you can also have templates
that modify selected elements. So it looks something like this:
<xsl:variable name="subset">
<xsl:apply-templates select="a" mode="subset"/>
</xsl:variable>
<!-- by default, an element is copied -->
<xsl:template match="*" mode="subset">
<xsl:copy><xsl:copy-of select="@*"/><xsl:apply-templates
mode="subset"/></xsl:copy>
</xsl:template>
<!-- I only want to include [only] the first <y> element that is contained
within <c>, no matter where it occurs. There may be no <y> elements present.
-->
<xsl:template match="y[not(. is (ancestor::c//y)[1])]" mode="subset"/>
<!-- I want to exclude all <z> elements that are contained within <c>, no
matter where they occur. Again, there may be none present. -->
<xsl:template match="z" mode="subset"/>
I used the XPath 2.0 "is" operator for comparing node identity here. The 1.0
equivalent of "A is B" is generate-id(A)=generate-id(B).
14.
Using position() function rightly
The following question was asked on XSL-List.
With this xml structure in mind:
<dfile>
<df_column_names>
<df_column name="employee_name" type="hyperlink">Name:</df_column>
<df_column name="department" type="text">Department:</df_column>
<df_column name="position" type="text">Position:</df_column>
</df_column_names>
<df_data_row>
<data_key>1695</data_key>
<df_data>Charles Hilditch</df_data>
<df_data>Processing</df_data>
<df_data>Mill Supervisor</df_data>
</df_data_row>
</dfile>
I am trying to select the type attribute for a particular df_data
element.
<xsl:for-each select="./df_data">
<xsl:value-of select="/dfile/df_column_names/df_column[position()]/@type"/>
</xsl:for-each>
My algorithm is simply selecting the type attribute from df_column whose
position is equal to current df_data element being processed.
I keep getting the type attribute for the first db_column name only.
Michael Kay provided following explanation:
When the value of a predicate [X] is an integer, it's a
shorthand for [position()=X]. So E[position()] means E[position()=position()]
which is the same as E[true()], or simply E. Your mistake is in forgetting that
the
context changes inside the square brackets. So you need to assign a variable to
the value of position() outside the predicate, and then use the variable inside
the predicate:
<xsl:variable name="pos" select="position">
<xsl:value-of select="x/y/z[$pos]"/>
15.
Encoding Country Codes
The following question was asked on XSL-List.
I require XSLT support in amending the country codes to my
resulting XML. I have all country names with relevant country codes in an
external XML file (i.e., CountryCode.xml, given below), here I require all
countries needs to be encoded in my each <affiliation> of the source xml, also
please find below the resulting xml for your easy understanding.
CountryCode.xml
<allcountry>
<country code="gb">England</country>
<country code="in">India</country>
<country code="us">USA</country>
<allcountry>
source.xml
<root>
<para>Some text</para>
<affiliation>Indian Institute of Technology, Chennai, India -
610545</affiliation>
<affiliation>SOME INSTITUTION NAME, ENGLAND - 12245</affiliation>
<affiliation>IEEE Institution, NY, USA</affiliation>
</root>
result.xml
<root>
<affiliation>Indian Institute of Technology, Chennai, <country
code="in">India</country> - 610545</affiliation>
<affiliation>SOME INSTITUTION NAME, <country code="gb">ENGLAND</country>
- 12245</affiliation>
<affiliation>IEEE Institution, NY, <country
code="us">USA</country></affiliation>
</root>
Please advise
The stylesheet for this problem is:
(modified identity stylesheet)
In this stylesheet, I have made following assumptions:
1) The CountryCode.xml file you posted specifies England. So I think source.xml
should also specify England (not ENGLAND) . I tested by changing to England..
2) My stylesheet is replacing a "string pattern" (its first occurence) in
<affiliation> tag with value from CountryCode.xml file..
This is having following effect:
<affiliation>
<country code="in">India</country>n Institute of Technology, Chennai,
India - 610545
</affiliation>
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="country" select="document('CountryCode.xml')" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="para" />
<xsl:template match="text()[not(normalize-space() = '')]">
<xsl:variable name="temp" select="." />
<xsl:for-each select="$country/allcountry/country[contains($temp,
.)]">
<xsl:value-of select="substring-before($temp, .)" />
<country code="{@code}">
<xsl:value-of select="." />
</country>
<xsl:value-of select="substring-after($temp, .)" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
16. Transforming XML into another XML format
The following question was asked on XSL-List.
I have following source XML file:
<root>
<p>some text</p>
<p>some text <i>italic text</i></p>
<p>some text <b><i>bold+italic text</i> bold text</b></p>
<p>some text <i>italic text <sub>subscript+italic text</sub></i> some
text</p>
<p>some text <b>bold text <i>Italic text</i> bold continues <i>x<inf>2</inf></i>
bold continues</b></p>
</root>
I need to transform this to this XML format:
<root>
<para>some text</para>
<para>some text <render font-style="italic">italic text</render></para>
<para>some text <render font-weight="bold" font-style="italic">bold+italic
text</render><render font weight="bold"> bold text</render></para>
<para>some text <render font-style="italic">italic text </render><render
font-style="italic" baseline-shift="sub" font-size="smaller">subscript+italic
text</render> some text</para>
<para>some text <render font-weight="bold">bold text </render><render
font-weight="bold" font style="italic">bold+Italic text</render><render
font-weight="bold"> bold continues </render><render font-weight="bold"
font-style="italic">x</render><render font-weight="bold" baseline-shift="sub"
font-size="smaller">2</render><render font-weight="bold"> bold
continues</render></para>
</root>
Michael Kay suggested this approach:
Interesting one. I think I would tackle this in the template
that matches the text nodes, something like this:
<xsl:template match="para//i/text() | para//b/text() | para//inf/text()">
<render>
<xsl:if test="ancestor::i">
<xsl:attribute
name="font-style">italic</xsl:attribute>
</xsl:if>
<xsl:if test="ancestor::b">
<xsl:attribute
name="font-weight">bold</xsl:attribute>
</xsl:if>
<xsl:if test="ancestor::inf">
<xsl:attribute
name="baseline-shift">sub</xsl:attribute>
<xsl:attribute
name="font-size">smaller</xsl:attribute>
</xsl:if>
<xsl:value-of select="."/>
</render>
</xsl:template>
(I think it should be <xsl:template match="p//i/text() | p//b/text() | p//inf/text()">.. It seems, Michael mistakingly wrote it.)
I suggested:
You may also try this.. This is a modified identity
stylesheet.. I might have missed some template rules
(I think I have).. But you may take the idea..
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<para>
<xsl:apply-templates />
</para>
</xsl:template>
<xsl:template match="i">
<render font-style="italic">
<xsl:apply-templates />
</render>
</xsl:template>
<xsl:template match="b/i">
<render font-weight="bold" font-style="italic">
<xsl:apply-templates />
</render>
</xsl:template>
<xsl:template match="i/sub">
<render font-style="italic" baseline-shift="sub"
font-size="smaller">
<xsl:apply-templates />
</render>
</xsl:template>
<xsl:template match="b/i/inf">
<render font-weight="bold" baseline-shift="sub"
font-size="smaller">
<xsl:apply-templates />
</render>
</xsl:template>
<xsl:template match="b">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="b/text()">
<render font-weight="bold">
<xsl:value-of select="." />
</render>
</xsl:template>
</xsl:stylesheet>
17. Validate generated XML file to a new XML schema in XSL
The following question was asked on XSL-List.
Given this XML document:
<?xml version="1.0"?>
<Unit xmlns="http://www.xml.com/xml/unit" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.xml.com/xml/unit/unit.xsd">
<Units>
<Unit_Code>5555</Unit_Code>
<Unit_Name>System</Unit_Name>
</Units>
</Unit>
I wish to display XML that will validate to a new XML schema file.
<?xml version="1.0"?>
<Unit xmlns="http://www.xml.com/xml/unit" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.xml.com/xml/unit/newUnit.xsd">
<Units>
<Unit_Code>5555</Unit_Code>
<Unit_Name>System</Unit_Name>
</Units>
</Unit>
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="@*[name() = 'xsi:schemaLocation']">
<xsl:attribute name="xsi:schemaLocation">http://www.xml.com/xml/unit/newUnit.xsd</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is tested with Saxon 6.5.3
Michael Kay provided following
explanation:
XSLT 2.0 allows you to specify the validation of the
generated XML file to be done as an intrinsic part of the transformation. The
advantage of this is that the validation is done earlier (and it's done
automatically).
Often it's possible to detect errors at the time the stylesheet is compiled,
before you even run it against a source document; and where the errors are
detected at run-time, you can get error messages that point to the line of code
in the stylesheet that generated the bad output, rather than having to study the
output of the schema processor which only tells you where the output itself is
wrong. Try it out with Saxon-SA.
However, going back to your approach: Looking at your input and output, what you
seem to be doing is a near-identity transform that changes the value of one
attribute. There's a standard pattern for that:
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@xsi:schemaLocation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:attribute name="xsi:schemaLocation">
<xsl:text>http://www.xml.com/xml/unit/newUnit.xsd</xsl:text>
</xsl:attribute>
</xsl:template>
Again, however, I'd question the design. Most schema processors allow you to say
"validate document X against schema Y" - the location of the schema doesn't have
to be hard-coded in the xsi:schemaLocation attribute.
Modifying the document to change the xsi:schemaLocation attribute should
therefore be quite unnecessary.
18. Handling mixed content
elements (transforming XML to another format)
The following question was asked on XSL-List.
In the XML snippet below...
<paragraph>
<bold>Actel</bold>
(Sunnyvale, CA) will showcase its third-generation flash-based FPGA
device, ProASIC3 (see <italic>page 105</italic>)—said to be the industry's
lowest-cost FPGA, starting at $1.50. The company's Libero integrated
design environment and broad IP offerings will also be on exhibit. Free
workshops and demonstrations will be offered throughout the show. (Booth #920,
http://info.edu/47)
</paragraph>
...I applied the following template...
<xsl:template match="paragraph">
<p>
<xsl:if test="bold">
<span style="font-weight: bold"><xsl:value-of
select="bold" /></span>
</xsl:if>
<xsl:if test="italic">
<span style="font-style: italic"><xsl:value-of
select="italic" /></span>
</xsl:if>
<xsl:if test="text()">
<xsl:value-of select="text()" />
</xsl:if>
</p>
</xsl:template>
...for which I received the following result...
<p>
<span style="font-weight: bold">Actel </span><span style="font-style:italic">page
105</span>(Sunnyvale, CA) will showcase its third-generation flash-based FPGA
device, ProASIC3 (see
</p>
In the source XML document, each paragraph may contain any number of <bold> and
<italic> tags in any order. How can I modify the "paragraph" template to account
for this?
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<html>
<head>
<title/>
</head>
<body>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="paragraph">
<p>
<xsl:apply-templates />
</p>
</xsl:template>
<xsl:template match="bold">
<span style="font-weight: bold">
<xsl:apply-templates />
</span>
</xsl:template>
</xsl:stylesheet>
The person who asked this question, had this query..
> If I understand you correctly, you've utilized an identity
template. Very
> cool. Unfortunately, and I did not specify this in my original post, the
> structure of the XML document is about half narrative-like and half
> data-like. The identity template would be too broad to address it.
Michael Kay replied:
Then you can either use a special mode for the identity
template, or you can constrain it to apply only within a paragraph by specifying
match="paragraph//*"
19.
Extracting information from different places in XML (using predicates, axes and
other constructs effectively)
The following question was asked on XSL-List.
I'm having trouble with a predicate selection (i.e. a
conditional selection). For the following xml structure.
<company>
<division DID="XXX">
<projects>
<project PID='XYZ'>
(Project ID)
<PNAME>Some
Name</PNAME> <!-- (Project Name) -->
<BUDGET>111</BUDGET>
<assigns>
<assign refEID='XXX000'>
<HOURS>12</HOURS>
</assign>
<assign refEID='XXX001'>
<HOURS>78</HOURS>
</assign>
</assigns>
</project>
<project PID='XY1'>
<PNAME>Some
OtherName</PNAME>
<BUDGET>112</BUDGET>
<assigns>
<assign refEID='XXX000'>
<HOURS>34</HOURS>
</assign>
<assign refEID='XXX002'>
<HOURS>1234</HOURS>
</assign>
</assigns>
</project>
Etc..
</projects>
<employees>
<employee EID='XXX000'>
<ENAME>Joe
Blow</ENAME>
<OFFICE>1204</OFFICE>
<BIRTHDATE>1924-08-01</BIRTHDATE>
</employee>
<employee EID='XXX001'>
<ENAME>Joe
Smith</ENAME>
<OFFICE>1203</OFFICE>
<BIRTHDATE>1930-08-01</BIRTHDATE>
</employee>
<employee EID='XXX002'>
<ENAME>Joe
Jerry</ENAME>
<OFFICE>0003</OFFICE>
<BIRTHDATE>1930-08-01</BIRTHDATE>
</employee>
</employees>
</division>
Etc.. (there are multiple divisions with the same structure, employees
can
only work for one division and a project only belongs to a single
division)
</company>
I want to have a table that shows PID, PNAME, BUDGET and then the employee EID
(unique identifier), the employee name (ENAME), OFFICE and HOURS spent on each
project.
The output I want is:
Division Name = XXX
PID='XYZ' PNAME=Some Name BUDGET=111
EID= XXX000 ENAME= Joe Blow OFFICE=1204 HOURS=12
EID= XXX001 ENAME= Joe Smith OFFICE=1203 HOURS=78
PID=' XY1' PNAME= Some OtherName BUDGET=112
EID= XXX000 ENAME= Joe Blow OFFICE=1204 HOURS=34
EID= XXX002 ENAME= Joe Jerry OFFICE=0003 HOURS=1234
This above output is to be produced for every division.
Aron Bock suggested this solution:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:key match="employee" name="employee-by-refEID" use="@EID"/>
<xsl:template match="/">
<xsl:apply-templates
select="company/division"/>
</xsl:template>
<xsl:template match="division">
<xsl:text>Division Name = </xsl:text><xsl:value-of
select="@DID"/><xsl:text>
</xsl:text>
<xsl:apply-templates
select="projects/project"/>
</xsl:template>
<xsl:template match="project">
<xsl:text>PID = </xsl:text><xsl:value-of
select="@PID"/>
<xsl:text>, PNAME = </xsl:text><xsl:value-of
select="PNAME"/>
<xsl:text>, BUDGET = </xsl:text><xsl:value-of
select="BUDGET"/>
<xsl:text>
</xsl:text>
<!-- list employees using key() -->
<xsl:apply-templates
select="assigns/assign" mode="use-key"/>
<xsl:text>
</xsl:text>
<!-- list employees using relative
path -->
<xsl:apply-templates
select="assigns/assign" mode="relative-path"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="assign" mode="use-key">
<xsl:text>EID = </xsl:text><xsl:value-of
select="@refEID"/>
<xsl:text>, ENAME = </xsl:text><xsl:value-of
select="key('employee-by-refEID', @refEID)/ENAME"/>
<xsl:text>, OFFICE = </xsl:text><xsl:value-of
select="key('employee-by-refEID', @refEID)/OFFICE"/>
<xsl:text>, HOURS = </xsl:text><xsl:value-of
select="HOURS"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="assign" mode="relative-path">
<xsl:variable name="refeid" select="@refEID"/>
<xsl:variable name="emp"
select="/company/division/employees/employee[@EID = current()/@refEID]"/>
<xsl:text>EID = </xsl:text><xsl:value-of
select="@refEID"/>
<xsl:text>, ENAME = </xsl:text><xsl:value-of
select="/company/division/employees/employee[@EID = current()/@refEID]/ENAME"/>
<xsl:text>, OFFICE = </xsl:text><xsl:value-of
select="../../../../employees/employee[@EID = @refeid]/OFFICE"/>
<xsl:text>, BIRTHDATE = </xsl:text><xsl:value-of
select="$emp/BIRTHDATE"/>
<xsl:text>, HOURS = </xsl:text><xsl:value-of
select="HOURS"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Aron explained:
I assume your real issue was how, once you were at a node, to get related
information from another node. Thus the stylesheet above shows how to do
it using keys, using an absolute path and matching on a common attribute,
and using a relative path and matching on a common attribute. Along the
way it also shows the use of modes, the current() function, and avoiding the
current() function by using a previously-initialized variable.
keys() are useful when you need to locate something repeatedly and fast; I
believe internally this is represented as a hash structure, so what you gain in
speed you may lose in memory. The usual space/time tradeoff.
When you need to access something repeatedly, but only in one place, it's useful
to create a local variable, as we do with $emp. That gives you to the
advantage of doing the full lookup just once and subsequent lookups
locally, without the reference staying around until transform-end (as would be
the case with keys).
I suggested this stylesheet:
(I am assuming, you wish to generate HTML)
<?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" />
<xsl:template match="/company">
<html>
<head>
<title/>
</head>
<body>
<xsl:apply-templates select="division" />
</body>
</html>
</xsl:template>
<xsl:template match="division">
<table border="1">
<th>
<td>
Division Name = <xsl:value-of
select="@DID" />
</td>
</th>
<xsl:for-each select="projects/project">
<tr>
<td>PID=<xsl:value-of select="@PID"
/></td>
<td>PNAME=<xsl:value-of select="PNAME"
/></td>
<td>BUDGET=<xsl:value-of
select="BUDGET" /></td>
</tr>
<xsl:for-each select="assigns/assign">
<tr>
<td>EID=<xsl:value-of
select="@refEID" /></td>
<td>ENAME=<xsl:value-of
select="ancestor::division[1]/employees/employee[@EID = current()/@refEID]/ENAME"
/></td>
<td>OFFICE=<xsl:value-of
select="ancestor::division[1]/employees/employee[@EID
= current()/@refEID]/OFFICE" /></td>
<td>HOURS=<xsl:value-of select="HOURS" /></td>
</tr>
</xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
My solution differs from Aron's in writing style. Aron has used xsl:apply-templates
(which is push processing style).
While I have used xsl:for-each (which is pull processing style)..
We may even mix these two styles of programming depending on requirement..
20.
Transforming XML
The following question was asked on
XSL-List.
Given this XML file:
<page>
<head>
<title>Example</title>
</head>
<content>
<h1>Example</h1>
<table>
<tr>
<td></td>
<td>Something #1</td>
</tr>
<tr>
<td>Somthing #2</td>
<td/>
</tr>
</table>
</content>
</page>
It needs to be transformed to:
<html>
<head>
<title>Example</title>
</head>
<body>
<div id="content">
<h1>Example</h1>
<table>
<tr>
<td>________NOTHING!_______</td>
<td>Something #1</td>
</tr>
<tr>
<td>Something #2</td>
<td>________NOTHING!_______</td>
</tr>
</table>
</div>
</body>
</html>
The stylesheet for this problem is:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="page">
<html>
<xsl:apply-templates />
</html>
</xsl:template>
<xsl:template match="content">
<body>
<div id="content">
<xsl:apply-templates />
</div>
</body>
</xsl:template>
<xsl:template match="td[normalize-space() = '']">
<td>__NOTHING__</td>
</xsl:template>
</xsl:stylesheet>
21.
Order of xsl:template
matching
The following question was asked on
XSL-List.
Given this XML chunk:
<TEXT>sldj aldj aldj a</TEXT>
<REPORT>12345</REPORT>
How can I apply templates and have the REPORT appear before the TEXT in the
results of the transformation?
I suggested..
For e.g. if the XML is:
<test>
<TEXT>sldj aldj aldj a</TEXT>
<REPORT>12345</REPORT>
</test>
You may do:
<xsl:template match="test">
<xsl:apply-templates select="REPORT" />
<xsl:apply-templates select="TEXT" />
</xsl:template>
<xsl:template match="TEXT">
<!-- anything you wish to write -->
</xsl:template>
<xsl:template match="REPORT">
<!-- anything you wish to write -->
</xsl:template>
David Carlisle explained:
If in the template for the parent you go
<xsl:apply-templates/>
Then nodes will be applied in document order. There are various ways to change
that depending what you want to happen in general, eg
<xsl:apply-templates select="REPORT"/>
<xsl:apply-templates select="TEXT"/>
would process REPORT then TEXT and ignore everything else
<xsl:apply-templates select="REPORT"/>
<xsl:apply-templates select="*[not(self::REPORT)]"/>
would process REPORT elements first then any other elements
<xsl:apply-templates>
<xsl:sort select="name()"/>
</xsl:apply-templates>
would process elements in alphabetic order.
Any of these has the requested effect on your small example of processing REPORT
then TEXT.
22.
Method for displaying time difference
The following question was asked on
XSL-List.
I have two elements:
<due date>2005-04-05</due date>
<actual arrival>2005-04-11T22:21:30</actual arrival>
what is the function to display the day-time difference?
Jeni Tennison provided following reply:
Assuming you're using XSLT 2.0 and that the elements are
actually called <due-date> and <actual-arrival>, you can use the minus operator
as follows:
xs:dateTime(actual-arrival) - xs:dateTime(xs:date(due-date))
to get the xdt:dayTimeDuration P6DT22H21M30S (6 days, 22 hours, 21 minutes, 30
seconds). You can then use the days-from-duration(), hours-from-duration() etc.
functions to extract the values of the individual components from that duration
in order to make something readable.
Note that the xs:date() constructor constructs an xs:date from the due date, and
the xs:dateTime() constructor casts this to a xs:dateTime by adding 00:00:00 as
the time.
Jeni Tennison is an invited expert to
XSLT 2.0 WG.
23. Transforming a Namespace Declaration
The following question was asked on microsoft.public.xsl Newsgroup.
Given an XML document:
<rootNode>
<ns1:SomeNode xmlns:ns1="http://www.mydomain.com/SomeNameSpace-A"
/>
</rootNode>
What is the best way to use XSL to make a document that looks like:
<rootNode>
<ns1:SomeNode xmlns:ns1="http://www.mydomain.com/SomeNameSpace-B"
/>
</rootNode>
I suggested this idea:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://whatever"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="node()[local-name() = 'SomeNode']">
<xsl:copy />
</xsl:template>
</xsl:stylesheet>
Oleg Tkachenko provided following XSLT stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0" xmlns:ns1="http://www.mydomain.com/SomeNameSpace-A">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns1:*">
<xsl:element name="{name()}" namespace="http://www.mydomain.com/SomeNameSpace-B">
<xsl:apply-templates select="@*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
24. Getting data between from-date and to-date
The following question was asked on
XSL-List.
I have an xml file. It contain number of employee records with basic
information like name, age, date of joining etc.
I wish to get particular employees covered between two different dates for
example from-date and to-date .
The XML file is:
<Employees>
<Employee Empname="Raj" DOJ ="02/04/2005" SftTime="0030" />
<Employee Empname="Rajkumar" DOJ ="02/04/2005" SftTime="0030" />
<Employee Empname="Raja" DOJ ="03/04/2005" SftTime="0000" />
<Employee Empname="Ravi" DOJ ="04/04/2005" SftTime="2330" />
<Employee Empname="john" DOJ ="05/04/2005" SftTime="1600" />
<Employee Empname="gopi" DOJ ="06/04/2005" SftTime="0100" />
<Employee Empname="ajith" DOJ ="13/04/2005" SftTime="2200" />
</Employees>
Stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="date1" select="'04-03-2005'" />
<xsl:variable name="date2" select="'04-05-2005'" />
<xsl:template match="/Employees">
<result>
<xsl:for-each
select="Employee">
<xsl:variable
name="doj" select="concat(substring(@DOJ,4,2),'-',substring(@DOJ,1,2),'-',substring(@DOJ,7,4))"
/>
<xsl:if test="($doj >= $date1) and ($doj <= $date2)">
<xsl:copy-of select="." />
</xsl:if>
</xsl:for-each>
</result>
</xsl:template>
</xsl:stylesheet>
(The stylesheet is tested with Saxon 8.4)
Michael Kay provided following explanation:
[1] If the source documents have a schema, and the
attributes are defined in the schema as being of type xs:date, and you are using
a schema-aware XSLT 2.0 processor, then you can just write
employee[@FromDate le $d and @ToDate ge $d]
If you are not using a schema, but you are using XSLT 2.0, and your dates are
stored in the ISO 8601 format that XML Schema uses, then you can still do the
same kind of comparison but you need to do a little more work:
employee[xs:date(@FromDate) le $d and xs:date(@ToDate) ge $d]
If you are not using a schema and your dates are in some other format, or if you
are using XSLT 1.0, then you will need to do even more work, but this then
depends entirely on the format you are starting with.
[2]
> <Employees>
> <Employee Empname="Raj" Empname="Raj" DOJ ="02/04/2005"
> SftTime="0030"
> >
> <Employee Empname="Rajkumar" DOJ ="02/04/2005" SftTime="0030"
>
> <Employee Empname="Raja" DOJ ="03/04/2005" SftTime="0000"
>
> <Employee Empname="Ravi" DOJ ="04/04/2005" SftTime="2330"
>
> <Employee Empname="john" DOJ ="05/04/2005" SftTime="1600"
>
> <Employee Empname="gopi" DOJ ="06/04/2005" SftTime="0100"
>
> <Employee Empname="ajith" DOJ ="13/04/2005" SftTime="2200"
>
> </Employees>
>
> I want to filter only employees in DOJ between 05/04/2005 and 13/04/2005 these
date.
It looks as if your dates are in the format dd/mm/yyyy, which although more
logical than the form mm/dd/yyyy, still does not sort naturally.
With XSLT 1.0, the technique is to convert the dates to numbers like this:
number(concat(substring(x, 7, 4), substring(x, 4, 2), substring(x, 1, 2)))
and then compare the numbers.
(This is assuming the month and day are always two digits: I shouldn't really
assume that simply because it's true for the six records shown above).
Rudolf P. Weinmann provided following answer:
As Mukul said, his stylesheet is based on XSLT 2.0 language.
Under XSLT 1.0 the result would be empty, because neither $doj nor $date1 and
$date2 can be converted to numbers
for the comparison in the xsl:if statement.
XSLT 1.0 can do the job as well, see example below.
Using param as a toplevel element, you can pass fromDate and toDate from the
command line or over an API. I prefer passing them as numbers (see Michael's
reasoning) in YYYYMMDD format. When comparing the values with >=, they get
implicitly converted to numbers. I prefer the explicit conversion using the
number() function.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:param name="fromDate" select="'20050403'"/>
<xsl:param name="toDate" select="'20050405'"/>
<xsl:template match="Employee">
<xsl:variable name="doj" select="number(translate('efghcdab','ab/cd/efgh',@DOJ))"/>
<xsl:if test="$doj >= number($fromDate) and $doj <= number($toDate)">
<xsl:call-template name="copy"/>
</xsl:if>
</xsl:template>
<xsl:template match="/|*" name="copy">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
25.
Getting sibling text
The following question was asked on XSL-List.
I need to isolate text 'Root level text'
from xml source:
<p>
<span>whatever</span>
Root level text
</p>
Stylesheet snippet for this problem is:
<xsl:template match="p">
<xsl:apply-templates select="span" />
</xsl:template>
<xsl:template match="span">
<xsl:value-of select="normalize-space(following-sibling::text()[1])" />
</xsl:template>
26.
How to Sort?
The following question was asked on XSL-List.
Given this XML document:
<BigList>
<BigElement>
<Code>XXP</Code>
<SmallList>
<SmallElement>
<Code>001</Code1>
<Name>A</Name>
</SmallElement>
<SmallElement>
<Code>002</Code1>
<Name>X</Name>
</SmallElement>
</SmallList>
</BigElement>
<BigElement>
<Code>YYJ</Code>
<SmallList>
<SmallElement>
<Code>01</Code1>
<Name>B</Name>
</SmallElement>
<SmallElement>
<Code>02</Code1>
<Name>Z</Name>
</SmallElement>
</SmallList>
</Element>
</BigList>
The desired output after sorting is:
<select id="SmallElements">
<option value="001">A</option>
<option value="01">B</option>
<option value="002">X</option>
<option value="02">Z</option>
</select>
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/BigList">
<select id="SmallElements">
<xsl:for-each select=".//Name">
<xsl:sort select="." />
<option value="{preceding-sibling::Code[1]}"><xsl:value-of
select="." /></option>
</xsl:for-each>
</select>
</xsl:template>
</xsl:stylesheet>
Aron Bock suggested:
use xsl:sort
<?xml version="1.0" encoding="iso8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<select id="SmallElements">
<xsl:apply-templates select="BigList/BigElement/SmallList/SmallElement">
<xsl:sort select="Name"/>
</xsl:apply-templates>
</select>
</xsl:template>
<xsl:template match="SmallElement">
<option value="{Code}">
<xsl:value-of select="Name"/>
</option>
</xsl:template>
</xsl:stylesheet>
27.
Identity Transformer based file size reducer
The following question was asked on XSL-List.
I need a simple transformation to reduce the size of a file... so like I
just need to see the first 10 elements of an xml source which is 10 megs. Those first 10 elements would be the first 10 child elements to
the source including their child elements.
Dimitre Novatchev provided following
answer:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*[position() > 10]"/>
<xsl:template match="node()[ancestor::*[3]]"/>
</xsl:stylesheet>
28.
Sum functionality
Michael Kay told on XSL-List
How to sum over values that are computed from those held in the nodes,
rather than the actual string-values of the nodes. The solutions on offer
include:
(a) create a temporary tree containing nodes holding the values directly, then
sum over the nodes in that temporary tree
(b) a recursive named template
(c) the f:map function in FXSL
(d) the saxon:sum() extension function in Saxon 6.5.3
(e) In XSLT 2.0, sum(for $x in $nodes return number(tokenize($x, ' ')))
(f) In Schema-aware XSLT 2.0, if the type of your gml:Pos nodes is list of
numbers, then the sum() function will do the job directly.
Dimitre Novatchev explained:
> What does map function do?
Formal definition in Haskell prelude (Prelude.hs):
map
:: (a -> b) -> [a] -> [b]
map f xs
= [ f x | x <- xs ]
The first line above defines the type (signature) of the "map" function. It
takes two arguments:
- a function of type a -> b (with domain "a" and codomain "b"
--
this means that the type of thie arguments is "a"
and the type of the results is "b")
Because "map" has an argument, which is a
function, "map" is a higher-order
function.
- a list of elements each of type "a"
The type of the result is a list of elements of type "b".
The second line of the definition above defines exactly the map function. We see
that the result of applying a function "map" to its two arguments -- a function
"f" and a list "xs" -- is the set of all "f x" (which means f(x))
where "x" belongs to the list "xs"
More informally, we have a list of elements of the same type ("a") and a
function "f", defined on "a" and producing results of type "b".
The result of applying "map" on "f" and a list "xs" (all of whose elements are
of type "a") is another list "ys" , whose elements "y" are the results of
applying "f" on the corresponding elements "x" of "xs".
Example:
map (2 * ) [1,2,3,4] = [2,4,6,8]
where (2 *) is a function, which produces twice its argument.
map string-length ['one', "two", "three", "four"] = [3, 3, 5,
4]
then, we'll have:
sum (map string-length ['one', "two", "three", "four"] ) = 15
> Please explain what does
> sum(f:map(f:string-length(), /*/node())) mean ..
Almost the same as the last line above
29. EXSLT for MSXML4
There is an implementation of EXSLT for MSXML4, which consists of the
common:node-set() function and all functions from the "set" module:
common:node-set()
set:intersection()
set:difference()
set:distinct()
set:leading()
set:trailing()
set:has-same-node()
For more information please see:
http://www.xml.com/pub/a/2003/08/06/exslt.html
30. XML file (Recursive Transformation)
The following question was asked on XSL-List
It is desired to transform this XML:
<menu name="link1"/>
<menu name="link2">
<menu name="link2a"/>
<menu name="link2b"/>
</menu>
to this one:
<ul>
<li>link1</li>
<li>link2
<ul>
<li>link2a</li>
<li>link2b</li>
</ul>
</li>
</ul>
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<ul>
<xsl:apply-templates />
</ul>
</xsl:template>
<xsl:template match="menu">
<xsl:choose>
<xsl:when test="child::*">
<li><xsl:value-of select="@name" /></li>
<ul>
<xsl:apply-templates />
</ul>
</li>
</xsl:when>
<xsl:otherwise>
<li>
<xsl:value-of select="@name" />
</li>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The above stylesheet is applied to XML:
<?xml version="1.0"?>
<root>
<menu name="link1"/>
<menu name="link2">
<menu name="link2a"/>
<menu name="link2b"/>
</menu>
</root>
Aron Bock provided this answer:
Luckily, there's an easier and more "natural" way to do this -- don't write
"tags", as you do, but create full-fledged elements. Using this, with
recursion, yields a simple approach.
With this input:
<data>
<menu name="link1"/>
<menu name="link2">
<menu name="link2a"/>
<menu name="link2b"/>
</menu>
</data>
This transform, using call-template:
<?xml version="1.0" encoding="iso8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/data">
<xsl:call-template name="write-menu">
<xsl:with-param
name="items" select="menu"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="write-menu">
<xsl:param name="items"
select="/.."/>
<ul>
<xsl:for-each
select="$items">
<li>
<xsl:value-of select="@name"/>
<xsl:if test="menu">
<xsl:call-template name="write-menu">
<xsl:with-param name="items" select="menu"/>
</xsl:call-template>
</xsl:if>
</li>
</xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>
Produces:
<?xml version="1.0" encoding="UTF-8"?>
<ul>
<li>link1</li>
<li>link2<ul>
<li>link2a</li>
<li>link2b</li>
</ul>
</li>
</ul>
As an aside, if iterating over a set of nodes, last() in a test can identify the
last node.
31. Testing if an Element is empty
The following question was asked on XSL-List
How would I test if a node is null?
I have this empty node <EMPTYNODE/>
David Carlisle expressed following
opinion:
How would I test if a node is null?
XPath doesn't have a null value. Testing for empty has an entry in the
FAQ.
like i have this empty node <EMPTYNODE />
<xsl:when test="EMPTYNODE[.!= '']">
is not working.
That tests if the string value of EMPTYNODE is not the empty string so
<EMPTYNODE /> and <EMPTYNODE><foo/></EMPTYNODE> would test the same
way
as they both have a string value of ''.
If you want to test if there are no children, then the set of child
nodes will be empty
<xsl:when test="EMPTYNODE[not(node())]">
I expressed following opinion..
I would do <xsl:if test="not(EMPTYNODE/node())">, or as David has
written.
32. Traversing nodes and
storing a matched value
The following question was asked on XSL-List
Given this XML document:
<menudata>
<menu name="link1">
<exsites>0</exsites>
</menu>
<menu name="link2">
<exsites>0</exsites>
<menu name="link2a">
<exsites>1</exsites>
<exsites>2</exsites>
</menu>
</menu>
</menudata>
It is desired:
The value of the <exsites> element determines if its parent <menu> element
will appear in the output, based on a $siteID parameter set in the stylesheet.
In some
cases, <menu> has multiple <exsites> children.
I'm not clear how to use XSL to say, "if the value of any <exsites> matches $siteID,
then flag its parent's node for different processing".
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:common="http://exslt.org/common"
version="1.0">
<xsl:output method="text" />
<xsl:param name="siteID" select="'1'" />
<xsl:template match="/">
<xsl:variable name="rtf">
<xsl:for-each select="//exsites">
<xsl:choose>
<xsl:when test=". = $siteID">
<menu name="{parent::menu/@name}">
<process x="yes" />
</menu>
</xsl:when>
<xsl:otherwise>
<menu name="{parent::menu/@name}">
<process x="no" />
</menu>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="common:node-set($rtf)/menu/process[@x =
'yes']">
<!-- some desired processing -->
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
33. XML to XML Transformation problem
Following question was asked on XSL-List.
It is needed to Transform:
<root>
<a>
<b>1</b>
<c>2</c>
<d>
<d1>text1</d1>
<d2>text2</d2>
</d>
</a>
<a>
<b>1</b>
<c>2</c>
<d>
<d1>more text1</d1>
<d2>more text1</d2>
</d>
</a>
</root>
to this XML:
<a>
<b>1</b>
<c>2</c>
<d>
<d1>text1</d1>
<d2>text2</d2>
</d>
<d>
<d1>more text1</d1>
<d2>more text1</d2>
</d>
</a>
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/root">
<a>
<xsl:for-each select="a">
<xsl:choose>
<xsl:when test="position() !=
last()">
<xsl:copy-of select="*"
/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="*[not(self::b)][not(self::c)]"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</a>
</xsl:template>
</xsl:stylesheet>
34. Generating Sequential IDs
Following question was asked on XSL-List.
I want to generate the sequence ids for all the nodes in the xml tree.
For example my source xml looks like this:
<A>
<B></B>
<C>
<D></D>
</C>
<E>
<F>
<G></G>
</F>
</E>
</A>
I want the output xml like:
<A id=1>
<B id=2></B>
<C id=3>
<D id=4></D>
</C>
<E id=5>
<F id=6>
<G id=8></G>
</F>
</E>
</A>
The following approaches are possible to solve this problem.
Andrew Welch provided the following answer:
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="id">
<xsl:number level="any" count="*"/>
</xsl:attribute>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
Joris Gillis suggested:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:attribute name="id"><xsl:number count="*"
level="any"/></xsl:attribute>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
35. Eliminating Duplicates with XSLT
XML file is:
<example>
<employee name="111"/>
<employee name="222"/>
<employee name="111"/>
</example>
XSLT stylesheet:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:template match="/example">
<xsl:for-each select="employee[not(@name = preceding-sibling::employee/@name)]">
<xsl:value-of select="@name" /><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
36. XML to XML Transformation problem (concatenating attribute values)
Following question was asked on
XSL-List.
I want a template which adds the attribute values of its
ancestors and create an attribute to the current node. The value to this
attribute is the result of concatenation of all the node values added of its
parents.
The XML file is something like:
<A seq="1">
<B seq="2" />
<C seq="3">
<D seq="4" />
</C>
</A>
The stylesheet for this problem is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | @*" priority="5">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="*" priority="6">
<xsl:copy>
<xsl:apply-templates select="@*" />
<xsl:attribute name="newSeq">
<xsl:call-template
name="concatenate-ancestor-attributes">
<xsl:with-param
name="attr_value" select="''" />
<xsl:with-param
name="element" select="." />
<xsl:with-param
name="ancestors" select="ancestor::*" />
</xsl:call-template>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template name="concatenate-ancestor-attributes">
<xsl:param name="attr_value" />
<xsl:param name="element" />
<xsl:param name="ancestors" />
<xsl:choose>
<xsl:when test="$element/parent::*">
<xsl:call-template
name="concatenate-ancestor-attributes">
<xsl:with-param name="attr_value" select="concat($attr_value,
$element/parent::*/@seq)" />
<xsl:with-param name="element" select="$element/parent::*" />
<xsl:with-param name="ancestors" select="$element/ancestor::*" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$attr_value" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
David Carlisle provided
the following XSLT 2.0 solution:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="newseq" select="string-join(ancestor-or-self::*/(if (@seq) then
string(@seq) else '0'),'')"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
37. Flat XML to Hierarchical XML transform
The following question was asked on
microsoft.public.xsl Newsgroup.
The xml-document consists of a series of nodes which together form a tree - this
is done by a parent/child relationship between the rows.
How do I transform flat xml document to a nested document?
Source document and desired output document are shown below
Source document is:
<Table>
<Node ID="1" ParentID="0" Text="1" />
<Node ID="2" ParentID="1" Text="1.1" />
<Node ID="3" ParentID="2" Text="1.1.1" />
<Node ID="4" ParentID="2" Text="1.1.2" />
<Node ID="5" ParentID="2" Text="1.1.3" />
<Node ID="6" ParentID="1" Text="1.2" />
<Node ID="7" ParentID="6" Text="1.2.1" />
</Table>
Desired output is:
<?xml version="1.0" encoding="utf-8" ?>
<TREENODES>
<treenode text="1">
<treenode text="1.1">
<treenode text="1.1.1" />
<treenode text="1.1.2" />
<treenode text="1.1.3" />
</treenode>
<treenode text="1.2">
<treenode text="1.2.1"/>
</treenode>
</treenode>
</TREENODES>
I want the XSL to run down the tree and create treenode start-tags as long as
there is children - and on the way back up the tree the end-tags have to be
inserted.
Dimitre Novatchev suggested this
stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="*/Node[not(@ParentID = /*/Node/@ID)]"/>
</xsl:template>
<xsl:template match="Node">
<treenode text="{@Text}">
<xsl:apply-templates select="/*/Node[@ParentID = current()/@ID]">
</xsl:apply-templates>
</treenode>
</xsl:template>
</xsl:stylesheet>
38. Attribute to Element Conversion (Transformation problem)
Following question was asked on XSL-List.
Given this XML document:
<Entry>
<Entry name="H" type="System" importance="1">
<Entry name="I" type="Category" importance="1">
<Entry name="J" type="Item" importance="1"></Entry>
<Entry name="K" type="Item" importance="2"></Entry>
</Entry>
<Entry name="L" type="Category" importance="2">
<Entry name="N" type="Item" importance="1"></Entry>
<Entry name="M" type="Item" importance="2"></Entry>
</Entry>
</Entry>
<Entry name="A" type="System" importance="2">
<Entry name="E" type="Category" importance="1">
<Entry name="G" type="Item" importance="1"></Entry>
<Entry name="F" type="Item" importance="2"></Entry>
</Entry>
<Entry name="B" type="Category" importance="2">
<Entry name="C" type="Item" importance="1"></Entry>
<Entry name="D" type="Item" importance="2"></Entry>
</Entry>
</Entry>
</Entry>
It is required to reform it by "type" with some new elements created so that the
output XML will look like:
<Entry>
<System type="System" importance="1"><name>H</name>
<Category type="Category" importance="1"><name>I</name>
<Item type="Item" importance="1"><name>J</name><
<Item type="Item" importance="2"><name>K</name><
</Category>
<Category type="Category" importance="2"><name>L</name>
<Item type="Item" importance="1"><name>N</name><
<Item type="Item" importance="2"><name>M</name><
</Category>
</System>
<System type="System" importance="2"><name>A</name>
<Category type="Category" importance="1"><name>E</name>
<Item type="Item" importance="1"><name>G</name><
<Item type="Item" importance="2"><name>F</name><
</Category>
<Category type="Category" importance="2"><name>B</name>
<Item type="Item" importance="1"><name>C</name><
<Item type="Item" importance="2"><name>D</name><
</Category>
</System>
</Entry>
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Entry[parent::*]">
<xsl:element name="{@type}">
<xsl:attribute name="type"><xsl:value-of select="@type" /></xsl:attribute>
<xsl:attribute name="importance"><xsl:value
<name><xsl:value-of select="@name" /></name>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
39. Generating Serial Numbers
Following question was asked on XSL-List.
I want to count all preceding sibling (steps) but they could be
in different elements.
<element1>
<element2>
<step/>
<step/>
</element2>
<element3>
<step/>
</element3>
<element4>
<step/>
<step/>
<step/>
</element4>
</element1>
The output I am after is:
<element1>
<element2>
<step number="1" />
<step number="2"/>
</element2>
<element3>
<step number="3"/>
</element3>
<element4>
<step number="4"/>
<step number="5"/>
<step number="6"/>
</element4>
</element1>
The stylesheet for this problem is:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL
<xsl:output method="xml" indent="yes" />
<xsl:template match="/element1">
<element1>
<xsl:for-each select="*">
<xsl:copy>
<xsl:call-template name="numberingTemplate">
<xsl:with-param name="node-set" select="*" />
</xsl:call-template>
</xsl:copy>
</xsl:for-each>
</element1>
</xsl:template>
<xsl:template name="numberingTemplate">
<xsl:param name="node-set" />
<xsl:for-each select="$node-set">
<xsl:element name="{name()}">
<xsl:attribute name="number"><xsl:value-of select="count(self::*
| preceding::step)" /></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
40. Displaying time in UTC in a XSLT stylesheet
Following question was asked on XSL-List.
Stylesheet (written using XSLT 2.0):
<?xml
version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xyz="http://utc"
version="2.0">
<xsl:output method="text" />
<xsl:template match="/">
UTC time - <xsl:value-of select="xyz:UtcTime(xs:string(current-time()))"
/>
</xsl:template>
<xsl:function name="xyz:UtcTime">
<xsl:param name="curr-time" as="xs:string"/>
<xsl:choose>
<xsl:when test="contains($curr-time,'+')">
<xsl:variable
name="local-time" select="substring-before($curr-time,'+')" />
<xsl:variable
name="time-zone" select="substring-after($curr-time,'+')" />
<xsl:variable
name="x" select="substring-before($local-time,':')" />
<xsl:variable
name="y" select="substring-before(substring-after($local-time,':'),':')" />
<xsl:variable
name="z" select="substring-after(substring-after($local-time,':'),':')" />
<xsl:variable
name="sec1" select="(xs:integer($x) * 60 * 60) + (xs:integer($y) * 60) +
xs:float($z)" />
<xsl:variable
name="u" select="substring-before($time-zone,':')" />
<xsl:variable
name="v" select="substring-after($time-zone,':')" />
<xsl:variable
name="sec2" select="(xs:integer($u) * 60 * 60) + (xs:integer($v) * 60)" />
<xsl:variable
name="sec3" select="$sec1 - $sec2" />
<xsl:value-of
select="floor(($sec3 div 60) div 60)" />:<xsl:value-of select="floor(($sec3 div
60) mod 60)" />:<xsl:value-of select="$sec3 - ((floor(($sec3 div 60) div 60) *
60 * 60) + (floor(($sec3 div 60) mod 60) * 60))" />
</xsl:when>
<xsl:when test="contains($curr-time,'-')">
<xsl:variable
name="local-time" select="substring-before($curr-time,'-')" />
<xsl:variable
name="time-zone" select="substring-after($curr-time,'-')" />
<xsl:variable
name="x" select="substring-before($local-time,':')" />
<xsl:variable
name="y" select="substring-before(substring-after($local-time,':'),':')" />
<xsl:variable
name="z" select="substring-after(substring-after($local-time,':'),':')" />
<xsl:variable
name="sec1" select="(xs:integer($x) * 60 * 60) + (xs:integer($y) * 60) +
xs:float($z)" />
<xsl:variable
name="u" select="substring-before($time-zone,':')" />
<xsl:variable
name="v" select="substring-after($time-zone,':')" />
<xsl:variable
name="sec2" select="(xs:integer($u) * 60 * 60) + (xs:integer($v) * 60)" />
<xsl:variable
name="sec3" select="$sec1 - $sec2" />
<xsl:value-of
select="floor(($sec3 div 60) div 60)" />:<xsl:value-of select="floor(($sec3 div
60) mod 60)" />:<xsl:value-of select="$sec3 - ((floor(($sec3 div 60) div 60) *
60 * 60) + (floor(($sec3 div 60) mod 60) * 60))" />
</xsl:when>
</xsl:choose>
</xsl:function>
</xsl:stylesheet>
David Carlisle
suggested:
Use:
adjust-time-to-timezone
where xdt is xmlns:xdt="http://www.w3.org/2005/04
41. Identity Transform (case conversion)
What will be the XSLT stylesheet, which when given any XML document as input, will produce an identical XML document as output. But the condition is: all the letters (a-z) anywhere in source XML should change from small case to upper case.
The stylesheet for this problem is:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
exclude-result-prefixes="exslt">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="small" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="caps" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:template match="*">
<xsl:element name="{translate(name(), $small, $caps)}">
<xsl:variable name="nsHolder">
<test>
<xsl:for-each select="namespace::*[not(. = 'http://www.w3.org/XML/1998/namespace')]">
<xsl:attribute name="{translate(name(), $small, $caps)}:dummy{position()}"
namespace="{translate(., $small, $caps)}"></xsl:attribute>
</xsl:for-each>
</test>
</xsl:variable>
<xsl:copy-of select="exslt:node-set($nsHolder)/test/namespace::*"/>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="translate(., $small, $caps)" />
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{translate(name(), $small, $caps)}"
namespace="{translate(namespace-uri(), $small, $caps)}"><xsl:value-of
select="translate(., $small, $caps)" /></xsl:attribute>
</xsl:template>
<xsl:template match="processing-instruction()">
<xsl:processing-instruction name="{translate(name(), $small,
$caps)}"><xsl:value-of select="translate(., $small, $caps)" /></xsl:processing-instruction>
</xsl:template>
<xsl:template match="comment()">
<xsl:comment><xsl:value-of select="translate(., $small,
$caps)" /></xsl:comment>
</xsl:template>
</xsl:stylesheet>
Thanks to
George Cristian Bina, Michael Kay and
Dimitre Novatchev for suggestions.
42. XML to XML transformation (Structural changes)
Following question was asked on XSL-List.
I'm trying to copy a single element
("topic") and attribute ("id") to a new XML file, discarding all other elements
and attributes. I'd also like to rename the topic element as topicref, and
rename the id attribute as href.
Input XML:
<topic id="unique_id">
<title>Title</title>
<body>
<p>Some text.</p>
</body>
<topic id="unique_id">
<title>Title</title>
<body>
<p>Some text.</p>
</body>
</topic>
<topic id="unique_id">
<title>Title</title>
<body>
<p>Some text.</p>
</body>
<topic id="unique_id">
<title>Title</title>
<body>
<p>Some text.</p>
</body>
</topic>
</topic>
</topic>
Desired output is:
<topicref href="unique_id">
<topicref href="unique_id"/>
<topicref href="unique_id">
<topicref href="unique_id"/>
</topicref>
</topicref>
The stylesheet for this problem is:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL
<xsl:output method="xml" indent="yes" />
<xsl:template match="topic">
<topicref href="{@id}">
<xsl:apply-templates />
</topicref>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
43. Writing modular stylesheet (XSLT 2.0)
Following question was asked on XSL-List.
I'm trying to find a better
alternative to our current test XSL template. Currently, we have the following
XML file structure (extremely simplified from the original):
<resultset>
<term>
<termname>First Term</termname>
<termattrs>
<termdef>This is the firstterm description.</termdef>
</termattrs>
</term>
<term>
<termname>Second Term</termname>
<termattrs>
<termdef>This is the second term description.</termdef>
</termattrs>
</term>
</resultset>
We're trying build a glossary of the terms listed in alphabetical order. In
other words, after the translation, viewing the XML (in IE) should produce an
output similar to the following:
A
B
...
F
First Term (This is the first term description.)
...
S
Second Term (This is the second term description.)
...
Z
Unfortunately, our current XSL logic involves an <xsl:for-each> loop which tests
whether the first letter of <termname> begins with a letter. However, this is
done for *each* letter, so we have duplicate code,
i.e.:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
...
<xsl:template match="/resultset">
<p>A</p>
<xsl:for-each select="term">
<xsl:if test="starts-with(termname, 'A') or starts-with(termname,
'a')">
<xsl:value-of select="termname" />
<xsl:value-of select="termattrs/termdef" />
</xsl:if>
</xsl:for-each>
<p>B</p>
<xsl:for-each select="term">
<xsl:if test="starts-with(termname, 'B') or starts-with(termname,
'b')">
<xsl:value-of select="termname" />
<xsl:value-of select="termattrs/termdef" />
</xsl:if>
</xsl:for-each>
...
</xsl:template>
</xsl:stylesheet>
Is there a better method than using multiple <xsl:for-each> loops?
Here is a better technique:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="http://dummy"
version="2.0">
<xsl:output method="html"/>
<xsl:template match="/resultset">
<xsl:variable name="here" select="." />
<html>
<head>
<title/>
</head>
<body>
<xsl:for-each select="65 to
90">
<xsl:variable
name="letter" select="codepoints-to-string(.)"/>
<p><xsl:value-of
select="$letter" /></p>
<xsl:variable
name="htmlFragment" select="my:getHtmlFragment($letter, $here)" />
<xsl:copy-of
select="$htmlFragment" />
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:function name="my:getHtmlFragment" as="node()*">
<xsl:param name="letter" as="xs:string"/>
<xsl:param name="here" as="element()"/>
<ul>
<xsl:for-each select="$here/term[starts-with(termname,
$letter) or starts-with(termname, lower-case($letter))]">
<li><xsl:value-of select="termname" /> (<xsl:value-of
select="termattrs/termdef" />)</li>
</xsl:for-each>
</ul>
</xsl:function>
</xsl:stylesheet>
(Thanks to David Carlisle for
suggestions)
44. Creating a two-column HTML table from an unsorted list of XML nodes
The following problem was posted on the newsgroup, XSLT@googlegroups.com
I have some XML elements that are not sorted:
<element name="sam" />
<element name="bob" />
<element name="alice" />
<element name="mary" />
<element name="fred" />
<element name="barbara" />
etc.
and we want to create a 2-column sorted HTML table with the names,
thus if we had the 6 elements above
alice fred
barbara mary
bob sam
We can have any number of elements. We need a XSLT 1.0 solution.
Solution:
At first thought, I felt this could be a difficult problem to solve, because the sorted output is split into two columns (and in a spiral fashion - i.e, down and then up).
After thinking a bit, I came up with the following solution, which uses the node-set extension function and some minor arithmetic trick:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
version="1.0">
<xsl:output method="html" />
<xsl:template match="/elements">
<html>
<head>
<title/>
</head>
<body>
<xsl:variable
name="n" select="count(element)" />
<xsl:variable
name="no-of-rows">
<xsl:choose>
<xsl:when test="$n mod 2 = 1">
<xsl:value-of select="floor(($n div 2) + 1)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$n div 2" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<table>
<xsl:variable name="sorted-sequence">
<xsl:apply-templates select="element">
<xsl:sort select="@name" />
</xsl:apply-templates>
</xsl:variable>
<xsl:for-each select="exslt:node-set($sorted-sequence)/name[position() <=
$no-of-rows]">
<xsl:variable name="x" select="position() + $no-of-rows" />
<tr>
<td>
<xsl:value-of select="." />
</td>
<td>
<xsl:value-of select="exslt:node-set($sorted-sequence)/name[$x]" />
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="element">
<name>
<xsl:value-of select="@name" />
</name>
</xsl:template>
</xsl:stylesheet>
I tried the above stylesheet with the following XML:
<elements>
<element name="sam" />
<element name="bob" />
<element name="alice" />
<element name="mary" />
<element name="fred" />
<element name="barbara" />
</elements>