XSLT for-each with and condition - xslt

I have an xml with multiple nodes and I am trying to fetch nodes based on an 'and' condition using xpath 1.0. I tried using contains in the for-each clause, when I use just one [contains(DateType, 'CAOSL')] it returns the result but when I add the 'and' clause the result is not returned even though there are 2 node that match the condition.
I would appreciate if someone can point out what's wrong or point me to the correct way of doing it.
Thanks in advance.
I have tried the for-each as below in the xslt file. The first one returns the node with matching CAOSL but the and condition fails.
XML:
<Products>
<Product>
<ProductSupply>
<SupplyDetail>
<Supplier>
<SupplierRole>01</SupplierRole>
<SuppierName>Test</SuppierName>
</Supplier>
<ProductAvailability>21</ProductAvailability>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1464739200</Date>
<DateCountry>NZ</DateCountry>
<DateType>NZPUB</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1464739200</Date>
<DateCountry>AU</DateCountry>
<DateType>AUPUB</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1496102400</Date>
<DateCountry>CA</DateCountry>
<DateType>CAOSL</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1480550400</Date>
<DateCountry>UK</DateCountry>
<DateType>UKOSL</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1464739200</Date>
<DateCountry>NZ</DateCountry>
<DateType>NZSHD</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>02</SupplyDateRole>
<Date>1463961600</Date>
<DateCountry>AU</DateCountry>
<DateType>AUOSL</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1464566400</Date>
<DateCountry>NZ</DateCountry>
<DateType>NZOSL</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>08</SupplyDateRole>
<Date>1464739200</Date>
<DateCountry>AU</DateCountry>
<DateType>AUSHD</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1496102400</Date>
<DateCountry>US</DateCountry>
<DateType>USOSL</DateType>
</SupplyDate>
</SupplyDetail>
</ProductSupply>
</Product>
XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="Products">
<Products>
<xsl:apply-templates select="Product" />
</Products>
</xsl:template>
<xsl:template match="Product">
<product>
<productsupply>
<supplydetail>
<xsl:for-each select="ProductSupply/SupplyDetail/SupplyDate[contains(DateType, 'CAOSL') and contains(DateType, 'USOSL')]">
<supplydate>
<DateType>
<xsl:value-of select="DateType" />
</DateType>
<DateCountry>
<xsl:value-of select="DateCountry" />
</DateCountry>
</supplydate>
</xsl:for-each>
</supplydetail>
</productsupply>
</product>
</xsl:template>
</xsl:stylesheet>
Output:
<result>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1496102400</Date>
<DateCountry>CA</DateCountry>
<DateType>CAOSL</DateType>
</SupplyDate>
<SupplyDate>
<SupplyDateRole>00</SupplyDateRole>
<Date>1496102400</Date>
<DateCountry>US</DateCountry>
<DateType>USOSL</DateType>
</SupplyDate>

Your data does not include a SupplyDate that has both a DateType containing CAOSL and a DateType containing USOSL.
So your logic is incorrect, but I can't work out from your question what your actual requirement is.
Incidentally, I think you have misunderstood what contains() does, though that's not your main bug. Given an element ABCXYZ, contains(a, "ABC") returns true. I think you want a = "ABC" which returns true only if ABC is the entire string value of the node.

Related

XSLT 3.0 How to String joing specific element texts in one element

Hi I have this sample that contains multiple products each with element <main_image>text</main_image> and then each have additional elements <image_>text...</image_>.
I want to merge them all to elements that contain <image_*> to the <main_image> separated by comma (,).
<shop>
<products>
<product>
<price>176.5500</price>
<pricecustomer>154.4812</pricecustomer>
<product_id>6167</product_id>
<model>BUN-001</model>
<ean></ean>
<mpn>BUN-001</mpn>
<isbn>0</isbn>
<minimum>1</minimum>
<tax_class>1</tax_class>
<quantity>0</quantity>
<main_image>https://www.test.com/image/products/Bundles/bundle-001.jpg</main_image>
<manufacturer></manufacturer>
<varos>0.00000000</varos>
<mikos>0.00000000</mikos>
<platos>0.00000000</platos>
<ipsos>0.00000000</ipsos>
<status>Published</status>
<image_1>https://www.test.com/image/products/beper/bt.200/BT.200.jpg</image_1>
<image_2>https://www.test.com/image/products/beper/bt.200/BT.200-1.jpg</image_2>
<image_3>https://www.test.com/image/products/zilan/zln7887/ZLN7887.jpg</image_3>
</product>
</products>
</shop>
and the result that i want is this
<shop>
<products>
<product>
<price>176.5500</price>
<pricecustomer>154.4812</pricecustomer>
<product_id>6167</product_id>
<model>BUN-001</model>
<ean></ean>
<mpn>BUN-001</mpn>
<isbn>0</isbn>
<minimum>1</minimum>
<tax_class>1</tax_class>
<quantity>0</quantity>
<main_image>https://www.test.com/image/products/Bundles/bundle-001.jpg,https://www.test.com/image/products/beper/bt.200/BT.200.jpg,https://www.test.com/image/products/beper/bt.200/BT.200-1.jpg,https://www.test.com/image/products/zilan/zln7887/ZLN7887.jpg</main_image>
<manufacturer></manufacturer>
<varos>0.00000000</varos>
<mikos>0.00000000</mikos>
<platos>0.00000000</platos>
<ipsos>0.00000000</ipsos>
<status>Published</status>
<image_1>https://www.test.com/image/products/beper/bt.200/BT.200.jpg</image_1>
<image_2>https://www.test.com/image/products/beper/bt.200/BT.200-1.jpg</image_2>
<image_3>https://www.test.com/image/products/zilan/zln7887/ZLN7887.jpg</image_3>
</product>
</products>
</shop>
I have tried this code snippet with XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="main_image">
<xsl:value-of select="if (../../../main_image/text()) then string-join((image_*/text()), ',') else ''"/>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
That results no main image at all, what do I miss? Thank you for any help
You can use
<xsl:template match="main_image">
<xsl:copy>
<xsl:value-of select="., ../*[matches(local-name(), '^image_')]" separator=","/>
</xsl:copy>
</xsl:template>

Transform to output xml based on the value of an element in the input xml

I am trying to learn the basics of XSLT, but am stuck on a particular use case. What I want to achieve is to transform one xml file into another xml (I am using XSLT 2.0), but a condition is that the grouping of elements in the output xml is decided by the value of one particular element in the input xml.
I will try to exemplify my question through a made-up example.
Lets say this is an input xml:
<products>
<shoes>
<shoe>
<name>Ecco City</name>
<category>Urban</category>
</shoe>
<shoe>
<name>Timberland Forest</name>
<category>Wildlife</category>
</shoe>
<shoe>
<name>Asics Gel-Kayano</name>
<category>Running</category>
</shoe>
</shoes>
<clothes>
<shorts>
<name>North Face</name>
<category>Wildlife</category>
</shorts>
<shorts>
<name>Adidas Running Shorts</name>
<category>Running</category>
</shorts>
</clothes>
Based on the value of the category element I want to, for each product, list similar products, that is, other products having the same category in the input xml, like this:
<output>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
</output>
This doesn't seem to be a grouping problem as such. If I understand correctly, you want to do something like:
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:key name="product-by-category" match="*" use="category" />
<xsl:template match="/products">
<output>
<xsl:for-each select="*/*">
<forSale>
<item>
<xsl:value-of select="name" />
</item>
<xsl:for-each select="key('product-by-category', category) except .">
<similarItem>
<xsl:value-of select="name" />
</similarItem>
</xsl:for-each>
</forSale>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<forSale>
<item>Ecco City</item>
</forSale>
<forSale>
<item>Timberland Forest</item>
<similarItem>North Face</similarItem>
</forSale>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
<forSale>
<item>North Face</item>
<similarItem>Timberland Forest</similarItem>
</forSale>
<forSale>
<item>Adidas Running Shorts</item>
<similarItem>Asics Gel-Kayano</similarItem>
</forSale>
</output>

Copy comments that are after the end of root tag using XSLT

I have an XML which needs to be reordered and stored in another file. I have done an xslt for that and it works fine.
However if there are comments after the xml ends then these comments are not copied. I need an xslt statement that copies comments which are present
after the root tag ends
Below is a code that explains the following
Original XML
<Company>
<Employee id="100" Name="John" >
<Salary value="15000"/>
<Qualification text="Engineering">
<State name="Kerala" code="02">
<Background text="Indian">
</Employee>
</Company>
<!--This file contains Employee information-->
<!--Please refer the file to get information about an employee-->
XSLT Transformation code
<?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 indent="yes" omit-xml-declaration="yes" method="xml" />
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Employee">
<xsl:copy>
<xsl:apply-templates select="Qualification"/>
<xsl:apply-templates select="Salary" />
<xsl:apply-templates select="Background"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output Obtained
<?xml version="1.0" encoding="utf-8"?>
<Company>
<Employee>
<Qualification text="Engineering" />
<Salary value="15000" />
<Background text="Indian" />
</Employee>
</Company>
Output Required
<?xml version="1.0" encoding="utf-8"?>
<Company>
<Employee>
<Qualification text="Engineering" />
<Salary value="15000" />
<Background text="Indian" />
</Employee>
</Company>
<!--This file contains Employee information-->
<!--Please refer the file to get information about an employee-->
There's nothing wrong with your transformation. I ran the same XSLT over the same XML (corrected to make it well-formed) using xsltproc and I get the correct output including the trailing comments (though they have lost their original indentation and spacing due to the strip-space and the indent="yes" in the stylesheet)
<Company>
<Employee>
<Qualification text="Engineering"/>
<Salary value="15000"/>
<Background text="Indian"/>
</Employee>
</Company><!--This file contains Employee information-->
<!--Please refer the file to get information about an employee-->
It looks like whatever processor or XML parser you're using (presumably a Microsoft one judging by the xmlns:msxsl="urn:schemas-microsoft-com:xslt") is ignoring comments after the end tag of the document element.

Comma separation

I have a set of productids like (123565,589655,45585,666669,5888) I want to put comma in front and back of these set of ids Like(,123565,589655,45585,666669,5888,)..
How can i write the XSLT code for doing this?
Just use:
<xsl:text>,</xsl:text><xsl:value-of select="$yourSequence"
separator=","/><xsl:text>,</xsl:text>
Depends terribly on your input XML file and what you want the output to look like. In any case, since you're using XSLT 2.0, you can use the string-join() function.
Let's say you have an input XML file that looks like this:
<products>
<product>
<name>Product #1</name>
<id>123565</id>
</product>
<product>
<name>Product #1</name>
<id>589655</id>
</product>
<product>
<name>Product #1</name>
<id>45585</id>
</product>
</products>
You could have a stylesheet like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text" indent="yes"/>
<xsl:variable name="SEPARATOR" select="','"/>
<xsl:template match="/">
<!--
Join the values of each products/product/id element with $SEPARATOR; prepend
and append the resulting string with commas.
-->
<xsl:value-of
select="concat($SEPARATOR, string-join((products/product/id),
$SEPARATOR), $SEPARATOR)"/>
</xsl:template>
</xsl:stylesheet>
Which would produce the following output:
,123565,589655,45585,
If you edit your question to include your input XML and what you want your output XML to look like, I can modify my answer accordingly.

Xpath Select values from one node that doesn't exist in another

<my-courses>
<courses-reqd>
<details>
<subject>Math</subject>
</details>
<details>
<subject>Econ</subject>
</details>
<details>
<subject>Geog</subject>
</details>
<details>
<subject>Phys</subject>
</details>
<detail>
<subject>Zool</subject>
</details>
</courses-reqd>
<courses-taken>
<details>
<subject>Math</subject>
</details>
<details>
<subject>Econ</subject>
</details>
<detail>
<subject>Zool</subject>
</details>
</courses-taken>
I have provided sample XML and want to know how to get the values the are not in the courses-taken but are in the courses-reqd by using XPATH. In the case provided, the answer would be Geog and Phys as they do not exist in course-taken.
<xsl:apply-templates select="my-courses\courses-reqd | not(my-courses\courses-taken)"/>
Is this possible and if so, how would I go abouts doing this. Any help would be appreciated.
Thanks
The simple answer here is to do this:
<xsl:apply-templates
select="/my-courses/courses-reqd/detail
[not(subject = /my-courses/courses/taken/details/subject)]" />
A better (more efficient) approach is to use keys.
This would be added towards the top of your XSLT:
<xsl:key name="kTaken" match="courses-taken//subject" use="."/>
And then this would be used in one of your templates:
<xsl:apply-templates
select="/my-courses/courses-reqd/detail[not(key('kTaken', subject))]" />
When this XSLT is run on your sample input (after the sample input has been corrected to consistently use elements named "details" and not "detail":
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kTaken" match="courses-taken//subject" use="."/>
<xsl:template match="/">
<root>
<xsl:apply-templates
select="/my-courses/courses-reqd/details[not(key('kTaken', subject))]" />
</root>
</xsl:template>
<xsl:template match="details">
<course>
<xsl:value-of select="subject"/>
</course>
</xsl:template>
</xsl:stylesheet>
The result is:
<root>
<course>Geog</course>
<course>Phys</course>
</root>