I'm quite new to XSLT and I was trying to copy an existent XML file I already have but with the elements reordered but I got stuck when trying to reorder grandchildren.
Let's say I have this input:
<grandParent>
<parent>
<c>789</c>
<b>
<b2>123</b2>
<b1>456</b1>
</b>
<a>123</a>
</parent>
....
</grandParent>
What I want to do is get the same XML file but changing the order of the tags to be a,b,c with b = b1, b2 in that order.
So I started with the XSLT file:
<xsl:template match="node()|#*"> <- This should copy everything as it is
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent"> <- parent elements will copy in this order
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="a"/>
<xsl:copy-of select="b"/>
<xsl:copy-of select="c"/>
</xsl:copy>
</xsl:template>
But "xsl:copy-of select="b"" copies the elements as they are specified (b2, b1).
I tried using another xsl:template for "grandParent/parent/b" but wouldn't help.
Maybe I'm not doing things the correct way... Any tips?
Thanks!
SOLUTION - Thanks to Nils
Your solution works just fine Nils, I just customized it a bit more to fit in my current scenario where "b" is optional and the names of the tags might not be correlative.
The final code is like this:
<xsl:template match="node()|#*"> <- This should copy everything as it is
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent"> <- parent elements will copy in this order
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="a"/>
<xslt:if test="b">
<b>
<xsl:copy-of select="b1"/>
<xsl:copy-of select="b2"/>
</b>
</xslt:if>
<xsl:copy-of select="b"/>
<xsl:copy-of select="c"/>
</xsl:copy>
</xsl:template>
I tried using another xsl:template for "grandParent/parent/b" but wouldn't help.
Since you have an identity template you should use <xsl:apply-templates> instead of <xsl:copy-of>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="a"/>
<xsl:apply-templates select="b"/>
<xsl:apply-templates select="c"/>
</xsl:copy>
</xsl:template>
Now you can add a similar template for the b elements
<xsl:template match="parent/b">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="b1"/>
<xsl:apply-templates select="b2"/>
</xsl:copy>
</xsl:template>
This will nicely handle the case where b doesn't exist - if the select="b" doesn't find any elements, then no templates will fire.
In fact, if the sort order is the same in both cases (alphabetically by element name) then you can combine the two templates into one which uses <xsl:sort>, giving a complete transformation of
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*" />
<xsl:output method="xml" indent="yes" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent | parent/b">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*">
<xsl:sort select="name()" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
(for the example XML you've given you don't actually need the #* bits because the XML doesn't include any attributes, but it won't do any harm to leave it there in case there are any in the real XML or you add any in future).
Using xsl:sort.
The following code is off the top of my head and might not work; the thought behind it should be clear though.
<xsl:template match="node()|#*"> <- This should copy everything as it is
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="grandParent/parent"> <- parent elements will copy in this order
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="a"/>
<b>
<xsl:for-each select="b/*">
<xsl:sort select="text()" />
<xsl:copy-of select="." />
</xsl:for-each>
</b>
<xsl:copy-of select="c"/>
</xsl:copy>
</xsl:template>
Here is a most generic solution -- using xsl:sort and templates -- no xsl:copy-of and no hardcoding of specific names:
<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:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<grandParent>
<parent>
<c>789</c>
<b>
<b2>123</b2>
<b1>456</b1>
</b>
<a>123</a>
</parent>
....
</grandParent>
the wanted, correct result is produced:
<grandParent>
<parent>
<a>123</a>
<b>
<b1>456</b1>
<b2>123</b2>
</b>
<c>789</c>
</parent>
....
</grandParent>
Now, let's change all the names in the XML document -- note that none of the other answers works with this:
<someGrandParent>
<someParent>
<z>789</z>
<y>
<y2>123</y2>
<y1>456</y1>
</y>
<x>123</x>
</someParent>
....
</someGrandParent>
We apply the same transformation and it again produces the correct result:
<someGrandParent>
<someParent>
<x>123</x>
<y>
<y1>456</y1>
<y2>123</y2>
</y>
<z>789</z>
</someParent>
....
</someGrandParent>
Related
I have a problem. Cant get to copy only node with specific attribute in XSLT. My XML;
<root>
<mouse code="red"> .. </mouse>
<mouse code="blue"> .. </mouse>
<mouse code="green"> .. </mouse>
</root>
I am trying to copy only node with red attribute to get XML like this:
<root>
<mouse code="red"> .. </mouse>
</root>
Can this be done with simple XSLT transformation?
DeLuka
You can do simply:
<xsl:template match="/root">
<xsl:copy>
<xsl:copy-of select="mouse[#code='red']"/>
</xsl:copy>
</xsl:template>
This can be done simply with xsl:templates matching the node you want and using copy/copy-of.
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="mouse[#code='red']">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
Of course, if there are other elements within mouse[#code='red'] then you'll need to handle them with another template.
There are many questions about how to remove duplicate elements when you can group those elements by a certain attribute or value, however, in my case the attributes are being dynamically generated in the XSLT already and I don't want to have to program in every attribute for every element to use as a grouping key.
How do you remove duplicate elements without knowing in advance their attributes? So far, I've tried using generate-id() on each element and grouping by that, but the problem is generate-id isn't generating the same ID for elements with the same attributes:
<xsl:template match="root">
<xsl:variable name="tempIds">
<xsl:for-each select="./*>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="tempID">
<xsl:value-of select="generate-id(.)"/>
</xsl:attribute>
<xsl:copy-of select="node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<xsl:for-each-group select="$tempIds" group-by="#tempID">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:template>
Test data:
<root>
<child1>
<etc/>
</child1>
<dynamicElement1 a="2" b="3"/>
<dynamicElement2 c="3" d="4"/>
<dynamicElement2 c="3" d="5"/>
<dynamicElement1 a="2" b="3"/>
</root>
With the end result being only one of the two dynamicElement1 elements remaining:
<root>
<child1>
<etc/>
</child1>
<dynamicElement1 a="2" b="3"/>
<dynamicElement2 c="3" d="4"/>
<dynamicElement2 c="3" d="5"/>
</root>
In XSLT 3 as shown in https://xsltfiddle.liberty-development.net/pPqsHTi you can use a composite key of all attributes with e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" composite="yes" group-by="#*">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that technically attributes are not ordered so it might be safer to group by a sort of the attributes by node-name() or similar, as done with XSLT 3 without higher-order functions in https://xsltfiddle.liberty-development.net/pPqsHTi/2
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mf="http://example.com/mf"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:function name="mf:node-sort" as="node()*">
<xsl:param name="input-nodes" as="node()*"/>
<xsl:perform-sort select="$input-nodes">
<xsl:sort select="namespace-uri()"/>
<xsl:sort select="local-name()"/>
</xsl:perform-sort>
</xsl:function>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" composite="yes" group-by="mf:node-sort(#*)">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
or as you could do with Saxon EE simply with
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" composite="yes" group-by="sort(#*, (), function($att) { namespace-uri($att), local-name($att) })">
<xsl:sequence select="."/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root/*[#a= following-sibling::*/#a]|root/*[#c= following-sibling::*/#c and #d= following-sibling::*/#d]"/>
You may try this
I have the following xml:
<ns0:Root xmlns:ns0="http://root" xmlns:nm="http://notroot">
<nm:MSH>
<content>bla</content>
</nm:MSH>
<ns0:Second>
<ns0:item>aaa</ns0:item>
</ns0:Second>
<ns0:Third>
<ns0:itemb>vv</ns0:itemb>
</ns0:Third>
</ns0:Root>
That is my expected result:
<Root xmlns="http://root" xmlns:nm="http://notroot">
<nm:MSH>
<content>bla</content>
</nm:MSH>
<Second>
<item>aaa</item>
</Second>
<Third>
<itemb>vv</itemb>
</Third>
</Root>
I need to write an xslt 1.0 that perform that map.
I really doesn't have a clue how to do it, thus it seems pretty simple.
Can anyone please help ?
To move the elements from xmlns:ns0="http://root" namespace to default namespace.
Use:
<xsl:template match="ns0:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
To make http://root the default namespace add xmlns="http://root"to the stylesheet declaration.
Therefore you may try something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ns0="http://root"
xmlns="http://root"
exclude-result-prefixes="ns0" >
<!-- Identity transform (e.g. http://en.wikipedia.org/wiki/Identity_transform#Using_XSLT -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- match root element and fore notroot namespace to this -->
<xsl:template match="/*">
<Root xmlns:nm="http://notroot">
<xsl:apply-templates select="#*|node()" />
</Root>
</xsl:template>
<xsl:template match="content">
<content>
<xsl:apply-templates select="#*|node()" />
</content>
</xsl:template>
<!-- move attributes with prefix ns0 to default namespace -->
<xsl:template match="#ns0:*">
<xsl:attribute name="newns:{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!-- move elements with prefix ns0 to default namespace -->
<xsl:template match="ns0:*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The only difference between your input and output is that the content element inside nm:MSH has moved from being in no namespace to being in the http://root namespace. This can be handled by an identity transformation with tweaks:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:ns0="http://root">
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="content">
<ns0:content><xsl:apply-templates select="#*|node()" /></ns0:content>
</xsl:template>
</xsl:stylesheet>
I was looking for some guidance on how best to approach my issue.
I have an XML document like the following but on a larger scale.
<NewDataSet>
<Table Attri1="Attri1Val" Attri2="Attri2Val" Attri3="Attri3Val" Attri4="Attri4Val" Attri5="Attri5Val" Attri6="Attri6Val" Attri7="Attri7" />
</NewDataSet>
I need to move certain attributes from the Table node, for example Attri2 and Attri5 into elements within the Table node, however I need to leave the rest of the attributes as they are.
What would be the best way to approach this? The data scale is about 3-4 times that shown.
EDIT:
Expected output:
<NewDataSet>
<Table Attri1="Attri1Val" Attri3="Attri3Val" Attri4="Attri4Val" Attri6="Attri6Val" Attri7="Attri7">
<Attri2>Attri2Val</Attri2>
<Attri5>Attri5Val</Attri5>
</Table>
</NewDataSet>
The complexity is not really the issue, more the scale of the data and what is the best way to deal with it.
Use
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Table">
<xsl:copy>
<xsl:apply-templates select="#*[not(name() = 'Attri2') and not(name() = 'Attri5')]"/>
<xsl:apply-templates select="#Attri2 | #Attri5 | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Table/#Attri2 | Table/#Attri5">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
[edit]
The name comparison of the attributes is a bit ugly but will probably do for your sample. What we really need is #* execpt (#Attri2, #Attri5), only that is XPath 2.0. With XPath 1.0 the equivalent is
<xsl:template match="Table">
<xsl:copy>
<xsl:variable name="all-attributes" select="#*"/>
<xsl:variable name="to-be-transformed" select="#Attri2 | #Attri5"/>
<xsl:apply-templates select="$all-attributes[count(. | $to-be-transformed) != count($to-be-transformed)]"/>
<xsl:apply-templates select="$to-be-transformed | node()"/>
</xsl:copy>
</xsl:template>
This generic transformation can handle any set of attributes, with any length, whose names can be specified externally to the transformation:
<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:param name="pToTransform" select="'|Attri2|Attri5|'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Table">
<xsl:copy>
<xsl:apply-templates select=
"#*[not(contains($pToTransform, concat('|',name(),'|')))] | node()"/>
<xsl:apply-templates mode="makeElement"
select="#*[contains($pToTransform, concat('|',name(),'|'))]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*" mode="makeElement">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the provided XML document:
<NewDataSet>
<Table Attri1="Attri1Val" Attri2="Attri2Val"
Attri3="Attri3Val" Attri4="Attri4Val"
Attri5="Attri5Val" Attri6="Attri6Val"
Attri7="Attri7" />
</NewDataSet>
the wanted, correct result is produced:
<NewDataSet>
<Table Attri1="Attri1Val" Attri3="Attri3Val" Attri4="Attri4Val" Attri6="Attri6Val" Attri7="Attri7">
<Attri2>Attri2Val</Attri2>
<Attri5>Attri5Val</Attri5>
</Table>
</NewDataSet>
I have an XML file from which I need to delete an attribute with name "Id" (It must be deleted wherever it appears) and also I need to rename the parent tag, while keeping its attributes and child elements unaltered .. Can you please help me modifying the code. At a time, am able to achieve only one of the two requirements .. I mean I can delete that attribute completely from the document or I can change the parent tag ..
Here is my code to which removes attribute "Id":
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#Id[parent::*]">
</xsl:template>
Please help me changing the parent tag name from "Root" to "Batch".
None of the offered solutions really solves the problem: they simply rename an element named "Root" (or even just the top element), without verifying that this element has an "Id" attribute.
wwerner is closest to a correct solution, but renames the parent of the parent.
Here is a solution that has the following properties:
It is correct.
It is short.
It is generalized (the replacement name is contained in a variable).
Here is the code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vRep" select="'Batch'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#Id"/>
<xsl:template match="*[#Id]">
<xsl:element name="{$vRep}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()|text()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#Id" />
<xsl:template match="Root">
<Batch>
<xsl:copy-of select="#*|*|text()" />
</Batch>
</xsl:template>
This should do the job:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()|text()" />
</xsl:copy>
</xsl:template>
<xsl:template match="node()[node()/#Id]">
<batch>
<xsl:apply-templates select='#*|*|text()' />
</batch>
</xsl:template>
<xsl:template match="#Id">
</xsl:template>
</xsl:stylesheet>
I tested with the following XML input:
<root anotherAttribute="1">
<a Id="1"/>
<a Id="2"/>
<a Id="3" anotherAttribute="1">
<b Id="4"/>
<b Id="5"/>
</a>
I would try:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#Id">
</xsl:template>
<xsl:template match="/Root">
<Batch>
<xsl:apply-templates select="#*|node()"/>
</Batch>
</xsl:template>
The first block copies all that is not specified, as you use.
The second replaces #id with nothing wherever is occurs.
The third renames /Root to /Batch.