How do I multiple nest the following xml using xsl 2.0 based on level1 (outer), level2(middle), level3(inner)?
<rootNode>
<fruits>
<fruit>
<level1>4</level1>
<level2/>
<level3/>
<kind>orange</kind>
<size>big</size>
<origin>california</origin>
</fruit>
<fruit>
<level1>4</level1>
<level2>2</level2>
<level3/>
<kind>lemon</kind>
<size>small</size>
<origin>florida</origin>
</fruit>
<fruit>
<level1>4</level1>
<level2>2</level2>
<level3>1</level3>
<kind>pineapple</kind>
<size>normal</size>
<origin>oregon</origin>
</fruit>
<fruit>
<level1>5</level1>
<level2>2</level2>
<level3/>
<kind>pineapple</kind>
<size>normal</size>
<origin>oregon</origin>
</fruit>
<fruit>
<level1>5</level1>
<level2>1</level2>
<level3/>
<kind>peer</kind>
<size>big</size>
<origin>ohio</origin>
</fruit>
</fruits>
</rootNode>
I can do it for level1 using the following xslt
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/rootNode/fruits">
<xsl:for-each-group select="fruit" group-by="level1">
<level1 name="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</level1>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However, using the output from it and doing it again for lavel2 doesn't work. Recursion function? How to write it?
As your title says, you need to nest two xsl:for-each-group instructions, one inside the other:
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:template match="/rootNode">
<xsl:for-each-group select="fruits/fruit" group-by="level1">
<level1 name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="level2">
<level2 name="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</level2>
</level1>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/bdxtre
Related
I am trying to figure out the XSLT (CSV output) for the below XML.
I would like to sort the grandchildren nodes i.e. sort Month node and pick the Sales_Program-ID node value of the set with the highest month value.
XML
<Root>
<Level1>
<EMPLID>123</EMPLID>
<Program>
<Sales_Program Name="XYZ">
<ID1>ab</ID1>
</Sales_Program>
<Start_Date>Jan1st</Start_Date>
**<Month>1</Month>**
</Program>
<Program>
<Sales_Program Name="ABC">
<ID1>cd</ID1>
</Sales_Program>
<Start_Date>Feb1</Start_Date>
**<Month>2</Month>**
</Program>
</Level1>
<Level1>
<EMPLID>456</EMPLID>
<Program>
<Sales_Program Name="XYZ">
<ID1>ab</ID1>
</Sales_Program>
<Start_Date>Jan1st</Start_Date>
<Month>1</Month>
</Program>
</Level1>
</Root>
Expected Output:
123,ab,Feb1,2 - (From first Level1 Node)
456,cd,Jan1st,1 (From second Level1 Node)
So you want to sort the Program elements by the Month child, in XSLT 3 with support for the higher-order sort function you can do that with
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="Root/Level1"/>
</xsl:template>
<xsl:template match="Level1">
<xsl:value-of select="EMPLID, sort(Program, (), function($p) { -$p/Month/xs:integer(.) })[1]/(Sales_Program/ID1, Start_Date, Month)" separator=","/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
in XSLT 2 you need to implement the sorting in your own function with xsl:perform-sort:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf"
version="2.0">
<xsl:output method="text"/>
<xsl:function name="mf:sort">
<xsl:param name="programs" as="element(Program)*"/>
<xsl:perform-sort select="$programs">
<xsl:sort select="xs:integer(Month)" order="descending"/>
</xsl:perform-sort>
</xsl:function>
<xsl:template match="/">
<xsl:apply-templates select="Root/Level1"/>
</xsl:template>
<xsl:template match="Level1">
<xsl:value-of select="EMPLID, mf:sort(Program)[1]/(Sales_Program/ID1, Start_Date, Month)" separator=","/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
I have to copy all the sets where the mail end with "test.com", if there count is greater than 5. I've tried several things, but nothing seems to work.
How can I do this with xslt 1.0?
<root>
<sets>
<set>
<mail>a#test.com</mail>
<foo/>
</set>
<set>
<mail>a#test.net</mail>
<foo/>
</set>
<set>
<mail>b#test.com</mail>
<foo/>
</set>
</sets>
</root>
For example I tried this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:if test="count(/root/sets/set[mail = '*test.com'])">
<root>
<sets>
<xsl:for-each select="/root/sets/set">
<xsl:if test="contains(./mail, 'test.com')">
<xsl:copy-of select="./*"/>
</xsl:if>
</xsl:for-each>
</sets>
</root>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I have to copy all the sets where the mail end with "test.com".
How about:
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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="sets">
<xsl:copy>
<xsl:apply-templates select="set[substring-after(mail, '#')='test.com']"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I want to compare the attributes of two xml-Files and identity transform the input file in the same step. The output xml should only contain elements whose attributes occur in the comparing xml. As shown in the given example, the last concept node should not be outputted, as there is no matching attribute in the comparing.xml
input.xml
<navigation
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<facets>
<facet id="d1e12000000000000000000000011111">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
<concept id="d1e12000000000000000000000000000">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
<concept id="d1e19000000000000000000000000000">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
</concepts>
</concept>
</concepts>
</concept>
</concepts>
</facet>
</facets>
part of comparing.xml with indefinite heading-levels
<foo>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">Myheading</heading>
<chapter>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">myheading</heading>
<operation>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">another heading</heading>
</operation>
</chapter>
desired output.xml with only applicable id's
<nav:navigation
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nav="http://www.nav.de/">
<nav:facets>
<nav:facet id="d1e12000000000000000000000011111">
<nav:title xml:lang="en">sometxt</nav:title>
<nav:title xml:lang="de">eintxt</nav:title>
<nav:concepts>
<nav:concept id="d1e12000000000000000000000000000">
<nav:title xml:lang="en">sometxt</nav:title>
<nav:title xml:lang="de">eintxt</nav:title>
<nav:concepts>
</nav:concepts>
</nav:concept>
</nav:concepts>
</nav:facet>
</nav:facets>
my xsl so far
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nav="http://www.nav.de/"
version="2.0" >
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:variable name="docu" select="document(comparing.xml)"/>
<xsl:template match="*">
<xsl:element name="nav:{name()}" namespace="http://www.nav.de/">
<xsl:copy-of select="namespace::*"/>
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
EDIT: sorry for posting this in the comment-section. I've tried something along those lines, but it didn't work
<xsl:template match="concept | facet">
<xsl:variable name="foo-id" select="#id"/>
<xsl:for-each select="$docu//heading">
<xsl:if test="contains(./#class, $foo-id)">
<xsl:apply-templates/>
</xsl:if>
</xsl:for-each>
</xsl:template>
I would suggest you try it this way:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:nav="http://www.nav.de/">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="comparing-url" select="'comparing.xml'"/>
<xsl:key name="comp" match="#class" use="tokenize(., '\|')" />
<xsl:template match="*">
<xsl:element name="nav:{name()}" >
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="*[#id][not(key('comp', #id, document($comparing-url)))]"/>
</xsl:stylesheet>
I wanted to extract leaf nodes and have them sorted.
My XSL gives unexpected results. How can I solve this?
Input
<root>
<b>
<b33 zzz="2" fff="3"></b33>
<b11></b11>
<b22></b22>
</b>
<a>
<a27></a27>
<a65 fff="0" eee="2" zzz="10"></a65>
<a11></a11>
</a>
</root>
Xsl
<?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" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:call-template name="leafnodes"/>
</root>
</xsl:template>
<xsl:template match="*[not(*)]|#*" name="leafnodes">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
<xsl:apply-templates select="#*">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output (I would expected it to be sorted, it is not)
<root>
<b33 fff="3" zzz="2" />
<b11 />
<b22 />
<a27 />
<a65 eee="2" fff="0" zzz="10" />
<a11 />
</root>
I would expect the nodes in the order a11, a27, a65, b11, b22, b33.
If I leave out the '[not(*)]', the xsl takes all nodes and sorts them properly.
How can this be solved?
To output all element which have no child sorted by name and the attributes also sorted by name. Try this;
<?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" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="//*[not(*)]">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates select="#*" >
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Which will generate following output:
<root>
<a11/>
<a27/>
<a65 eee="2" fff="0" zzz="10"/>
<b11/>
<b22/>
<b33 fff="3" zzz="2"/>
</root>
i am trying to paas the dynamic parameter while calling the template to suppress nodes from the xml.
I would call this template like:
transform employee.xml suppress.xsl ElementsToSuppress=id,fname
employee.xml
<?xml version="1.0" encoding="utf-8" ?>
<Employees>
<Employee>
<id>1</id>
<firstname>xyz</firstname>
<lastname>abc</lastname>
<age>32</age>
<department>xyz</department>
</Employee>
<Employee>
<id>2</id>
<firstname>XY</firstname>
<lastname>Z</lastname>
<age>21</age>
<department>xyz</department>
</Employee>
</Employees>
Suppress.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0" xmlns:elements="http://localhost">
<elements:name abbrev="id">id</elements:name>
<elements:name abbrev="fname">firstname</elements:name>
<xsl:param name="ElementsToSuppress" ></xsl:param>
<xsl:variable name="tokenizedSample" select="tokenize($ElementsToSuppress,',')"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
<xsl:for-each select="$tokenizedSample">
<xsl:call-template name ="Suppress" >
<xsl:with-param name="parElementName">
<xsl:value-of select="."/>
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="Suppress">
<xsl:param name="parElementName" select="''"></xsl:param>
<xsl:variable name="extNode" select="document('')/*/elements:name[#abbrev=$parElementName]"/>
<xsl:call-template name="test" >
<xsl:with-param name="parElementName" >
<xsl:value-of select="$extNode"/>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="test" match="*[name() = $parElementName]" >
<xsl:param name="parElementName" select="''"></xsl:param>
<xsl:call-template name="SuppressElement" />
</xsl:template>
<xsl:template name="SuppressElement" />
</xsl:stylesheet>
Can we achieve output by using this or some other way? The ideal way is to pass the comma separated abbreviations of nodes and suppress them in one call.
Any help will be appreciated.
Regards,
AB
You don't need XSLT 2.0 for this processing.
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pNodesToSuppress"
select="'id,fname,pi'"/>
<my:toSuppress>
<name abbrev="id">id</name>
<name abbrev="fname">firstname</name>
<name abbrev="pi">somePI</name>
</my:toSuppress>
<xsl:variable name="vToSuppressTable" select=
"document('')/*/my:toSuppress/*"/>
<xsl:variable name="vNamesToSupress">
<xsl:for-each select=
"$vToSuppressTable
[contains(concat(',',$pNodesToSuppress,','),
concat(',',#abbrev, ',')
)
]">
<xsl:value-of select="concat(',',.)"/>
</xsl:for-each>
<xsl:text>,</xsl:text>
</xsl:variable>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()" priority="0.01">
<xsl:if test=
"not(contains($vNamesToSupress,
concat(',',name(),',')
)
)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (with an added processing instruction to demonstrate how we can also delete PIs -- not only elements):
<Employees>
<Employee>
<id>1</id>
<firstname>xyz</firstname>
<lastname>abc</lastname>
<age>32</age>
<department>xyz</department>
</Employee>
<?somePI This PI will be deleted ?>
<Employee>
<id>2</id>
<firstname>XY</firstname>
<lastname>Z</lastname>
<age>21</age>
<department>xyz</department>
</Employee>
</Employees>
produces the wanted, correct results:
<Employees>
<Employee>
<lastname>abc</lastname>
<age>32</age>
<department>xyz</department>
</Employee>
<Employee>
<lastname>Z</lastname>
<age>21</age>
<department>xyz</department>
</Employee>
</Employees>
Do note:
This is a pure XSLT 1.0 transformation.
2.Not only elements, but also processing instructions are deleted, when their name is specified via the external parameter $pNodesToSuppress.
I don't get why the parameter value "fname" would suppress an element called "firstname" but apart from that you might simply want
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:param name="ElementsToSuppress" as="xs:string" select="'id,firstname'"/>
<xsl:variable name="names-to-suppress" as="xs:QName*"
select="for $s in tokenize($ElementsToSuppress, ',') return QName('', $s)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[node-name(.) = $names-to-suppress]"/>
</xsl:stylesheet>
[edit]
I missed that your sample stylesheet seemed to contain a mapping from those parameters to the elements names in the sample input. In that case here is an adapted version of the stylesheet to handle that case:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:data="http://example.com/data"
xmlns:elements="http://example.com/elements"
exclude-result-prefixes="xs data elements"
version="2.0">
<data:data>
<elements:name abbrev="id">id</elements:name>
<elements:name abbrev="fname">firstname</elements:name>
</data:data>
<xsl:key name="e-by-abbrev" match="elements:name" use="#abbrev"/>
<xsl:param name="ElementsToSuppress" as="xs:string" select="'id,fname'"/>
<xsl:variable name="names-to-suppress" as="xs:QName*"
select="for $s in tokenize($ElementsToSuppress, ',') return QName('', key('e-by-abbrev', $s, document('')/xsl:stylesheet/data:data))"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[node-name(.) = $names-to-suppress]"/>
</xsl:stylesheet>
[second edit to implement further request] Here is a sample that also checks the relationship attribute values:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:data="http://example.com/data"
xmlns:elements="http://example.com/elements"
exclude-result-prefixes="xs data elements"
version="2.0">
<xsl:param name="ElementsToSuppress" as="xs:string" select="'id'"/>
<xsl:param name="parVAObjectRelationship" as="xs:string" select="'a,b'"/>
<xsl:variable name="names-to-suppress" as="xs:QName*"
select="for $s in tokenize($ElementsToSuppress, ',') return QName('', key('e-by-abbrev', $s, document('')/xsl:stylesheet/data:data))"/>
<xsl:variable name="att-values-to-suppress" as="xs:string*"
select="tokenize($parVAObjectRelationship, ',')"/>
<data:data>
<elements:name abbrev="id">id</elements:name>
<elements:name abbrev="fname">firstname</elements:name>
</data:data>
<xsl:key name="e-by-abbrev" match="elements:name" use="#abbrev"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#relationship = $att-values-to-suppress]"/>
<xsl:template match="*[node-name(.) = $names-to-suppress]"/>
</xsl:stylesheet>
When applied to
<Employees>
<Employee>
<id>1</id>
<firstname>xyz</firstname>
<lastname relationship="a">abc</lastname>
<age relationship="b">32</age>
<department>xyz</department>
</Employee>
</Employees>
the output is
<Employees>
<Employee>
<firstname>xyz</firstname>
<department>xyz</department>
</Employee>
</Employees>
You might want to add strip-space and output indent="yes" to prevent the blank lines.
If you are using the old Saxon-B or newer Saxon-PE or Saxon-EE as XSLT processor, you can use a saxon extension to achieve dynamic template calls:
<saxon:call-template name="{$templateName}"/>
Don't forget to declare the saxon-Namespace in xsl-stylesheet element:
<xsl:stylesheet xmlns:saxon="http://saxon.sf.net/" [...] >