Add attribute to a DOM NodeList node using XSLT - xslt

I have to add one attribute Publisher="Penguin" to the nodes from a NodeList : The input xml looks like:
<Rack RackNo="1">
<Rows>
<Row RowNo="1" NoOfBooks="10"/>
<Row RowNo="2" NoOfBooks="15"/>
<Rows>
</Rack>
The output xml lookslike:
<Rack RackNo="1">
<Rows>
<Row RowNo="1" NoOfBooks="10" Publisher="Penguin"/>
<Row RowNo="2" NoOfBooks="15" Publisher="Penguin"/>
<Rows>
</Rack>
The xsl i wrote is :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<Order>
<xsl:copy-of select = "Rack/#*"/>
<xsl:for-each select="Rows/Row">
<OrderLine>
<xsl:copy-of select = "Row/#*"/>
<xsl:attribute name="Publisher"></xsl:attribute>
<xsl:copy-of select = "Row/*"/>
</OrderLine>
</xsl:for-each>
<xsl:copy-of select = "Rack/*"/>
</Order>
</xsl:template>
</xsl:stylesheet>
This doesnt return the desired output.
Any help will be much appreciated.
Thanks in advance guys.

This is a job for the XSLT identity transform. On its own it simple creates a copy of all the nodes in your input XML
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
All you need to do is add an extra template to match Row element, and add a Publisher attribute to it. It might be good to first parameterise the publisher you wish to add
<xsl:param name="publisher" select="'Penguin'" />
You then create the matching template as follows:
<xsl:template match="Row">
<OrderLine Publisher="{$publisher}">
<xsl:apply-templates select="#*|node()"/>
</OrderLine>
</xsl:template>
Note the use of "Attribute Value Templates" to create the Publisher attribute. The curly braces indicate it is an expression to be evaluated. Also note in your XSLT it looks like you are renaming the elements too, so I have done this in my XSLT as well. (If this is not the case, simply replace OrderLine back with Row.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="publisher" select="'Penguin'" />
<xsl:template match="Rack">
<Order>
<xsl:apply-templates />
</Order>
</xsl:template>
<xsl:template match="Rows">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Row">
<OrderLine Publisher="{$publisher}">
<xsl:apply-templates select="#*|node()"/>
</OrderLine>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<Order>
<OrderLine Publisher="Penguin" RowNo="1" NoOfBooks="10"></OrderLine>
<OrderLine Publisher="Penguin" RowNo="2" NoOfBooks="15"></OrderLine>
</Order>

Related

current-group() except for one element in xslt

The below is my input xml. I'm trying group by using current-group() function but it is not meeting my requirement, below I have provided the details.
<UsrTimeCardEntry>
<Code>1<Code>
<Name>TC1</Name>
<Person>
<Code>074</Code>
</Person>
</UsrTimeCardEntry>
<UsrTimeCardEntry>
<Code>2<Code>
<Name>TC2</Name>
<Person>
<Code>074</Code>
</Person>
</UsrTimeCardEntry>
I want to group it by Person/Code so that it looks like this
<Person Code="074">
<UsrTimeCardEntry>
<Code>1</Code>
<Name>TC1</Name>
</UsrTimeCardEntry>
<UsrTimeCardEntry>
<Code>2</Code>
<Name>TC2</Name>
</UsrTimeCardEntry>
</Person>
For which I'm using the below xslt, but it is again copying the Person which I don't want, what it that I'm missing here, I tried using current-group() except and not[child::Person] but that too did not work.
<xsl:template match="businessobjects">
<xsl:for-each-group select="UsrTimeCardEntry" group-by="Person/Code">
<Person Code="{current-grouping-key()}">
<xsl:copy-of select="current-group()"></xsl:copy-of>
</Person>
</xsl:for-each-group>
</xsl:template>
Instead of using xsl:copy-of here, use xsl:apply-templates, then you can add a template to ignore the Person node
<xsl:template match="Person" />
This assumes you are also using the identity template to copy all other nodes normally.
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="businessobjects">
<xsl:for-each-group select="UsrTimeCardEntry" group-by="Person/Code">
<Person Code="{current-grouping-key()}">
<xsl:apply-templates select="current-group()" />
</Person>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="Person" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Using apply-template instead of call-template

Is it possible to replace the call-template statement in the following stylesheet with a apply-statement? So that the structure of the templates are nearly the same. With structure I mean that I have a xpath to select a element form the source xml e.g. /shiporder/address/city and I have a target xpath for my output xml e.g. /root/Address/Country then I step reverse through the source path. All /shiporder/address/city goes under Country all /shiporder/address goes under Address and the root shiporder become the tag root.
Source XML:
<shiporder>
<shipto>orderperson1</shipto>
<shipfrom>orderperson2</shipfrom>
<address>
<city>London</city>
</address>
<address>
<city>Berlin</city>
</address>
</shiporder>
Stylesheet:
<xsl:template match="/">
<xsl:apply-templates select="shiporder"/>
</xsl:template>
<xsl:template match="/shiporder">
<root>
<xsl:apply-templates select="address/city"/>
<xsl:call-template name="Identity" />
</root>
</xsl:template>
<xsl:template name="Identity">
<Identity>
<xsl:call-template name="Name" />
</Identity>
</xsl:template>
<xsl:template name="Name">
<Name>
<xsl:apply-templates select="/shiporder/shipto"/>
</Name>
</xsl:template>
<xsl:template match="/shiporder/shipto">
<Last>
<xsl:apply-templates select="text()"/>
</Last>
</xsl:template>
<xsl:template match="/shiporder/address/city">
<Country>
<xsl:apply-templates select="text()"/>
</Country>
</xsl:template>
you can use the following:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/shiporder">
<root>
<xsl:apply-templates select="address/city"/>
<xsl:apply-templates select="shipto"/>
</root>
</xsl:template>
<xsl:template match="shipto">
<Identity>
<Name>
<Last><xsl:value-of select="."/></Last>
</Name>
</Identity>
</xsl:template>
<xsl:template match="/shiporder/address/city">
<Country>
<xsl:value-of select="."/>
</Country>
</xsl:template>
</xsl:stylesheet>
Generally speaking, <xsl:call-template name="..."/> can be turned into a <xsl:apply-templates select="current()" mode="..."/> and <xsl:template match="node()" mode="..."/> (as long as this mode is not used anywhere else).
But there, the upvoted answer is way more suited.

Add mandatory nodes with XSLT

I am facing an xslt/xpath problem and hope someone could help, in a few words here is what I try to achieve.
I have to transform an XML document where some nodes may be missing, these missing nodes are mandatory in the final result. I have the set of mandatory node names available in an xsl:param.
The base document is:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TRANSFORM.xslt"?>
<BEGIN>
<CLIENT>
<NUMBER>0021732561</NUMBER>
<NAME1>John</NAME1>
<NAME2>Connor</NAME2>
</CLIENT>
<PRODUCTS>
<PRODUCT_ID>12</PRODUCT_ID>
<DESCRIPTION>blah blah</DESCRIPTION>
</PRODUCTS>
<PRODUCTS>
<PRODUCT_ID>13</PRODUCT_ID>
<DESCRIPTION>description ...</DESCRIPTION>
</PRODUCTS>
<OPTIONS>
<OPTION_ID>1</OPTION_ID>
<DESCRIPTION>blah blah blah ...</DESCRIPTION>
</OPTIONS>
<PROMOTIONS>
<PROMOTION_ID>1</PROMOTION_ID>
<DESCRIPTION>blah blah blah ...</DESCRIPTION>
</PROMOTIONS>
</BEGIN>
Here is the stylesheet so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:param name="mandatoryNodes" as="xs:string*" select=" 'PRODUCTS', 'OPTIONS', 'PROMOTIONS' "/>
<xsl:template match="/">
<xsl:apply-templates select="child::node()"/>
</xsl:template>
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="BEGIN">
<xsl:element name="BEGIN">
<xsl:for-each select="$mandatoryNodes">
<!-- If there is no node with this name -->
<xsl:if test="count(*[name() = 'current()']) = 0">
<xsl:element name="{current()}" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="child::node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I tried the transformation in XML Spy, the xsl:iftest failed saying that 'current item is PRODUCTS of type xs:string.
I've tried the same xsl:if outside of a for-each and it seems to work ... what am I missing ?
Inside of <xsl:for-each select="$mandatoryNodes"> the context item is a string but you want to access the primary input document and its nodes so you need to store that document or the template's context node in a variable and use that e.g.
<xsl:template match="BEGIN">
<xsl:variable name="this" select="."/>
<xsl:element name="BEGIN">
<xsl:for-each select="$mandatoryNodes">
<!-- If there is no child node of `BEGIN` with this name -->
<xsl:if test="count($this/*[name() = current()]) = 0">
<xsl:element name="{current()}" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="child::node()"/>
</xsl:element>
</xsl:template>

XSLT: When element exists add it and change value otherwise add element with value

My problem is that in some xml files an element exists and in another it does not.
When the element exists, it's value should be changed. If it does not exists, it should be added.
Here is an example for better understanding:
<root>
<group>
<element1>SomeValue1</element1>
<element2>SomeValue2</element2>
</group>
</root>
Let's say I always want element1, element2 and element3 with the values Changed1, Changed2, Changed3.
It should end up like this:
<root>
<group>
<element1>Changed1</element1>
<element2>Changed2</element2>
<element3>Changed3</element3>
</group>
</root>
What can I do to make it happen?
Thanking you in anticipation
Dennis
I'm not sure if it's the most elegant solution in the world, but I think this would probably do what you're after:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- If the element exists, do what you want to do -->
<xsl:template match="element1">
<xsl:copy>Changed1</xsl:copy>
</xsl:template>
<xsl:template match="element2">
<xsl:copy>Changed2</xsl:copy>
</xsl:template>
<xsl:template match="element3">
<xsl:copy>Changed3</xsl:copy>
</xsl:template>
<!-- If the element doesn't exist, add it -->
<xsl:template match="group">
<xsl:copy>
<xsl:apply-templates/>
<xsl:if test="not(element1)">
<element1>Changed1</element1>
</xsl:if>
<xsl:if test="not(element2)">
<element2>Changed2</element2>
</xsl:if>
<xsl:if test="not(element3)">
<element3>Changed3</element3>
</xsl:if>
</xsl:copy>
</xsl:template>
<!-- Identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Anything which isn't explicitly matched should just copy across untouched.
Of course, if the values are constant (that is, element1, element2 and element3 will always have the same values regardless of whether they're new or updated) then you can have something simpler:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- If the element exists, remove it -->
<xsl:template match="element1 | element2 | element3"/>
<!-- Now put in your preferred elements -->
<xsl:template match="group">
<xsl:copy>
<xsl:apply-templates/>
<element1>Changed1</element1>
<element2>Changed2</element2>
<element3>Changed3</element3>
</xsl:copy>
</xsl:template>
<!-- Identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Which essentially removes the original "element" nodes and puts yours in in their place.
Here is a more generic solution. The names of the elements can be specified separately from the code:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:newValues>
<element1>Changed1</element1>
<element2>Changed2</element2>
<element3>Changed3</element3>
</my:newValues>
<xsl:variable name="vElements" select="document('')/*/my:newValues/*"/>
<xsl:template match="*" name="identity" mode="copy">
<xsl:element name="{name()}">
<xsl:copy-of select="namespace::*[not(name()='my' or name()='xsl')]"/>
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="*">
<xsl:if test="not($vElements[name()=name(current())])">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
<xsl:template match="group">
<xsl:copy>
<xsl:apply-templates select="node()"/>
<xsl:apply-templates select="$vElements" mode="copy"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<root>
<group>
<element1>SomeValue1</element1>
<element2>SomeValue2</element2>
</group>
</root>
the wanted, correct result is produced:
<root>
<group>
<element1>Changed1</element1>
<element2>Changed2</element2>
<element3>Changed3</element3>
</group>
</root>
Note: The seemingly complicated processing that discards the "xsl" anf "my" namespaces is unnecessary when the to-be-modified elements are in their separate document. This code intentionally puts the to-be-modified elements in the same document as the xslt stylesheet for demonstration purposes. In practice, just an <xsl:copy-of select="$vModifiedDoc/*"> will be used.
Your example might be oversimplified. It looks like you can just generate the 3 "changed" values for every element you encounter... so I'm guessing it's a bit more complicated than that in real life.
In any case, if the changed value is not a replacement but actually a modification of the original value, you can probably use a for-each on groups and then generate the 3 elements, where the value of each element is based on an xsl-if element that checks whether the original value exists and uses it if so.
Just for fun, an XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vAdd" as="element()*">
<element1>Changed1</element1>
<element2>Changed2</element2>
<element3>Changed3</element3>
</xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="group/*[name()=$vAdd/name()]"/>
<xsl:template match="group/*[last()]" priority="1">
<xsl:next-match/>
<xsl:apply-templates select="$vAdd"/>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<group>
<element1>Changed1</element1>
<element2>Changed2</element2>
<element3>Changed3</element3>
</group>
</root>

XSLT: How to change an attribute value during <xsl:copy>?

I have an XML document, and I want to change the values for one of the attributes.
First I copied everything from input to output using:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
And now I want to change the value of the attribute "type" in any element named "property".
This problem has a classical solution: Using and overriding the identity template is one of the most fundamental and powerful XSLT design patterns:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pNewType" select="'myNewType'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="property/#type">
<xsl:attribute name="type">
<xsl:value-of select="$pNewType"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document:
<t>
<property>value1</property>
<property type="old">value2</property>
</t>
the wanted result is produced:
<t>
<property>value1</property>
<property type="myNewType">value2</property>
</t>
Tested on a simple example, works fine:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#type[parent::property]">
<xsl:attribute name="type">
<xsl:value-of select="'your value here'"/>
</xsl:attribute>
</xsl:template>
Edited to include Tomalak's suggestion.
The top two answers will not work if there is a xmlns definition in the root element:
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<property type="old"/>
</html>
All of the solutions will not work for the above xml.
The possible solution is like:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()[local-name()='property']/#*[local-name()='type']">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
some new value here
</xsl:attribute>
</xsl:template>
<xsl:template match="#*|node()|comment()|processing-instruction()|text()">
<xsl:copy>
<xsl:apply-templates select="#*|node()|comment()|processing-instruction()|text()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You need a template that will match your target attribute, and nothing else.
<xsl:template match='XPath/#myAttr'>
<xsl:attribute name='myAttr'>This is the value</xsl:attribute>
</xsl:template>
This is in addition to the "copy all" you already have (and is actually always present by default in XSLT). Having a more specific match it will be used in preference.
I had a similar case where I wanted to delete one attribute from a simple node, and couldn't figure out what axis would let me read the attribute name. In the end, all I had to do was use
#*[name(.)!='AttributeNameToDelete']
I also came across same issue and i solved it as follows:
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- copy property element while only changing its type attribute -->
<xsl:template match="property">
<xsl:copy>
<xsl:attribute name="type">
<xsl:value-of select="'your value here'"/>
</xsl:attribute>
<xsl:apply-templates select="#*[not(local-name()='type')]|node()"/>
</xsl:copy>
</xsl:template>
For the following XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<property type="foo"/>
<node id="1"/>
<property type="bar">
<sub-property/>
</property>
</root>
I was able to get it to work with the following XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//property">
<xsl:copy>
<xsl:attribute name="type">
<xsl:value-of select="#type"/>
<xsl:text>-added</xsl:text>
</xsl:attribute>
<xsl:copy-of select="child::*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If your source XML document has its own namespace, you need to declare the namespace in your stylesheet, assign it a prefix, and use that prefix when referring to the elements of the source XML - for example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes" />
<!-- identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- exception-->
<xsl:template match="xhtml:property/#type">
<xsl:attribute name="type">
<xsl:text>some new value</xsl:text>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Or, if you prefer:
...
<!-- exception-->
<xsl:template match="#type[parent::xhtml:property]">
<xsl:attribute name="type">
<xsl:text>some new value</xsl:text>
</xsl:attribute>
</xsl:template>
...
ADDENDUM:
In the highly unlikely case where the XML namespace is not known beforehand, you could do:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes" />
<!-- identity transform -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- exception -->
<xsl:template match="*[local-name()='property']/#type">
<xsl:attribute name="type">
<xsl:text>some new value</xsl:text>
</xsl:attribute>
</xsl:template>
Of course, it's very difficult to imagine a scenario where you would know in advance that the source XML document contains an element named "property", with an attribute named "type" that needs replacing - but still not know the namespace of the document. I have added this mainly to show how your own solution could be streamlined.