I would like to replace an element from an external XML file. Currently I have an XSLT which adds elements, but not replacing them.
Input.xml:
<all>
<item>
<sku>3850950</sku>
<name>Macbook Pro</name>
<qty>3</qty>
<img>original.jpg</img>
</item>
<item>
<sku>3859955</sku>
<name>iPhone 12</name>
<qty>9</qty>
<img>original-apple.jpg</img>
</item>
</all>
XSLT:
<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:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:key name="group" match="additem" use="sku" />
<xsl:template match="item">
<xsl:copy>
<xsl:copy-of select="*"/>
<xsl:apply-templates select="key('group', sku, document('./extra-info.xml'))"/>
</xsl:copy>
</xsl:template>
<xsl:template match="additem">
<xsl:for-each select="img">
<img>
<xsl:value-of select="."/>
</img>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Extra-info.xml:
<all>
<additem>
<sku>3850950</sku>
<img>aaaaaaa</img>
<img>bbbbbbb</img>
</additem>
<additem>
<sku>3859955</sku>
</additem>
</all>
I would like to match img elements from extra-info.xml, the common key is sku. Replace <img> in the input.xml if same exists in extra-info.xml and add all of them.
Example output:
<all>
<item>
<sku>3850950</sku>
<name>Macbook Pro</name>
<qty>3</qty>
<img>aaaaaaa</img>
<img>bbbbbbb</img>
</item>
<item>
<sku>3859955</sku>
<name>iPhone 12</name>
<qty>9</qty>
<img>original-apple.jpg</img>
</item>
</all>
AFAICT, you want to 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:key name="additem" match="additem" use="sku" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:variable name="additem" select="key('additem', sku, document('extra-info.xml'))"/>
<xsl:copy>
<xsl:copy-of select="* except img"/>
<xsl:choose>
<xsl:when test="$additem/img">
<xsl:copy-of select="$additem/img"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="img"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Related
<DETAILS>
<PUT>
<RECORD>ABC_PQRST0123456-001_1</RECORD>
<NUMBER>4</NUMBER>
<INST>1,2</INST>
</PUT>
<PUT>
<RECORD>ABC_PQRST0123456-001_2</RECORD>
<NUMBER>1</NUMBER>
</PUT>
</DETAILS>
How to remove the other loop elements from where INST don't have values.Can someone help me with xslt Transformation code to sort this
<PUT>
<RECORD>ABC_PQRST0123456-001_2</RECORD>
<NUMBER>1</NUMBER>
</PUT>
This does what you describe:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="*" />
</xsl:template>
<xsl:template match="PUT[not(INST)]">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<!-- match but dont do anything -->
<xsl:template match="PUT">
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I'm using a for-each in my XSLT template.
This is my example input XML:
<products>
<data>
<label_1>some_label1</label_1>
<label_2>some_label2</label_2>
<values>
<a>a</a>
<b>b</b>
</values>
</data>
<data>
<label_1>some_label1</label_1>
<label_2>some_label2</label_2>
<values>
<c>c</c>
<d>d</d>
</values>
</data>
</products>
Now based on my template:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http:/example.com/ns">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:variable name="values" select="values" />
<xsl:for-each select="$values">
<xsl:apply-templates select="#*|node()" />
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>
I get only <values></values> and that is ok for me.
That's my output:
<products>
<data>
<a>a</a>
<b>b</b>
</data>
<data>
<c>c</c>
<d>d</d>
</data>
</products>
What i need in my output is namespace like this:
<products>
<data>
<ns:a>a</ns:a>
<ns:b>b</ns:b>
</data>
<data>
<ns:c>c</ns:c>
<ns:d>d</ns:d>
</data>
</products>
So what i understand is "each element of values is applied by template". How can I add namespace ?
You can get output similar to what you show (albeit well-formed) by using:
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="data">
<xsl:copy>
<xsl:apply-templates select="values/*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="values/*">
<xsl:element name="ns:{local-name()}" namespace="http:/example.com/ns">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Or, if you prefer:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http:/example.com/ns">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/products">
<products>
<xsl:for-each select="data">
<xsl:copy>
<xsl:for-each select="values/*">
<xsl:element name="ns:{local-name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
Replace http:/example.com/ns with your own namespace URI.
Credits
This answer follows the technique used in this SO answer to a similar problem.
Solution
Add namespace information to all descendants of specific elements. Augment the stylesheet by a template matching this set of nodes:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://my.ns.uri"
>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:variable name="values" select="values" />
<xsl:for-each select="$values">
<xsl:apply-templates select="#*|node()" />
</xsl:for-each>
</data>
</xsl:template>
<!--
Added template.
-->
<xsl:template match="data//*">
<xsl:element name="ns:{name()}" namespace="http://my.ns.uri">
<xsl:for-each select=".">
<xsl:apply-templates select="#*|node()" />
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I want to transform a source xml into a target xml where certain matches from the source xml are included in different context in the target xml. For example I have a source xml like:
<shiporder>
<shipto>orderperson1</shipto>
<shipto>orderperson1</shipto>
<city>London</city>
</shiporder>
On this source xml I apply the following stylesheet:
<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="/">
<xsl:call-template name="root" />
</xsl:template>
<xsl:template name="root">
<root>
<xsl:apply-templates select="/shiporder"/>
<xsl:call-template name="Customer"/>
</root>
</xsl:template>
<xsl:template name="Customer">
<Customer>
<!--<xsl:apply-templates select="/shiporder"/>-->
</Customer>
</xsl:template>
<xsl:template match="/shiporder">
<xsl:apply-templates select="shipto"/>
</xsl:template>
<xsl:template match="/shiporder/shipto">
<Address>
<xsl:apply-templates select="text()"/>
</Address>
</xsl:template>
</xsl:stylesheet>
In the template of name Customer I like to apply a template like:
<xsl:template match="/shiporder">
<xsl:apply-templates select="city"/>
</xsl:template>
<xsl:template match="/shiporder/city">
<City>
<xsl:apply-templates select="text()"/>
</City>
</xsl:template>
But I already defined a template with match /shiporder. So I don't know how to design a stylesheet where both templates with the same match exists in their own context?
If you use mode, like #michael.hor257k suggested you can differentiate between two or more templates that match on the same element but with different results.
In your case that could end up looking like 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" indent="yes" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:call-template name="root" />
</xsl:template>
<xsl:template name="root">
<root>
<xsl:apply-templates select="/shiporder" mode="root"/>
<xsl:call-template name="Customer"/>
</root>
</xsl:template>
<xsl:template name="Customer">
<Customer>
<xsl:apply-templates select="/shiporder" mode="customer"/>
</Customer>
</xsl:template>
<xsl:template match="/shiporder" mode="root">
<xsl:apply-templates select="shipto"/>
</xsl:template>
<xsl:template match="/shiporder" mode="customer">
<xsl:apply-templates select="city"/>
</xsl:template>
<xsl:template match="shipto">
<Address>
<xsl:apply-templates select="text()"/>
</Address>
</xsl:template>
<xsl:template match="city">
<City>
<xsl:apply-templates select="text()"/>
</City>
</xsl:template>
</xsl:stylesheet>
Obviously all credits here go to Michael for pointing this out first.
I have this XML:
<root>
<tab name="Detail">
<section name="mysection">
<items level="1">
<Idx_name>9</Idx_name>
<Type>mytype</Type>
<item name="myname">
<Grams um="(g)">9,0</Grams>
<Pre-infusion>Max</Pre-infusion>
</item>
<Std._Mode>On</Std._Mode>
<Price>100</Price>
</items>
</section>
</tab>
</root>
and this XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items/*">
<xsl:choose>
<xsl:when test="not(name()='item')">
<xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Now, what I want is:
<root>
<tab name="Detail">
<section name="mysection">
<items level="1" Idx_name="9" Type="mytype" Std._Mode="On" Price="100">
<item name="myname">9,0Max</item>
</items>
</section>
</tab>
</root>
I obtain the error: "An attribute cannot be added after a child"
Unluckily, I can not change the order of elements in the node items of my original XML
How can I do it ?
Thanks
Ivan
Make sure you process the elements first you want to transform into attributes e.g. with XSLT 2.0 where you can process sequences which have a order you can simply do
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items">
<xsl:copy>
<xsl:apply-templates select="#*, * except item, item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items/*[not(self::item)]">
<xsl:attribute name="{name()}" select="."/>
</xsl:template>
<xsl:template match="items/item">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT 1.0 you would need to spell out several apply-templates in the order you want.
I'm struggling with trying to test to see if an element exists. If it doesn't, I'd like to add in a default value. Here's my XML
<records>
<record>
<InstanceData>
<instance>
<FirstName>Johhny</FirstName>
<LastName>Jenkins</LastName>
<AlbumCount>3</AlbumCount>
</instance>
</InstanceData>
</record>
<record>
<InstanceData>
<instance>
<FirstName>Art</FirstName>
<LastName>Tatum</LastName>
<AlbumCount>7</AlbumCount>
</instance>
</InstanceData>
</record>
<record>
<InstanceData>
<instance>
<FirstName>Count</FirstName>
<LastName>Basie</LastName>
</instance>
</InstanceData>
</record>
</records>
I'd like to be able to copy over existing values and set any record without the Album Count element to <AlbumCount>0</AlbumCount>. This is the xslt I've been working with but I think I'm some way off the mark.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="Records">
<xsl:for-each select="node()">
<xsl:choose>
<xsl:when test="name()='AlbumCount'">
<xsl:element name="AlbumCount">
<xsl:choose>
<xsl:when test="name()='AlbumCount'">
<xsl:copy-of select=".">
</xsl:copy-of>
</xsl:when>
<xsl:otherwise>
<AlbumCount>0</AlbumCount>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select=".">
</xsl:copy-of>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Thanks for looking.
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" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="no"/>
<!-- identity template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="instance[not(AlbumCount)]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<AlbumCount>0</AlbumCount>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Start with the identity transformation, then just handle the exception differently.
You test for the existance of an element simply with the elements name, for example:
<xsl:if test="not(AlbumCount)">
<AlbumCount>0</AlbumCount>
</xsl:if>
The simpler way to do what you want is to use the standard copy template combined with a special rule for places where AlbumCount elements need adding:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<!-- Standard copy template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Special template to add AlbumCount elements where required -->
<xsl:template match="records/record/InstanceData/instance">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:if test="not(AlbumCount)">
<AlbumCount>0</AlbumCount>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>