I have many files that resemble the following a.xml file, although they are much larger:
<?xml version="1.0" encoding="UTF-8"?>
<a version="3.0">
<b bb="P1">
<!--============== b:P1 c:1 ==============-->
<c cc="1">
<d dd="61">d1
</d>
</c>
<!--============== b:P1 c:2 ==============-->
<c cc="2">
<d dd="17">d2
</d>
</c>
</b>
</a>
For each c there is only one preceding comment.
I want to output a file with the same structure of the following a.csv file:
1|1|a|0| |0| |0|
2|1|a|1|b|0| |0|
3|1|a|1|b|1|!|0|
3|1|a|1|b|2|c|0|
4|1|a|1|b|2|c|1|d
3|1|a|1|b|3|!|0|
3|1|a|1|b|4|c|0|
4|1|a|1|b|4|c|1|d
It represents the hierarchical tree for a.xml:
Field 1 is the hierarchical level. For instance a has level 1, b has level 2, etc.
Fields 2, 4, 6 and 8 are equal to:
if the current node's level is less than the current field's level then 0
else the total number of preceding siblings and comments plus one
Field 3, 5, 7 and 9 are equal to:
if the current node's level is less than the current field's level then " "
else either "!" if the current node is preceded by a comment or the node's name
In this example level 3 contains comments.
I cannot find a good way to do a for-each that includes both nodes and comments. When I use <xsl:for-each select="*"> I only loop through the nodes.
Because of that, I've come out with the following xslt, that checks if the current node is preceded by a comment:
<?xml version="1.0"?>
<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="/">
<xsl:for-each select="*">
<xsl:variable name="elm01" select="local-name()" />
<xsl:text>1</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|0| |0| |0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm02" select="local-name()" />
<xsl:text>2</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|0| |0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm03" select="local-name()" />
<xsl:if test="preceding-sibling::comment()[1]">
<xsl:text>3</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|!</xsl:text>
<xsl:text>|0|</xsl:text>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:text>3</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<!-- TODO: I want to count the total of preceding siblings and comments -->
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*|comment())+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm03" />
<xsl:text>|0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm04" select="local-name()" />
<xsl:text>4</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm03" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm04" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
However when I run the following command:
xsltproc a.xslt a.xml > a.csv
I get the following a.csv file:
1|1|a|0| |0| |0|
2|1|a|1|b|0| |0|
3|1|a|1|b|1|!|0|
3|1|a|1|b|1|c|0|
4|1|a|1|b|1|c|1|d
3|1|a|1|b|2|!|0|
3|1|a|1|b|2|c|0|
4|1|a|1|b|2|c|1|d
Please notice that field 6 is incorrect:
it is equal to 1 both for the 1st comment and the 1st node c and its children
it is equal to 2 both for the 2nd comment and the 2nd node c and its children
Do you have any solutions to suggest?
SOLUTION (by Tim)
I can now get the correct output by using the following xslt file:
<?xml version="1.0"?>
<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="/">
<xsl:for-each select="*">
<xsl:variable name="elm01" select="local-name()" />
<xsl:text>1</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|0| |0| |0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm02" select="local-name()" />
<xsl:text>2</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|0| |0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm03" select="local-name()" />
<xsl:if test="preceding-sibling::comment()[1]">
<xsl:text>3</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*|preceding-sibling::comment())"/>
<xsl:text>|!</xsl:text>
<xsl:text>|0|</xsl:text>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:text>3</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*|preceding-sibling::comment())+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm03" />
<xsl:text>|0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm04" select="local-name()" />
<xsl:text>4</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*|../preceding-sibling::comment())+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm03" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm04" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Alternatively you can also use the xslt in Tim's reply, that gets rid of repetitions.
There expression you want is this...
<xsl:value-of select="count(preceding-sibling::*|preceding-sibling::comment()) + 1" />
Or this would work too...
<xsl:value-of select="count(preceding-sibling::node()[self::*|self::comment()]) + 1" />
But you can also use xsl:number
<xsl:number count="*|comment()" />
Your stylesheet does seem a bit over-complicated though, with much repetition. Try this more generic one instead. This recursively calls each level, passing in the constructed line each call to save having to build it each time.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:param name="maxLevel" select="4" />
<xsl:template match="*|comment()">
<xsl:param name="level" select="1" />
<xsl:param name="prev" />
<xsl:variable name="new">
<xsl:value-of select="$prev" />
<xsl:text>|</xsl:text>
<xsl:number count="*|comment()" />
<xsl:text>|</xsl:text>
<xsl:choose>
<xsl:when test="self::*">
<xsl:value-of select="local-name()" />
</xsl:when>
<xsl:otherwise>
<xsl:text>!</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$level" />
<xsl:value-of select="$new" />
<xsl:call-template name="pad">
<xsl:with-param name="levels" select="$maxLevel - $level" />
</xsl:call-template>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="*|comment()">
<xsl:with-param name="level" select="$level + 1" />
<xsl:with-param name="prev" select="$new" />
</xsl:apply-templates>
</xsl:template>
<xsl:template name="pad">
<xsl:param name="levels" />
<xsl:if test="$levels > 0">
<xsl:text>|0| </xsl:text>
<xsl:call-template name="pad">
<xsl:with-param name="levels" select="$levels - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/jyRYYiy
Related
I would like to convert xml file format to another format;
using XSL version 1.0 or 2.0.
Input XML file:
<ROOT_XML>
<Struct id="_6" name="Result" context="_1" members="_9 _10 _11 _13 _14 "/>
<FundamentalType id="_7" name="int" size="32" align="32"/>
<FundamentalType id="_8" name="float" size="32" align="32"/>
<Field id="_9" name="angle" type="_8" offset="0" context="_6"/>
<Field id="_10" name="row" type="_7" offset="32" context="_6"/>
<Field id="_11" name="cloth" type="_18" offset="96" context="_6"/>
<Destructor id="_13" name="EmptyClass" artificial="1" throw="" context="_6">
</Destructor>
<Constructor id="_14" name="Result" context="_6">
<Argument type="_20" location="f0:2" file="f0" line="2"/>
</Constructor>
<Constructor id="_15" name="Result" context="_6"/>
<FundamentalType id="_17" name="unsigned int" size="32" align="32"/>
<ArrayType id="_18" min="0" max="29u" type="_21" size="240" align="8"/>
<ReferenceType id="_19" type="_6" size="32" align="32"/>
<ReferenceType id="_20" type="_6c" size="32" align="32"/>
<FundamentalType id="_21" name="char" size="8" align="8"/>
</ROOT_XML>
Output XML file:
<Struct Result>
<Fields>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Fields>
</Struct>
The is an example on how to parse the 'members' attribute list
http://www.w3.org/1999/XSL/Transform" version = "1.0">
<xsl:template match="/ROOT_XML/Struct">
<Struct><xsl:value-of select="name"/>
<xsl:choose>
<xsl:when test="boolean(./#members)">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="./#members"/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</Struct>
</xsl:template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<member>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
</member>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<member>
<xsl:value-of select="$list"/>
</member>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This code is a starting point to extract the relevant id's from the 'members' attribute of the 'Struct' node, and later be used to emit only the 'Field' nodes.
In addition, the output XML file might contains more than one 'Struct' node,
For Instance:
<Struct Result>
<Fields>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Fields>
</Struct>
<Struct Answer>
<Fields>
<Field name="direction" type="float" size="32"/>
<Field name="col" type="int" size="32"/>
<Field name="paper" type="char" size="232"/>
</Fields>
</Struct>
Thanks for the reply. still, I would like to emphasize the logic on how to get the xml output.
The xslt processor needs to parse the 'members' attribute of the Struct node.
The 'members' attribute is a list of Field's ids.
In the above example:
Only "members=_9 _10 _11" are Field nodes! and therefore they should be output as done by Michael previously.
The rest of the items in the list are omitted (i.e. members="_13 _14")
The combined code: (I need assistance to continue...)
http://www.w3.org/1999/XSL/Transform" version = "1.0">
<Struct><xsl:value-of select="name"/>
<xsl:choose>
<xsl:when test="boolean(./#members)">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="./#members"/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</Struct>
</xsl:template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<member>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
<!-- TODO: select holds the member's id...
Q: how should we continue from here?? -->
</member>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<member>
<xsl:value-of select="$list"/>
<!-- TODO: select holds the member's id...
Q: how should we continue from here?? -->
</member>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:template match="ROOT_XML">
<Struct>
<xsl:apply-templates select="Field"/>
</Struct>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
</xsl:stylesheet>
when applied to your example input, will return:
<?xml version="1.0" encoding="UTF-8"?>
<Struct>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Struct>
How this works:
If a Field's type matches an id of a FundamentalType, then
the type and the size values are looked up from the matching
FundamentalType;
If a Field's type matches an id of an ArrayType, then the size value is looked up from the matching ArrayType, while the type value is looked up from the FundamentalType whose id matches the type attribute of the ArrayType.
Edit:
If you want each Struct to include only Fields whose id is listed in its members attribute, you can do it this way:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="field" match="Field" use="#id" />
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:variable name="xml" select="/" />
<xsl:template match="/ROOT_XML">
<root>
<xsl:for-each select="Struct">
<Struct name="{#name}">
<xsl:apply-templates select="key('field', tokenize(#members, ' '))"/>
</Struct>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
</xsl:stylesheet>
Edit 2
The same thing in XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="field" match="Field" use="#id" />
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:variable name="xml" select="/" />
<xsl:template match="/ROOT_XML">
<root>
<xsl:for-each select="Struct">
<xsl:variable name="members">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="#members"/>
</xsl:call-template>
</xsl:variable>
<Struct name="{#name}">
<xsl:apply-templates select="key('field', exsl:node-set($members)/token)"/>
</Struct>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="' '"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<token>
<xsl:value-of select="substring-before($text, $delimiter)"/>
</token>
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<token>
<xsl:value-of select="$text"/>
</token>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I have some xml in the following format:
<top>
<topValue Value="1#1#5" />
<topValue Value="2#2#10" />
<topValue Value="1#1#3" />
<topValue Value="2#2#30" />
</top>
and output should look like that:
<boo>
<booEnrty>
<v>5</v>
<v>10</v>
</booEnrty>
<booEnrty>
<v>3</v>
<v>30</v>
</booEnrty>
</boo>
my XSLT to transform
<boo>
<xsl:for-each select="top/topValue">
<xsl:if test="position() mod 2 = 0">
<booEnrty>
<v><xsl:value-of select="substring-after(substring-after(#Value,'#'),'#')"/></v>
</booEnrty>
</xsl:if>
</xsl:for-each>
</boo>
What should the XSLT document look like to do this transform?
Any ideas?
Thanks
Maybe someone got a better approach to this, but the XSLT below works for your case.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="top">
<boo>
<xsl:apply-templates select="topValue[position() mod 2 = 1]"/>
</boo>
</xsl:template>
<xsl:template match="topValue[position() mod 2 = 1]">
<booEntry>
<v>
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string" select="#Value" />
<xsl:with-param name="delimiter" select="'#'" />
</xsl:call-template>
</v>
<xsl:apply-templates select="following-sibling::*[1]"/>
</booEntry>
</xsl:template>
<xsl:template match="topValue">
<v>
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string" select="#Value" />
<xsl:with-param name="delimiter" select="'#'" />
</xsl:call-template>
</v>
</xsl:template>
<xsl:template name="substring-after-last">
<xsl:param name="string" />
<xsl:param name="delimiter" />
<xsl:choose>
<xsl:when test="contains($string, $delimiter)">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string"
select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiter" select="$delimiter" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$string"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
How about something short and simple?
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/top">
<boo>
<xsl:for-each select="topValue[position() mod 2 = 1]">
<booEnrty>
<xsl:for-each select=". | following-sibling::topValue[1]">
<v>
<xsl:value-of select="substring-after(substring-after(#Value,'#'),'#')"/>
</v>
</xsl:for-each>
</booEnrty>
</xsl:for-each>
</boo>
</xsl:template>
</xsl:stylesheet>
I would like to print path of element and attributes if any along with values using XSLT. e.g
XML :
<root>
<node attr='abc' module='try'>
<subnode>Roshan</subnode>
<subnode>Chetan</subnode>
<subnode>Jennifer</subnode>
</node>
</root>
Output :
/root##
/root/node##
/root/node/#attr##abc
/root/node/#module##try
/root/node/subnode[1]##Roshan
/root/node/subnode[2]##Chetan
/root/node/subnode[3]##Jennifer
I am trying with below snippet, but could only print path of element and it's value
<xsl:template match="*">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/',local-name())" />
<xsl:if
test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
<xsl:value-of
select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')" />
</xsl:if>
<!-- <xsl:call-template name="attrData"></xsl:call-template> -->
</xsl:for-each>
<xsl:text>##</xsl:text>
<xsl:apply-templates select="node()" />
</xsl:template>
I am new to XSLT. Please help!!!!
I made the following XSLT and added also the [position] to the output. You can remove that if you need.
This gives this output:
/root[1]
/root[1]/node[1]
/root[1]/node[1]/#attr[1]##abc
/root[1]/node[1]/#module[1]##try
/root[1]/node[1]/subnode[1]##Roshan
/root[1]/node[1]/subnode[2]##Chetan
/root[1]/node[1]/subnode[3]##Jennifer
With this XSLT. With the two output template you can choose how to print the Xpath.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="" version="2.0">
<xsl:output method="text" encoding="utf-8" />
<xsl:template match="/">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="*">
<xsl:call-template name="generateXPath">
<xsl:with-param name="previous" select="''"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="generateXPath">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:if test="not(empty(.))">
<xsl:variable name="thisXPath" select="concat($previous, '/', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']')"></xsl:variable>
<xsl:apply-templates select="." mode="output">
<xsl:with-param name="previous" select="$previous"/>
</xsl:apply-templates>
<xsl:text>
</xsl:text>
<xsl:for-each select="*|#*">
<xsl:call-template name="generateXPath">
<xsl:with-param name="previous" select="$thisXPath"/>
</xsl:call-template>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="output">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:variable name="thisXPath">
<xsl:value-of select="concat($previous, '/', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']')"></xsl:value-of>
<xsl:if test="not(*)">
<xsl:value-of select="concat('##',text())"></xsl:value-of>
</xsl:if>
</xsl:variable>
<xsl:value-of select="$thisXPath" />
</xsl:template>
<xsl:template match="#*" mode="output">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:variable name="thisXPath" select="concat($previous, '/#', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']','##',.)"></xsl:variable>
<xsl:value-of select="$thisXPath" />
</xsl:template>
</xsl:stylesheet>
I'm transforming XML to an HTML document. In this document I want to embed XML markup for a node that was just transformed (the HTML document is a technical spec).
For example, if my XML was this:
<transform-me>
<node id="1">
<stuff type="floatsam">
<details>Various bits</details>
</stuff>
</node>
</transform-me>
I'd want my XSLT output to look something like this:
<h1>Node 1</h1>
<h2>Stuff (floatsam)</h2>
Various bits
<h2>The XML</h2>
<stuff type="floatsam">
<details>Various bits</details>
</stuff>
I'm hoping there is an XSLT function that I can call in my <stuff> template to which I can pass the current node (.) and get back escaped XML markup for <stuff> and all its descendants. I have a feeling unparsed-text() might be the way to go but can't get it to work.
Very simple template
<xsl:template match="node()" mode="print">
<xsl:choose>
<!-- is it element? -->
<xsl:when test="name()">
<!-- start tag -->
<xsl:text><</xsl:text>
<xsl:value-of select="name()" />
<!-- attributes -->
<xsl:apply-templates select="#*" mode="print" />
<xsl:choose>
<!-- has children -->
<xsl:when test="node()">
<!-- closing bracket -->
<xsl:text>></xsl:text>
<!-- children -->
<xsl:apply-templates mode="print" />
<!-- end tag -->
<xsl:text></</xsl:text>
<xsl:value-of select="name()" />
<xsl:text>></xsl:text>
</xsl:when>
<!-- is empty -->
<xsl:otherwise>
<!-- closing bracket -->
<xsl:text>/></xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- text -->
<xsl:otherwise>
<xsl:copy />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*" mode="print">
<xsl:text> </xsl:text>
<xsl:value-of select="name()" />
<xsl:text>="</xsl:text>
<xsl:value-of select="." />
<xsl:text>"</xsl:text>
</xsl:template>
Used
<xsl:apply-templates mode="print" />
You can even add pretty printing if you want.
The answer to this is more complex that you would at first think. Proper "double-escaping" of attribute values and text nodes must occur.
This XSLT 1.0 template does a correct (though not complete) printing of an XML node, including (an attempt to do) proper pretty-printing with configurable indentation:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="html" encoding="utf-8" />
<!-- defaults and configurable parameters -->
<xsl:param name="NL" select="'
'" /><!-- newline sequence -->
<xsl:param name="INDENTSEQ" select="' '" /><!-- indent sequence -->
<xsl:variable name="LT" select="'<'" />
<xsl:variable name="GT" select="'>'" />
<xsl:template match="transform-me">
<html>
<body>
<!-- this XML-escapes an entire sub-structure -->
<pre><xsl:apply-templates select="*" mode="XmlEscape" /></pre>
</body>
</html>
</xsl:template>
<!-- element nodes will be handled here, incl. proper indenting -->
<xsl:template match="*" mode="XmlEscape">
<xsl:param name="indent" select="''" />
<xsl:value-of select="concat($indent, $LT, name())" />
<xsl:apply-templates select="#*" mode="XmlEscape" />
<xsl:variable name="HasChildNode" select="node()[not(self::text())]" />
<xsl:variable name="HasChildText" select="text()[normalize-space()]" />
<xsl:choose>
<xsl:when test="$HasChildNode or $HasChildText">
<xsl:value-of select="$GT" />
<xsl:if test="not($HasChildText)">
<xsl:value-of select="$NL" />
</xsl:if>
<!-- render child nodes -->
<xsl:apply-templates mode="XmlEscape" select="node()">
<xsl:with-param name="indent" select="concat($INDENTSEQ, $indent)" />
</xsl:apply-templates>
<xsl:if test="not($HasChildText)">
<xsl:value-of select="$indent" />
</xsl:if>
<xsl:value-of select="concat($LT, '/', name(), $GT, $NL)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(' /', $GT, $NL)" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- comments will be handled here -->
<xsl:template match="comment()" mode="XmlEscape">
<xsl:param name="indent" select="''" />
<xsl:value-of select="concat($indent, $LT, '!--', ., '--', $GT, $NL)" />
</xsl:template>
<!-- text nodes will be printed XML-escaped -->
<xsl:template match="text()" mode="XmlEscape">
<xsl:if test="not(normalize-space() = '')">
<xsl:call-template name="XmlEscapeString">
<xsl:with-param name="s" select="." />
<xsl:with-param name="IsAttribute" select="false()" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<!-- attributes become a string: '{name()}="{escaped-value()}"' -->
<xsl:template match="#*" mode="XmlEscape">
<xsl:value-of select="concat(' ', name(), '="')" />
<xsl:call-template name="XmlEscapeString">
<xsl:with-param name="s" select="." />
<xsl:with-param name="IsAttribute" select="true()" />
</xsl:call-template>
<xsl:value-of select="'"'" />
</xsl:template>
<!-- template to XML-escape a string -->
<xsl:template name="XmlEscapeString">
<xsl:param name="s" select="''" />
<xsl:param name="IsAttribute" select="false()" />
<!-- chars &, < and > are never allowed -->
<xsl:variable name="step1">
<xsl:call-template name="StringReplace">
<xsl:with-param name="s" select="$s" />
<xsl:with-param name="search" select="'&'" />
<xsl:with-param name="replace" select="'&'" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="step2">
<xsl:call-template name="StringReplace">
<xsl:with-param name="s" select="$step1" />
<xsl:with-param name="search" select="'<'" />
<xsl:with-param name="replace" select="'<'" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="step3">
<xsl:call-template name="StringReplace">
<xsl:with-param name="s" select="$step2" />
<xsl:with-param name="search" select="'>'" />
<xsl:with-param name="replace" select="'>'" />
</xsl:call-template>
</xsl:variable>
<!-- chars ", TAB, CR and LF are never allowed in attributes -->
<xsl:choose>
<xsl:when test="$IsAttribute">
<xsl:variable name="step4">
<xsl:call-template name="StringReplace">
<xsl:with-param name="s" select="$step3" />
<xsl:with-param name="search" select="'"'" />
<xsl:with-param name="replace" select="'"'" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="step5">
<xsl:call-template name="StringReplace">
<xsl:with-param name="s" select="$step4" />
<xsl:with-param name="search" select="' '" />
<xsl:with-param name="replace" select="'	'" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="step6">
<xsl:call-template name="StringReplace">
<xsl:with-param name="s" select="$step5" />
<xsl:with-param name="search" select="'
'" />
<xsl:with-param name="replace" select="'
'" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="step7">
<xsl:call-template name="StringReplace">
<xsl:with-param name="s" select="$step6" />
<xsl:with-param name="search" select="'
'" />
<xsl:with-param name="replace" select="'
'" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$step7" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$step3" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- generic string replace template -->
<xsl:template name="StringReplace">
<xsl:param name="s" select="''" />
<xsl:param name="search" select="''" />
<xsl:param name="replace" select="''" />
<xsl:choose>
<xsl:when test="contains($s, $search)">
<xsl:value-of select="substring-before($s, $search)" />
<xsl:value-of select="$replace" />
<xsl:variable name="rest" select="substring-after($s, $search)" />
<xsl:if test="$rest">
<xsl:call-template name="StringReplace">
<xsl:with-param name="s" select="$rest" />
<xsl:with-param name="search" select="$search" />
<xsl:with-param name="replace" select="$replace" />
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$s" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied to this test XML:
<transform-me>
<node id="1">
<!-- a comment -->
<stuff type="fl"'
oatsam">
<details>Various bits & pieces</details>
<details>
</details>
<details attr="value">
<childnode>text and <escaped-text /></childnode>
</details>
</stuff>
</node>
</transform-me>
The following output is produced (source code):
<html>
<body>
<pre><node id="1">
<!-- a comment -->
<stuff type="fl"'
	oatsam">
<details>Various bits & pieces</details>
<details />
<details attr="value">
<childnode>text and <escaped-text /<</childnode>
</details>
</stuff>
</node>
</pre>
</body>
</html>
and when viewed in the browser you see:
<node id="1">
<!-- a comment -->
<stuff type="fl"'
oatsam">
<details>Various bits & pieces</details>
<details />
<details attr="value">
<childnode>text and <escaped-text /<</childnode>
</details>
</stuff>
</node>
Note that by "not complete" I mean that things like namespaces and processing instructions for example are currently unhandled. But its easy to make a template for them.
#Oded: Sorry to have been poor in my exposition... My input document has a fragment like this:
<recordset name="resId" >
<record n="0">example 1</record>
<record n="1">example 2</record>
<record n="2">example 1</record>
....
<record n="N">example 1</record>
</recordset>
containing an arbitrarily long node sequence. The attribute "n" reports the order of the node in the sequence. I need to arrange as output that sequence in a M (rows) x N (columns) table and I have some trouble doing that. I cannot call a template
<xsl:template match="recordset">
<table>
<xsl:apply-templates select="record"/>
</table>
</xsl:template>
with something like:
<xsl:template match="record">
<xsl:if test="#n mod 3 = 0">
<tr>
</xsl:if>
........
<td><xsl:value-of select"something"></td>
because code is invalid (and I should repeat it at the end of the template in some way)
and I must put some (maybe too much) trust in the presence of the numbered attribute. Someone has a hint? Thanks!
You must ensure that nesting is never broken. Things you want nested in the output must be nested in the XSLT.
<xsl:variable name="perRow" select="3" />
<xsl:template match="recordset">
<table>
<xsl:apply-templates
mode = "tr"
select = "record[position() mod $perRow = 1]"
/>
</table>
</xsl:template>
<xsl:template match="record" mode="tr">
<tr>
<xsl:variable name="td" select="
. | following-sibling::record[position() < $perRow]
" />
<xsl:apply-templates mode="td" select="$td" />
<!-- fill up the last row -->
<xsl:if test="count($td) < $perRow">
<xsl:call-template name="filler">
<xsl:with-param name="rest" select="$perRow - count($td)" />
</xsl:call-template>
</xsl:if>
</tr>
</xsl:template>
<xsl:template match="record" mode="td">
<td>
<xsl:value-of select="." />
</td>
</xsl:template>
<xsl:template name="filler">
<xsl:param name="rest" select="0" />
<xsl:if test="$rest">
<td />
<xsl:call-template name="filler">
<xsl:with-param name="rest" select="$rest - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Using xslt 2.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:param name="rows">3</xsl:param>
<xsl:template match="recordset">
<table>
<xsl:for-each-group select="record" group-by="count(preceding-sibling::*) mod $rows ">
<xsl:value-of select="current-grouping-key()"/>
<tr>
<xsl:for-each select="current-group()">
<td>
<xsl:apply-templates/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0, using a general n-per-row template.
With the row element name as a parameter, the n-per-row template is not tied to you input or output format.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="recordset">
<table>
<xsl:call-template name="n-per-row">
<xsl:with-param name="select" select="record" />
<xsl:with-param name="row-size" select="2"/>
<xsl:with-param name="row-element" select="'tr'"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template match="record">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template name="n-per-row">
<xsl:param name="select" />
<xsl:param name="row-size" />
<xsl:param name="row-element" />
<xsl:param name="start">
<xsl:text>1</xsl:text>
</xsl:param>
<xsl:variable name="count" select="count($select)" />
<xsl:variable name="last-tmp" select="number($start) + number($row-size)" />
<xsl:variable name="last">
<xsl:choose>
<xsl:when test="$last-tmp > $count">
<xsl:value-of select="$count"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$last-tmp"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$row-element}">
<xsl:apply-templates select="$select[position() <= $last]"/>
</xsl:element>
<xsl:if test="count($select) > $last">
<xsl:call-template name="n-per-row">
<xsl:with-param name="select" select="$select[position() > $last]"/>
<xsl:with-param name="row-size" select="$row-size"/>
<xsl:with-param name="row-element" select="$row-element"/>
<xsl:with-param name="start" select="$start"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>