XSLT: y/n on new field if price difference > 1 - xslt

I am creating a new productfeed and need the following field: <diff>.
if difference between price and old_price is more than 1: y (from Yes) in field: <diff>
if difference between price and old_price is 1 or less than 1: n (from No) in field: <diff>
File: Data.xml
<?xml version="1.0"?>
<products>
<product id="0001">
<price>120.00</price>
<old_price>125.00</old_price>
</product>
<product id="0002">
<price>5.00</price>
<old_price>5.50</old_price>
</product>
</products>
Wished output:
<?xml version="1.0"?>
<products>
<product id="0001">
<diff>y</diff>
</product>
<product id="0002">
<diff>n</diff>
</product>
</products>

I haven't tested that but it should be like that:
<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:template match="/">
<products>
<xsl:for-each select="//product">
<diff>
<xsl:choose>
<xsl:when test="price - old_price > 1">
y
</xsl:when>
<xsl:otherwise>
n
</xsl:otherwise>
</xsl:choose>
</diff>
<xsl:copy-of select="*" />
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
Will test and put update

Give this a whirl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="price[translate(. - ../old_price, '-', '') > 1]">
<diff>y</diff>
</xsl:template>
<xsl:template match="price">
<diff>n</diff>
</xsl:template>
<xsl:template match="old_price" />
</xsl:stylesheet>
When run on your sample input, this produces:
<products>
<product id="0001">
<diff>y</diff>
</product>
<product id="0002">
<diff>n</diff>
</product>
</products>

Related

XSLT transformation with multiple nodes

I have multiple occurence nodes which need to be generated at output using XSLT transformation. Could you please help me on this.
Following XSLT code only generate one node occurrence only. Could you please help me with below XSLT code how to generate multiple nodes elements in Input XML
Input XML
<?xml version="1.0" encoding="UTF-8" ?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" >
<soapenv:Body>
<ns1:getGenResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
<ns1:getGenReturn xsi:type="soapenc:Array" soapenc:arrayType="xsd:anyType[2]" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
</ns1:getGenReturn>
</ns1:getGenResponse>
<multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns2:Gen" xmlns:soapenc=http://schemas.xmlsoap.org/soap/encoding/>
<name xsi:type="xsd:string">ULM</name>
<mail xsi:type="xsd:string">ulm#gmail.com</mail>
</multiRef>
<multiRef id="id1" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns3:Gen" " xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<name xsi:type="xsd:string">ABC</name>
<mail xsi:type="xsd:string">abc#gmail.com</mail>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>
XSLT Code used for this transformation
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" x
xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:if test="//soap:Body/multiRef">
<xsl:element name="getGenResponse">
<xsl:element name="getGenReturn">
<xsl:element name="name"><xsl:value-of select="//name"/></xsl:element>
<xsl:element name="mail"><xsl:value-of select="//mail"/></xsl:element>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:template>
<!-- 'Copy ' node -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output from above XSLT
<?xml version="1.0" encoding="UTF-8"?>
<getGenResponse>
<getGenReturn>
<name> ULM </name>
<mail>ulm#gmail.com<mail>
</getGenReturn>
/getGenResponse>
Output expected
<?xml version="1.0" encoding="UTF-8"?>
<getGenResponse>
<getGenReturn>
<name> ULM </name>
<mail>ulm#gmail.com<mail>
</getGenReturn>
<getGenReturn>
<name>ABC</name>
<mail>abc#gmail.com<mail>
</getGenReturn>
/getGenResponse>
At you moment all you are doing is testing a multiRef element exists, and outputting only one new getGenReturn element.
All you really need to do is replace the xsl:if with xsl:for-each to select all the elements, then you will get one getGenReturn for each. And also change the xsl:value-of to use a relative path
<xsl:template match="/">
<xsl:element name="getGenResponse">
<xsl:for-each select="//soap:Body/multiRef">
<xsl:element name="getGenReturn">
<xsl:element name="name"><xsl:value-of select="name"/></xsl:element>
<xsl:element name="mail"><xsl:value-of select="mail"/></xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
Or better still, do this, as xsl:element is not really needed here if you are using static names
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body/multiRef">
<getGenReturn>
<name><xsl:value-of select="name"/></name>
<mail><xsl:value-of select="mail"/></mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
Note, you don't actually need the identity template in this case. Try this XSLT:
<xsl:stylesheet version="1.0" xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="soap response">
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body/multiRef">
<getGenReturn>
<name><xsl:value-of select="name"/></name>
<mail><xsl:value-of select="mail"/></mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>
<xsl:stylesheet version="1.0" xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="soap response">
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body/multiRef">
<getGenReturn>
<name><xsl:value-of select="name"/></name>
<mail><xsl:value-of select="mail"/></mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>

Replace the XML element's name with the value of an attribute anywhere

I have a working XSLT that replaces an element name with an attribute value only when the attribute is named "AttributeID". It saves the original element name in a new attribute called StepClass but it only works at one level.
This XML:
<Products>
<Product>
<Values>
<Value AttributeID="One">1</Value>
<MultiValue AttributeID="Multi1">
<Value>111</Value>
</MultiValue>
</Values>
</Product>
</Products>
Becomes this XML:
<Products>
<Product>
<Values>
<One StepClass="Value">1</One>
<Multi1 StepClass="MultiValue">
<Value>111</Value>
</Multi1>
</Values>
</Product>
</Products>
Using this XSLT:
<?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"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Change Values/Value value of AttributeID -->
<!-- <xsl:template match="Values/Value|MultiValue|MetaData/Value"> This was working with 1 level -->
<xsl:template match="*[#AttributeID]">
<xsl:element name="{#AttributeID}">
<xsl:attribute name="StepClass">
<xsl:value-of select="name()" />
</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<!--empty template suppresses this attribute-->
<xsl:template match="#AttributeID" />
</xsl:stylesheet>
But fails as soon as the source XML has nested products. I would like the substitution to work at any level. What am I doing wrong here?
Nested XML:
<Products>
<Product>
<Values>
<Value AttributeID="One">1</Value>
<MultiValue AttributeID="Multi1">
<Value>111</Value>
</MultiValue>
</Values>
<Product>
<Values>
<Value AttributeID="Two">2</Value>
<MultiValue AttributeID="Multi2">
<Value>222</Value>
</MultiValue>
</Values>
</Product>
</Product>
</Products>
XSLT as depicted in the post works as expected.

xsl to reset a existing line level element value using a header level element in an XML

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>

XSLT 1 Exclude parent element if not in a list

I want to perform an xslt transformation where I split products up by type. As an example:
Source XML:
<Products>
<Product>
<Name>Cheese</Name>
<Value>30</Value>
</Product>
<Product>
<Name>Bread</Name>
<Value>10</Value>
</Product>
<Product>
<Name>Bacon</Name>
<Value>100</Value>
</Product>
</Products>
Required Output XML:
<Products>
<AnimalProducts>
<Product>
<Name>Cheese</Name>
<Value>30</Value>
</Product>
<Product>
<Name>Bacon</Name>
<Value>100</Value>
</Product>
</AnimalProducts>
<VeganProducts>
<Product>
<Name>Bread</Name>
<Value>10</Value>
</Product>
</VeganProducts>
</Products>
If there are no animal products, or no vegan products then the parent elements should not be included. I have it half working with:
<xsl:variable name="veganProducts" select="'Bread:Lettuce'" />
<xsl:if test="Products/Product[count(*) > 0]">
<AnimalProducts>
<xsl:for-each select="Products/Product">
<xsl:if test="not(contains(concat(':', $veganProducts, ':'), concat(':', Name, ':')))">
<Product>
<Name>
<xsl:value-of select="Name" />
</Name>
<Value>
<xsl:value-of select="Value" />
</Value>
</Product>
</xsl:if>
</xsl:for-each>
</AnimalProducts>
<VeganProducts>
<xsl:for-each select="Products/Product">
<xsl:if test="contains(concat(':', $veganProducts, ':'), concat(':', Name, ':'))">
<Product>
<Name>
<xsl:value-of select="Name" />
</Name>
<Value>
<xsl:value-of select="Value" />
</Value>
</Product>
</xsl:if>
</xsl:for-each>
</VeganProducts>
</xsl:if>
The problem is I am getting empty parent elements if there are no vegan or animal products in certain cases. I am unsure how I can test for this.
This is a good time to make use of variables:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:variable name="veganProductNames" select="'Bread:Lettuce'" />
<xsl:variable name="veganProductNamesPadded"
select="concat(':', $veganProductNames, ':')" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:variable name="animalProducts"
select="Product[not(contains($veganProductNamesPadded,
concat(':', Name, ':')))]" />
<xsl:variable name="veganProducts"
select="Product[contains($veganProductNamesPadded,
concat(':', Name, ':'))]" />
<xsl:if test="$animalProducts">
<AnimalProducts>
<xsl:apply-templates select="$animalProducts" />
</AnimalProducts>
</xsl:if>
<xsl:if test="$veganProducts">
<VeganProducts>
<xsl:apply-templates select="$veganProducts" />
</VeganProducts>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, the result is:
<Products>
<AnimalProducts>
<Product>
<Name>Cheese</Name>
<Value>30</Value>
</Product>
<Product>
<Name>Bacon</Name>
<Value>100</Value>
</Product>
</AnimalProducts>
<VeganProducts>
<Product>
<Name>Bread</Name>
<Value>10</Value>
</Product>
</VeganProducts>
</Products>
I would prefer to solve this the XML way:
XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://www.example.com/my"
exclude-result-prefixes="my">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<my:vegan-items>
<item>Bread</item>
<item>Lettuce</item>
</my:vegan-items>
<xsl:variable name="vegan-items" select="document('')/xsl:stylesheet/my:vegan-items/item" />
<xsl:template match="/Products">
<xsl:variable name="vegan-products" select="Product[Name=$vegan-items]" />
<xsl:variable name="animal-products" select="Product[not(Name=$vegan-items)]" />
<xsl:copy>
<xsl:if test="$animal-products">
<AnimalProducts>
<xsl:copy-of select="$animal-products"/>
</AnimalProducts>
</xsl:if>
<xsl:if test="$vegan-products">
<VeganProducts>
<xsl:copy-of select="$vegan-products"/>
</VeganProducts>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here we are assuming that anything not "vegan" is "animal" (since no list of "animal" items has been provided).
Keeping an external XML document listing the items in each category would probably be even better.

Merging two diff XMLS using XSLT(1.0)

I am new to XSLT and I need help in merging two different XML documents into one.
XML1.xml
<customers>
<customer>
<Person name="Ram" Id="101"/>
<address>flat 4</address>
</customer>
<customer>
<Person name="Raghav" Id="102"/>
<address>flat 9</address>
</customer>
</customers>
XML2.xml
<Products>
<Product>
<name>Onida Tv</name>
<consumer>Ram</consumer>
</Product>
<Product>
<name>washing machine</name>
<consumer>Ram</consumer>
</Product>
<Product>
<name>Water purifier</name>
<consumer>Raghav</consumer>
</Product>
<Product>
<name>iPhone</name>
<consumer>Raghav</consumer>
</Products>
</Products>
Desired XML output:
<customers>
<customer>
<Person name="Ram" Id="101"/>
<address>flat 4</address>
<products>
<name>washing machine</name>
<name>Onida TV</name>
</products>
</customer>
<customer>
<Person name="Raghav" Id="102"/>
<address>flat 9</address>
<products>
<name>iPhone</name>
<name>Water purifier</name>
</products>
</customer>
</customers>
The second XML is to be considered external in this context. I need to append to each customer the corresponding products. How can I do that?
Try it this way:
XSLT 1.0
<?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"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="lookup-document" select="document('XML2.xml')" />
<xsl:key name="product-by-consumer" match="Product" use="consumer" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="customer">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:variable name="name" select="Person/#name" />
<products>
<!-- switch context to the other document in order to use key -->
<xsl:for-each select="$lookup-document">
<xsl:copy-of select="key('product-by-consumer', $name)/name"/>
</xsl:for-each>
</products>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that this assumes that customer names are unique.