I have the following source XML document:
<UserDefinedFields>
<UserDefinedField>
<Name>ABC</Name>
<Value>123</Value>
</UserDefinedField>
<UserDefinedField>
<Name>XYZ</Name>
<Value>645q3245</Value>
</UserDefinedField>
</UserDefinedFields>
I want to overwrite matching nodes from an input XML if there is a matching <Name> value.. So in other words, the end result of merging this in:
<UserDefinedField>
<Name>XYZ</Name>
<Value>NEWVALUE!</Value>
</UserDefinedField>
... would be:
<UserDefinedFields>
<UserDefinedField>
<Name>ABC</Name>
<Value>123</Value>
</UserDefinedField>
<UserDefinedField>
<Name>XYZ</Name>
<Value>NEWVALUE!</Value>
</UserDefinedField>
</UserDefinedFields>
What is an appropriate XSLT transformation to achieve this?
XSLT 2.0 or 1.0 answers are fine... 2.0 preferred though.
You can do this with grouping:
<xsl:for-each-group
select="$doc1//UserDefinedField, $doc2//UserDefinedField"
group-by="Name">
<xsl:copy-of select="current-group()[last()]"/>
</xsl:for-each-group>
Assuming you are instructing your processor to process the "initial" XML document, and supplying the path to the "overriding" document as a parameter, you could do:
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:param name="path-to-update">update.xml</xsl:param>
<xsl:key name="fld" match="UserDefinedField" use="Name" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Value">
<xsl:variable name="update" select="key('fld', ../Name, document($path-to-update))" />
<xsl:copy>
<xsl:value-of select="if ($update) then $update/Value else ."/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Just use this template to override the identity rule:
<xsl:template match="UserDefinedField[key('kFieldByName', Name, $vDoc2)]/Value/text()">
<xsl:value-of select="key('kFieldByName', ../../Name, $vDoc2)[1]"/>
</xsl:template>
Here I assume that the second document has a document element (top element) and can contain many UserDefinedField elements at different depth. For convenience only, the second document is inlined in the transformation -- in a real case the doc() function can be used. I also declare an <xsl:key> to efficiently find a new value using the Name child's value of a UserDefinedField in the second document.
Here is the complete transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kFieldByName" match="Value" use="../Name"/>
<xsl:variable name="vDoc2">
<patterns>
<UserDefinedField>
<Name>XYZ</Name>
<Value>NEWVALUE!</Value>
</UserDefinedField>
</patterns>
</xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="UserDefinedField[key('kFieldByName', Name, $vDoc2)]/Value/text()">
<xsl:value-of select="key('kFieldByName', ../../Name, $vDoc2)[1]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<UserDefinedFields>
<UserDefinedField>
<Name>ABC</Name>
<Value>123</Value>
</UserDefinedField>
<UserDefinedField>
<Name>XYZ</Name>
<Value>645q3245</Value>
</UserDefinedField>
</UserDefinedFields>
the wanted, correct result is produced:
<UserDefinedFields>
<UserDefinedField>
<Name>ABC</Name>
<Value>123</Value>
</UserDefinedField>
<UserDefinedField>
<Name>XYZ</Name>
<Value>NEWVALUE!</Value>
</UserDefinedField>
</UserDefinedFields>
Related
Beginner XSL question..and I know there are similar questions and answers posted, but I can't figure out how to apply them to my XSLT...
My source XML looks like (this is just a fragment of a much larger XML file)
<?xml version="1.0" encoding="UTF-8"?>
<COLLECTION><Release NAME="Release" TYPE="Unknown" STATUS="0">
<Transaction>
<TransactionNumber>4</TransactionNumber>
<ReleaseNumber>4</ReleaseNumber>
<PrimaryObjectID>OR:wt.part.WTPart:121581:416986630-1502721046884-982634822-1-0-0-127#ODIGettingStarted.tri.co.uk</PrimaryObjectID>
<CreatedBy>orgadmin</CreatedBy>
<CreatedDate>2017-09-27 08:34:31 EDT</CreatedDate>
<Locale>en_US</Locale>
<Destination>CRP1</Destination>
</Transaction>
</Release>
I want to exclude the Locale and Destination nodes from the output.
My complete solution will be more complex that requires me to split the XML into three, hence my use of but the relevant code I have so far is :-
<?xml version = "1.0" encoding = "UTF-8"?>
<xsl:stylesheet version = "2.0"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:param name ="outputFileDir" select="'file:///D:/workspace/TPHMOT_xsl/TPHMOT_xsl/xsl_output'"/>
<xsl:template match ="/">
<xsl:result-document href="{$outputFileDir}/ESI_ItemMasters_1.xml" method="xml" indent="yes">
<COLLECTION>
<xsl:apply-templates select="COLLECTION/Release"/>
</COLLECTION>
</xsl:result-document>
<xsl:result-document href="{$outputFileDir}/ESI_ConfigurableItem_1.xml" method="xml" indent="yes">
<COLLECTION>
<xsl:apply-templates select="COLLECTION/Release"/>
</COLLECTION>
</xsl:result-document>
<xsl:result-document href="{$outputFileDir}/ESI_GenericBOM_1.xml" method="xml" indent="yes">
<COLLECTION>
<xsl:apply-templates select="COLLECTION/Release"/>
</COLLECTION>
</xsl:result-document>
</xsl:template>
<xsl:template match="Release">
<xsl:copy-of select="self::node()"/>
</xsl:template>
</xsl:stylesheet>
This outputs
<?xml version="1.0" encoding="UTF-8"?>
<COLLECTION>
<Release NAME="Release" TYPE="Unknown" STATUS="0">
<Transaction>
<TransactionNumber>4</TransactionNumber>
<ReleaseNumber>4</ReleaseNumber>
<PrimaryObjectID>OR:wt.part.WTPart:121581:416986630-1502721046884-982634822-1-0-0-127#ODIGettingStarted.tri.co.uk</PrimaryObjectID>
<CreatedBy>orgadmin</CreatedBy>
<CreatedDate>2017-09-27 08:34:31 EDT</CreatedDate>
<Locale>en_US</Locale>
<Destination>CRP1</Destination>
</Transaction>
</Release>
</COLLECTION>
How can I adapt my XSL to exclude Locale and Destination child nodes?
Many thanks in advance for any help offered!
Instead of copying your complete element in
<xsl:template match="Release">
<xsl:copy-of select="self::node()"/>
</xsl:template>
you only need to use the identity transformation
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
and then empty template(s) to prevent the elements you don't want from being copied:
<xsl:template match="Locale | Destination"/>
Happy new year all,
I have a requirement to reset a line level element value with the header level element value. Below is the input XML and expected output XML, any guidance is appreciated. I tried few ways to template match but I am unable to produce the expected result.
Input:
<Products>
<Company>ABC</Company>
<City>Pandora</City>
<Product>
<Name>Avatar</Name>
<MadeInCity>New York</MadeInCity>
</Product>
<Product>
<Name>Titanic</Name>
<MadeInCity>San Francisco</MadeInCity>
</Product>
....
</Products>
Output:
<Products>
<Company>ABC</Company>
<Product>
<Name>Avatar</Name>
<MadeInCity>Pandora</MadeInCity>
</Product>
<Product>
<Name>Titanic</Name>
<MadeInCity>Pandora</MadeInCity>
</Product>
....
</Products>
As shown above, I want to do below things using XSL
Replace the values of Products/Product/MadeInCity element value with Products/City element value
Remove the element Products/City
Below is what I have attempted
<?xml version="1.0"?>
<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="Products/Product/MadeInCity">
<value>
<xsl:choose>
<xsl:when test="Products/City">
<xsl:value-of select="Products/City"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</value>
</xsl:template>
</xsl:stylesheet>
-Thank you
I would do it this way:
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>
<!-- if City exists, replace the value of MadeInCity -->
<xsl:template match="/Products[City]/Product/MadeInCity">
<xsl:copy>
<xsl:value-of select="/Products/City"/>
</xsl:copy>
</xsl:template>
<!-- remove City -->
<xsl:template match="City"/>
</xsl:stylesheet>
I am trying to obtain a list of all the elements with values that aren't in the (Line 1, Line2), and then insert them into the tags similar to the test.
Right now I can retrieve all the elements, but I'm having trouble restricting this to just my desired values. And then I'm unsure how to match and do a for each on elements outside my match criteria. Any advice would be greatly appreciated!
Given the Following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>test</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
Current XSL for getting elements
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:for-each select="node()[text() != '']">
<xsl:value-of select="local-name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
My WIP xml for inserting the result xml tags is below. I'm unsure how to insert the results of the above xsl into this,
<xsl:template match="Element">
<xsl:copy-of select="."/>
<Element>Value1</Element>
</xsl:template>
And ultimate desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>Identifier</Element>
<Element>Gender</Element>
<Element>Title</Element>
<Element>Name</Element>
<Element>AddressType</Element>
<Element>Line1</Element>
<Element>Suburb</Element>
<Element>State</Element>
<Element>PostCode</Element>
<Element>Country</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
I would change the current template to use mode attribute, so it is only used in specific cases, rather than matching all elements. You should also change it to output elements, not text, like so:
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
Then you can call it like this....
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
Try this XSLT. Note the use of the identity template to copy all other existing elements unchanged
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
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>
Below is the actual xml:
<?xml version="1.0" encoding="utf-8"?>
<employee>
<Name>ABC</Name>
<Dept>CS</Dept>
<Designation>sse</Designation>
</employee>
And i want the output as below:
<?xml version="1.0" encoding="utf-8"?>
<employee>
<Name>ABC</Name>
<Age>34</Age>
<Dept>CS</Dept>
<Domain>Insurance</Domain>
<Designation>sse</Designation>
</employee>
Is this possible to add XML element in between using xslt?
Please give me sample!
Here is an XSLT 1.0 stylesheet that will do what you asked:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Name">
<xsl:copy-of select="."/>
<Age>34</Age>
</xsl:template>
<xsl:template match="Dept">
<xsl:copy-of select="."/>
<Domain>Insurance</Domain>
</xsl:template>
</xsl:stylesheet>
Obviously the logic will vary depending on where you will be getting the new data from, and where it needs to go. The above stylesheet merely inserts an <Age> element after every <Name> element, and a <Domain> element after every <Dept> element.
(Limitation: if your document could have <Name> or <Dept> elements within other <Name> or <Dept> elements, only the outermost ones will have this special processing. I don't think you intend for your document to have this kind of recursive structure, so it wouldn't affect you, but it's worth mentioning just in case.)
I have modified few things in the existing stylesheet ,it will allow you to choose the specific element and update in your xml.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Name[1]">
<xsl:copy-of select="."/>
<Age>34</Age>
</xsl:template>
<xsl:template match="Dept[1]">
<xsl:copy-of select="."/>
<Domain>Insurance</Domain>
</xsl:template>
</xsl:stylesheet>
XML:
<?xml version="1.0" encoding="utf-8"?>
<employee>
<Name>ABC</Name>
<Dept>CS</Dept>
<Designation>sse</Designation>
<Name>CDE</Name>
<Dept>CSE</Dept>
<Designation>sses</Designation>
</employee>