I have a scenario where I need to convert the input XML to a CSV file. The output should have values for every attribute with their respective XPATH.
For example: If my input is
<School>
<Class>
<Student name="" class="" rollno="" />
<Teacher name="" qualification="" Employeeno="" />
</Class>
</School>
The expected output would be:
School/Class/Student/name, School/Class/Student/class, School/Class/Student/rollno,
School/Class/Teacher/name, School/Class/Teacher/qualification, School/Class/Teacher/Employeeno
An example does not always embody a rule. Assuming you want a row for each element that has any attributes, no matter where in the document it is, and a column for each attribute of an element, try:
Edit:
This is an improved version, corrected to work properly with nested elements.
<?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" encoding="UTF-8"/>
<xsl:template match="*">
<xsl:param name="path" />
<xsl:variable name="newpath" select="concat($path, '/', name())" />
<xsl:apply-templates select="#*">
<xsl:with-param name="path" select="$newpath"/>
</xsl:apply-templates>
<xsl:if test="#*">
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:apply-templates select="*">
<xsl:with-param name="path" select="$newpath"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#*">
<xsl:param name="path" />
<xsl:value-of select="substring(concat($path, '/', name()), 2)"/>
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to the following test input:
<Root>
<Parent parent="1" parent2="1b">
<Son son="11" son2="11b"/>
<Daughter daughter="12" daughter2="12b">
<Grandson grandson="121" grandson2="121b"/>
<Granddaughter granddaughter="122" granddaughter2="122b"/>
</Daughter>
<Sibling/>
</Parent>
</Root>
the result is:
Root/Parent/parent, Root/Parent/parent2
Root/Parent/Son/son, Root/Parent/Son/son2
Root/Parent/Daughter/daughter, Root/Parent/Daughter/daughter2
Root/Parent/Daughter/Grandson/grandson, Root/Parent/Daughter/Grandson/grandson2
Root/Parent/Daughter/Granddaughter/granddaughter, Root/Parent/Daughter/Granddaughter/granddaughter2
Note that the number of columns in each row can vary - this is often unacceptable in a CSV document.
Related
New to XSLT and I've been experimenting but want to know if this would be possible.
I want to transform some XML to .csv
The crux of the problem is that I want to create a numeric id for each selected element and then re-use that id for said element to link back
Given the following XML:
<root>
<executables>
<executable name="foo">
<executables>
<executable name="bar"></executable>
</executables>
</executable>
</executables>
<constraints>
<constraint name="baz" from="foo" to="bar"></constraint>
</constraints>
</root>
I'd like the result to be something along the lines of:
id,type,name,from,to
1,executable,foo,,
2,executable,bar,,
3,constraint,baz,1,2
Is this even possible?
Here is my starting XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" indent="no"/>
<xsl:template match="text()" />
<xsl:template match="/">
<xsl:text>id,type,name,from,to
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="executables">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="constraints">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="executable">
<xsl:number format="1" level="any"/>,executable,<xsl:value-of select="#name" /><xsl:text>,,
</xsl:text>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="constraint">
<xsl:number format="1" level="any"/>,constraint,<xsl:value-of select="#name" />,<xsl:value-of select="#from" />,<xsl:value-of select="#to" /><xsl:text>
</xsl:text>
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
which gives this result:
id,type,name,from,to
1,executable,foo,,
2,executable,bar,,
1,constraint,baz,foo,baz
So I basically need to use the <xsl:number> matched by the attribute #name, which will be unique. Also the number isn't quite right; it counted from 1 again for the constraint match.
For the two <xsl:number format="1" level="any"/> I think you want <xsl:number count="executable | constraint" format="1" level="any"/>.
For the references set up a key <xsl:key name="ref" match="executable" use="#name"/> and then instead of the <xsl:value-of select="#from" /> use e.g. <xsl:apply-templates select="key('ref', #from)" mode="number"/> and set up
<xsl:template match="executable" mode="number">
<xsl:number level="any"/>
</xsl:template>
If the constraint elements can also be referenced then use match="executable | constraint" in the key declaration and also <xsl:number count="executable | constraint" level="any"/> in that template.
And for the <xsl:value-of select="#to" /> you use <xsl:apply-templates select="key('ref', #to)" mode="number"/>.
https://xsltfiddle.liberty-development.net/gWvjQgk
I would use actual generated ids, as mentioned in your title, instead of trying to produce sequential numbering:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" indent="no"/>
<xsl:strip-space elements="*"/>
<xsl:key name="exe-by-name" match="executable" use="#name" />
<xsl:template match="/root">
<xsl:text>id,type,name,from,to
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="executable">
<xsl:value-of select="generate-id()" />
<xsl:text>,executable,</xsl:text>
<xsl:value-of select="#name" />
<xsl:text>,,
</xsl:text>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="constraint">
<xsl:value-of select="generate-id()" />
<xsl:text>,constraint,</xsl:text>
<xsl:value-of select="#name" />
<xsl:text>,</xsl:text>
<xsl:value-of select="generate-id(key('exe-by-name', #from))" />
<xsl:text>,</xsl:text>
<xsl:value-of select="generate-id(key('exe-by-name', #to))" />
<xsl:text>
</xsl:text>
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
Demo (using corrected XML): https://xsltfiddle.liberty-development.net/gWvjQgk/1
I would like to get the distinct values inside of a for loop, or within some group. Since the xsl:key can only be declared at the top level, how would I be able to make a xsl:key for each group? In the example below, the group would be the most outer fruit tags. Note that there's also a xsl:sort. If there is a way to accomplish this by just xpaths (preceding-sibling), I would love to know this solution as well. I'm not sure if I would need to use the Muenchian method to accomplish this, but this is what I have:
Input.xml
<root>
<fruits>
<fruit>
<fruit id="2">
<banana><taste>Yummy</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
<fruit id="1">
<banana><taste>Eh</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
</fruit>
<fruit>
<fruit id="2">
<banana><taste>Yummy</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
<fruit id="1">
<banana><taste>Amazing</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
</fruit>
</fruits>
</root>
Transform.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:key name="taste" use="." match="taste" />
<xsl:template match="root">
<xsl:apply-templates select="fruits" />
</xsl:template>
<xsl:template match="fruits">
<xsl:element name="newFruits">
<xsl:call-template name="test" />
</xsl:element>
</xsl:template>
<xsl:template name="test">
<xsl:for-each select="fruit">
<xsl:sort select="fruit/#id" />
<xsl:element name="newFruit">
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste',.)[1])]/..">
<xsl:element name="fruit">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output (comments in the output is the desired tags that should appear)
<?xml version="1.0" encoding="UTF-8"?>
<newFruits>
<newFruit>
<fruit>Yummy</fruit>
<fruit>Disgusting</fruit>
<fruit>Eh</fruit>
</newFruit>
<newFruit>
<!-- <fruit>Yummy</fruit> -->
<!-- <fruit>Disgusting</fruit> -->
<fruit>Amazing</fruit>
</newFruit>
</newFruits>
The issue is that you want your taste elements to be distinct per each top-level fruit element. Your current grouping is getting the distinct elements for the whole document.
If you can't update to XSLT 2.0 then shed a tear, as you have to then use a concatenated key in XSLT 1.0, to include a unique identifier for the relevant fruit element, which can be achieved by using generate-id()
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
Then, in your "test" template, define a variable to hold the id for the relevant fruit...
<xsl:variable name="id" select="generate-id()" />
And your expression to get the distinct tastes becomes this...
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes" />
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
<xsl:template match="root">
<xsl:apply-templates select="fruits" />
</xsl:template>
<xsl:template match="fruits">
<newFruits>
<xsl:call-template name="test" />
</newFruits>
</xsl:template>
<xsl:template name="test">
<xsl:for-each select="fruit">
<xsl:variable name="id" select="generate-id()" />
<newFruit>
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
<fruit>
<xsl:value-of select="."/>
</fruit>
</xsl:for-each>
</newFruit>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note, you don't really need the first template, and I can't see the point of a named template, so you can simplify the above XSLT to this...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes" />
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
<xsl:template match="fruits">
<newFruits>
<xsl:apply-templates select="fruit" />
</newFruits>
</xsl:template>
<xsl:template match="fruit">
<xsl:variable name="id" select="generate-id()" />
<newFruit>
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
<fruit>
<xsl:value-of select="."/>
</fruit>
</xsl:for-each>
</newFruit>
</xsl:template>
</xsl:stylesheet>
Can any guide me to split the given xml element values into multiple child elements based on a token. Here is my sample input xml and desired output. I have a limitation to use xsl 1.0. Thank you.
Input XML:
<?xml version='1.0' encoding='UTF-8'?>
<SQLResults>
<SQLResult>
<ACTION1>Action1</ACTION1>
<ACTION2>Action2</ACTION2>
<Encrypt>Program=GPG;Code=23FCS;</Encrypt>
<SENDER>Program=WebPost;Protocol=WS;Path=/home/Inbound</SENDER>
</SQLResult>
</SQLResults>
Output XML:
<?xml version='1.0' encoding='UTF-8'?>
<SQLResults>
<SQLResult>
<ACTION1>Action1</ACTION1>
<ACTION2>Action2</ACTION2>
<Encrypt>
<Program>GPG</Program>
<Code>23FCS</Code>
</Encrypt>
<SENDER>
<Program>Action4</Program>
<Protocol>WS</Protocol>
<Path>/home/Inbound</Path>
</SENDER>
</SQLResult>
</SQLResults>
In XSLT 2 it would be easy, just with the following template:
<xsl:template match="Encrypt|SENDER">
<xsl:copy>
<xsl:analyze-string select="." regex="(\w+)=([\w/]+);?">
<xsl:matching-substring>
<element name="{regex-group(1)}">
<xsl:value-of select="regex-group(2)"/>
</element>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:copy>
</xsl:template>
Because you want to do it in XSLT 1, you have to express it another way.
Instead of analyze-string you have to:
Tokenize the content into non-empty tokens contained between ; chars.
You have to add tokenize template.
Each such token divide into 2 substrings, before and after = char.
Create an element with the name equal to the first substring.
Write the content of this element - the second substring.
XSLT 1 has also such limitation that the result of the tokenize template
is a result tree fragment (RTF) not the node set and thus it cannot be
used in XPath expressions.
To circumvent this limitation, you must use exsl:node-set function.
So the whole script looks like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Encrypt|SENDER">
<xsl:copy>
<xsl:variable name="tokens">
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="."/>
<xsl:with-param name="delim" select="';'"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="exsl:node-set($tokens)/token">
<xsl:variable name="t1" select="substring-before(., '=')"/>
<xsl:variable name="t2" select="substring-after(., '=')"/>
<xsl:element name="{$t1}">
<xsl:value-of select="$t2" />
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="txt" />
<xsl:param name="delim" select="' '" />
<xsl:choose>
<xsl:when test="$delim and contains($txt, $delim)">
<token>
<xsl:value-of select="substring-before($txt, $delim)" />
</token>
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="substring-after($txt, $delim)" />
<xsl:with-param name="delim" select="$delim" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$txt">
<token><xsl:value-of select="$txt" /></token>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
As part of an XSLT, I need to add all the values of the "Duration" element and display the value. Now, the below XML is a part of the larger XML I'm working on. In the below XML, I need to match
a/TimesheetDuration/Day*/Duration, add the values and display them. I dont want to store all the values in variables and add them. Is there any other clean way of doing this?
<?xml version="1.0" ?>
<a>
<TimesheetDuration>
<Day1>
<BusinessDate>6/12/2013</BusinessDate>
<Duration>03:00</Duration>
</Day1>
<Day2>
<BusinessDate>6/13/2013</BusinessDate>
<Duration>04:00</Duration>
</Day2>
<Day3>
<BusinessDate>6/14/2013</BusinessDate>
<Duration>05:00</Duration>
</Day3>
</TimesheetDuration>
</a>
An XPath 2.0 solution, assuming the durations are in the form HH:MM, would be
sum(for $d in a//Duration
return xs:dayTimeDuration(replace($d, '(..):(..)', 'PT$1H$2M')))
In xslt 1.0 you could do it for example with following 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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<Durations>
<xsl:apply-templates select="a/TimesheetDuration/node()[starts-with(name(),'Day')]" />
<xsl:variable name="hours">
<xsl:call-template name="sumHours">
<xsl:with-param name="Day" select="a/TimesheetDuration/node()[starts-with(name(),'Day')][1]" />
</xsl:call-template>
</xsl:variable>
<SumOfHours>
<xsl:value-of select="$hours" />
</SumOfHours>
<!-- Sum of minutes would be calculated similarly -->
</Durations>
</xsl:template>
<xsl:template match="node()[starts-with(name(),'Day')]">
<xsl:copy-of select="Duration" />
</xsl:template>
<xsl:template name="sumHours">
<xsl:param name="tmpSum" select="0" />
<xsl:param name="Day" />
<xsl:variable name="newTmpSum" select="$tmpSum + substring-before($Day/Duration, ':')" />
<xsl:choose>
<xsl:when test="$Day/following-sibling::node()[starts-with(name(),'Day')]">
<xsl:call-template name="sumHours">
<xsl:with-param name="tmpSum" select="$newTmpSum" />
<xsl:with-param name="Day" select="$Day/following-sibling::node()[starts-with(name(),'Day')]" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$newTmpSum" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
It produces output
<?xml version="1.0" encoding="UTF-8"?>
<Durations>
<Duration>03:00</Duration>
<Duration>04:00</Duration>
<Duration>01:00</Duration>
<SumOfHours>8</SumOfHours>
</Durations>
I have a pretty flat XML structure that I need to reorder into categorised sections and, for the life of me, I can't figure out how to do it in XSLT (not that I'm by any means an expert.)
Basically, the original XML looks kinda like:
<things>
<thing>
<value>one</value>
<type>a</type>
</thing>
<thing>
<value>two</value>
<type>b</type>
</thing>
<thing>
<value>thee</value>
<type>b</type>
</thing>
<thing>
<value>four</value>
<type>a</type>
</thing>
<thing>
<value>five</value>
<type>d</type>
</thing>
</things>
And I need to output something like:
<data>
<a-things>
<a>one</a>
<a>four</a>
</a-things>
<b-things>
<b>two</b>
<b>three</b>
</b-things>
<d-things>
<d>five</d>
</d-things>
</data>
Note that I can't output <c-things> if there aren't any <c> elements, but I do know ahead of time what the complete list of types is, and it's fairly short so handcoding templates for each type is definitely possible. It feels like I could probably hack something together using <xsl:if> and <xsl:for-each> but it also feels like there must be a more ... 'templatey' way to do it. Can anyone help?
Cheers.
As you are using Saxon, use the native XSLT 2.0 grouping.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="things">
<data>
<xsl:for-each-group select="thing" group-by="type">
<xsl:element name="{concat(current-grouping-key(),'-things')}">
<xsl:for-each select="current-group()">
<xsl:element name="{current-grouping-key()}">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</data>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0 you can group with keys. This approach is called Muenchian Grouping.
The xsl:key defines an index containing thing elements, grouped by the string value of their type element. Function key() returns all nodes from the key with the specified value.
The outer xsl:for-each selects the thing elements that are the first returned by key() for their value.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="thing" match="thing" use="type" />
<xsl:template match="things">
<data>
<xsl:for-each select="thing[generate-id(.)=generate-id(key('thing',type)[1])]">
<xsl:element name="{concat(type,'-things')}">
<xsl:for-each select="key('thing',type)">
<xsl:element name="{type}">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>
The generic solution is to use an XSL key:
<xsl:key name="kThingByType" match="thing" use="type" />
<xsl:template match="things">
<xsl:copy>
<xsl:apply-templates select="thing" mode="group">
<xsl:sort select="type" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="thing" mode="group">
<xsl:variable name="wholeGroup" select="key('kThingByType', type)" />
<xsl:if test="generate-id() = generate-id($wholeGroup[1])">
<xsl:element name="{type}-thing">
<xsl:copy-of select="$wholeGroup/value" />
</xsl:element>
</xsl:if>
</xsl:template>
The above yields:
<things>
<a-thing>
<value>one</value>
<value>four</value>
</a-thing>
<b-thing>
<value>two</value>
<value>thee</value>
</b-thing>
<d-thing>
<value>five</value>
</d-thing>
</things>
In XSLT 2, you can do this very elegantly. Say you have a template for formatting each thing before it is wrapped in an <a> element:
<xsl:template match="thing" mode="format-thing">
<xsl:value-of select="value/text()"/>
</xsl:template>
Then you can apply that to each thing of some $type to build the <a-things> elements via a function:
<xsl:function name="my:things-group" as="element()">
<xsl:param name="type" as="xs:string"/>
<xsl:param name="things" as="element(thing)*"/>
<xsl:element name="{ concat($type, '-things') }">
<xsl:for-each select="$things[type/text() eq $type]">
<xsl:element name="{ $type }">
<xsl:apply-templates select="." mode="format-thing"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:function>
Then you can call that function for each unique type (a, b, d in your sample input) to build the entire output and you're done:
<xsl:template match="/">
<data>
<xsl:sequence select="
for $type in distinct-values(things/thing/type/text())
return my:things-group($type, /things/thing)
"/>
</data>
</xsl:template>
Of course, asking the question made it obvious...
My solution does use an <xsl:if>, but I can't see how it couldn't now I think about it. My solution looks basically like:
<xsl:if test="/things/thing/type = 'a'">
<a-things>
<xsl:apply-templates select="/things/thing[type='a']" mode="a" />
</a-things>
</if>
<xsl:template match="/things/thing[type='a']" mode="a">
<a><xsl:value-of select="value"/>
</xsl:template>
And repeat for the other types. I've coded it up, and it seems to work just fine.
<a-things>
<xsl:for-each select="thing[type = 'a']">
<a><xsl:value-of select="./value" /></a>
</xsl:for-each>
</a-things>
If you want to get really snazzy, replace the <a-things> and the predicate with parameters and use attribute value templates:
<xsl:param name="type" />
<xsl:element name="{$type}-things">
<xsl:for-each select="thing[type = $type]">
<xsl:element name="{$type}"><xsl:value-of select="./value" /></xsl:element>
</xsl:for-each>
</xsl:element>
And using grouping, you can do it without the if:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="things">
<data>
<xsl:for-each select="thing[not(type=preceding-sibling::thing/type)]">
<xsl:variable name="type"><xsl:value-of select="type" /></xsl:variable>
<xsl:element name="concat($type, '-things')">
<xsl:for-each select="../thing[type=$type]">
<xsl:element name="$type">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>