Block XML elements if underneath item is missing or blank - xslt-2.0

I have an xml file which is generated by a process. This xml may have many products and Items under each product. However, there are scenarios when
product can have no items underneath as shown in the example below. In this situation, I want xml not to generate products where there is not item.
I want xsl script which should be able to do this. Can anyone please help?
The input xml file is
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product>
<prod id>P16754</prod id>
<product-status>CREATED</product-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
<items/>
<created-on>2016-08-12T11:30:00</created-on>
<created-by>Administrator</created-by>
<last-changed-on>2016-08-04T17:34:00</last-changed-on>
<last-changed-by>ap0712</last-changed-by>
<delete>false</delete>
</product>
<product>
<prod id>P16754</prod id>
<product-status>CREATED</product-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
<items>
<item>
<item id>i16754</item id>
<item-status>CREATED</item-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
</item>
<items/>
<created-on>2016-08-12T11:30:00</created-on>
<created-by>Administrator</created-by>
<last-changed-on>2016-08-04T17:34:00</last-changed-on>
<last-changed-by>ap0712</last-changed-by>
<delete>false</delete>
</product>
</products>
Since the first product does not have item so this product as well as item should be removed from the output xml. The output should be
<products>
<product>
<prod id>P16754</prod id>
<product-status>CREATED</product-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
<items>
<item>
<item id>i16754</item id>
<item-status>CREATED</item-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
</item>
<items/>
<created-on>2016-08-12T11:30:00</created-on>
<created-by>Administrator</created-by>
<last-changed-on>2016-08-04T17:34:00</last-changed-on>
<last-changed-by>ap0712</last-changed-by>
<delete>false</delete>
</product>
</products>
The code I am using to achieve based upon suggestions here is
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Prod ID[not(items/item)]"/>
</xsl:stylesheet>
I see that it deletes the product and item if it is blank but I am getting the following in the output for example xslt code is coming in the output
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Prod ID[not(items/item)]"/>
</xsl:stylesheet>

Any time you want to transform a document and only make some changes you start with the identity transformation template
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
and then you add templates for those elements you want to transform. If you want to delete an element then you add an empty template matching that element, as you want to remove all product elements not having item elements you use
<xsl:template match="product[not(items/item)]"/>
So all you need is
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="product[not(items/item)]"/>
</xsl:transform>
which transforms the corrected input
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product>
<prod-id>P16754</prod-id>
<product-status>CREATED</product-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
<items/>
<created-on>2016-08-12T11:30:00</created-on>
<created-by>Administrator</created-by>
<last-changed-on>2016-08-04T17:34:00</last-changed-on>
<last-changed-by>ap0712</last-changed-by>
<delete>false</delete>
</product>
<product>
<prod-id>P16754</prod-id>
<product-status>CREATED</product-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
<items>
<item>
<item-id>i16754</item-id>
<item-status>CREATED</item-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
</item>
</items>
<created-on>2016-08-12T11:30:00</created-on>
<created-by>Administrator</created-by>
<last-changed-on>2016-08-04T17:34:00</last-changed-on>
<last-changed-by>ap0712</last-changed-by>
<delete>false</delete>
</product>
</products>
into the output
<?xml version="1.0" encoding="UTF-8"?><products>
<product>
<prod-id>P16754</prod-id>
<product-status>CREATED</product-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
<items>
<item>
<item-id>i16754</item-id>
<item-status>CREATED</item-status>
<validation-status>Valid</validation-status>
<duplication-status>Unique</duplication-status>
<content-status>New</content-status>
</item>
</items>
<created-on>2016-08-12T11:30:00</created-on>
<created-by>Administrator</created-by>
<last-changed-on>2016-08-04T17:34:00</last-changed-on>
<last-changed-by>ap0712</last-changed-by>
<delete>false</delete>
</product>
</products>
online at http://xsltransform.net/bFWR5DD.

Related

Generating a sequence that will sort in alphabetic order

Is there a built-in or simple way to generate a sequence of values that will sort in alphabetical order?
As an example, the following item elements have a child sequence element intended for sorting on. The data is being generated for another system that requires this information, since it does not work with the document ordering.
<list>
<item>
<sequence>1</sequence>
</item>
<item>
<sequence>2</sequence>
</item>
<item>
<sequence>3</sequence>
</item>
<item>
<sequence>4</sequence>
</item>
...
<item>
<sequence>14</sequence>
</item>
<item>
<sequence>15</sequence>
</item>
Instead of sorting numerically, the system sorts alphabetically and it cannot change this behavior. As a result the items are sorted as 1,10,11,12,13,14,15,2,3,4,5,6,7,8,9
Furthermore, the actual number of items is unbounded, so there needs to be general way of generating an alphabetic sequence that will sort in the right order. That said, the number of items should be relatively small, under 1000.
This is what I have so far:
<xsl:variable name="idchars" as="xs:string *"
select="
('0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z')
"
/>
<xsl:variable name="idlen" as="xs:integer" select="count($idchars)" />
<xsl:function name="f:genId" as="xs:string">
<xsl:param name="pSeq" as="xs:integer" />
<xsl:variable name="vLen" as="xs:integer" select="$pSeq idiv $idlen" />
<xsl:value-of>
<xsl:for-each select="1 to $vLen">
<xsl:sequence select="$idchars[$idlen]" />
</xsl:for-each>
<xsl:sequence select="$idchars[$pSeq mod $idlen]" />
</xsl:value-of>
</xsl:function>
You could use format-number() in order to generate a left-padded sequence of numbers with a variable length of leading zeros, then convert each of those digits into a letter using codepoints-to-string(), and then join them together with string-join():
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:f="local">
<xsl:output indent="yes"/>
<xsl:function name="f:genId" as="xs:string">
<xsl:param name="pSeq" as="xs:integer" />
<xsl:param name="digits" as="xs:integer"/>
<xsl:variable name="picture" select="string-join( ((1 to $digits) ! '0'), '')" />
<xsl:variable name="padded-string" select="format-number($pSeq, $picture)" as="xs:string"/>
<xsl:variable name="padded-letter-seq" select="(1 to string-length($picture)) ! substring($padded-string, ., 1) ! codepoints-to-string(xs:integer(.) + 65)" as="xs:string*"/>
<xsl:sequence select="string-join($padded-letter-seq, '')"/>
</xsl:function>
<xsl:function name="f:genId" as="xs:string">
<xsl:param name="pSeq" as="xs:integer"/>
<xsl:sequence select="f:genId($pSeq, 4)"/>
</xsl:function>
<xsl:template match="/">
<xsl:sequence select="f:genId(3)"/>
<xsl:sequence select="f:genId(3,5)"/>
</xsl:template>
</xsl:stylesheet>
Just use the <xsl:sort> xslt element, simply like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="*">
<xsl:sort select="sequence"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<list>
<item>
<sequence>1</sequence>
</item>
<item>
<sequence>2</sequence>
</item>
<item>
<sequence>3</sequence>
</item>
<item>
<sequence>4</sequence>
</item>
...
<item>
<sequence>14</sequence>
</item>
<item>
<sequence>15</sequence>
</item>
</list>
the wanted, correct result is produced:
<list>
<item>
<sequence>1</sequence>
</item>
<item>
<sequence>14</sequence>
</item>
<item>
<sequence>15</sequence>
</item>
<item>
<sequence>2</sequence>
</item>
<item>
<sequence>3</sequence>
</item>
<item>
<sequence>4</sequence>
</item>
</list>
Do note:
This:
<xsl:sort select="sequence"/>
is equivalent to:
<xsl:sort select="sequence" data-type="text"/>
as "text" is the default value for the data-type attribute.
If it is necessary to sort numerically, one should explicitly use:
<xsl:sort select="sequence" data-type="number"/>

Unable to group the element value using group by in XSLT

I have an XML lie below:
<Products>
<Product1>
<Reference>000510143244</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product1>
<Reference>45768799322</Reference>
<Value1>543</Value1>
</Product1>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
<Value1>543</Value1>
</Product2>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
<Value1>543</Value1>
</Product2>
</Products>
I want to get only first value of the Product1 reference...but I am unable to get that.Also it is not mandatory that Product 1 will always be the first element in input xml.
Any suggestions how can I get that?
I have tried to get the value as :
<xsl:template match="//Products">
<xsl:variable name="Product1">
<xsl:for-each-group select="/Reference" group-by="/Reference">
<xsl:copy-of select="." />
</xsl:for-each-group>
</xsl:variable>
</xsl:template>
Update:1
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
My expected output is :000510143244
To get the first occurrence of <Products> who has <Product1>, you might need to match the parent tag or root tag of your input XML.
Assuming your input as below:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<root>
<Products>
<Product2>
<Reference>35726318090</Reference>
</Product2>
</Products>
<Products>
<Product1>
<Reference>02563899183</Reference>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
</Product1>
</Products>
<Products>
<Product1>
<Reference>000510143244</Reference>
</Product1>
</Products>
<Products>
<Product2>
<Reference>35726318090</Reference>
</Product2>
</Products>
</root>
The following code can give you the result:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="root">
<xsl:for-each-group select="Products/Product1" group-by="Reference">
<xsl:copy-of select="current-group()[1]" />
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
See the demo: https://xsltfiddle.liberty-development.net/3NJ38Zx
Update:
OR you can simply achieve it by following code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Update 2:
<xsl:template match="root">
<xsl:variable name="ref">
<xsl:for-each-group select="Products/Product1" group-by="Reference">
<xsl:copy-of select="current-group()[1]/Reference" />
</xsl:for-each-group>
</xsl:variable>
<xsl:value-of select="$ref"/>
</xsl:template>
https://xsltfiddle.liberty-development.net/3NJ38Zx/1
Update 3:
You cannot assign a value to global variable from a template.
There are two ways to get what you required.
1) Create a global variable as below which will take first <Products> whose child element is <Product1> and will display it's Reference
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:variable name="ref" select="root/Products[child::Product1][1]/Product1/Reference" />
<xsl:template match="/">
<xsl:value-of select="$ref" />
</xsl:template>
</xsl:stylesheet>
2) You can modify the template as below to get the result.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="Products[child::Product1][1]/Product1/Reference">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>

Dynamically replacing substring in an given data(XML to fixed length)

Actually I have started with my XSLT work recently, I am facing difficulty in solving one of the requirement.
I am trying to fetch an substring from DATA element in the mentioned input i,e is ECHO and OKAY these codes need to be replaced with the values present under CODE/ECHO and CODE/OKAY in the same input. I had tried storing the substring in a variable and as the variable value and tag value would be same, I have tried to fetch that in . But its not working.
Is it that we cant use variables in the XPATHS or there is some other representation which needs to be used? Could anyone please help me with this.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output omit-xml-declaration="yes" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:variable name="String" select="substring(DATA, (string-length(substring(DATA,0,77)) + 1), 4)" />
<xsl:variable name="String1" >
<xsl:value-of select="Root/CODES/$String" />
</xsl:variable>
<xsl:value-of select="$break" />
<xsl:value-of select="$String1" />
</xsl:for-each>
</xsl:template>
Input:
<?xml version='1.0' encoding='utf-8'?>
<ROOT>
<INPUT>
<I_FILENAME>ERES</I_FILENAME>
</INPUT>
<CODES>
<ECHO>A1</ECHO>
<OKAY>A2</OKAY>
</CODES>
<TABLES>
<T_ER>
<item>
<DATA> HEADERERESRGCITIS220190301124112000000RGERSD46</DATA>
</item>
<item>
<DATA>000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000ECHO00400300000000000XXXXXX 000{ P 2018-11-05</DATA>
</item>
<item>
<DATA>000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000OKAY00400300000000000XXXXXX 000{ P 2018-11-05</DATA>
</item>
<item>
<DATA>TRAILERERESRGCITIS220190301124112000000001570000</DATA>
</item>
</T_ER>
</TABLES>
</ROOT>
EXPECTED OUT PUT:
HEADERERESRGCITIS220190301124112000000RGERSD46
000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000A100400300000000000XXXXXX 000{ P 2018-11-05
000000 ABCD EF 0000000000 2018-11-060000000000EF 000000000000010000A200400300000000000XXXXXX 000{ P 2018-11-05
<xsl:template match="INPUT|CODES">
</xsl:template>
<xsl:template match="TABLES">
<xsl:variable name="break" select="'
'" />
<xsl:for-each select="T_ER/item"><xsl:value-of select="$break"></xsl:value-of>
<xsl:value-of select="DATA"/>
</xsl:for-each>
</xsl:template>

Change tag name while transformation using XSLT

I am working on XML transformation using XSLT and facing issue while renaming tag. Please find below detail for the same. My transformed XML should have BookName instead of Name and LibraryName instead of Name tag.
Input XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Catalog xmlns="http://example.com">
<Books>
<Book>
<Name>Wise Otherwise</Name>
<author>Great Expectations</author>
</Book>
<Book>
<Name>Rich Dad Poor Dad</Name>
<author>Orange</author>
</Book>
</Books>
<libraries>
<library>
<Name> Forsyth </Name>
<city> Cumming </city>
</library>
<library>
<Name> COBB </Name>
<city> Marietta </city>
</library>
</libraries>
</Catalog>
Expected XML After Transformation
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Catalog xmlns="http://example.com">
<Books>
<Book>
<BookName>Wise Otherwise</BookName>
<author>Great Expectations</author>
</Book>
<Book>
<Name>Rich Dad Poor Dad</Name>
<author>Orange</author>
</Book>
</Books>
<libraries>
<library>
<LibraryName> Forsyth </LibraryName>
<city> Cumming </city>
</library>
<library>
<LibraryName> COBB </LibraryName>
<city> Marietta </city>
</library>
</libraries>
</Catalog>
My XSLT for the same
<?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">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns:Name">
<xsl:for-each select="Catalog/Books/Book/Name">
<BookName>
<xsl:apply-templates />
</BookName>
</xsl:for-each>
<xsl:for-each select="Catalog/libraries/library/Name">
<LibraryName>
<xsl:apply-templates />
</LibraryName>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You can use this XSLT for reference:
<?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" xmlns="http://example.com" exclude-result-prefixes="ns">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns:Book/ns:Name">
<BookName>
<xsl:apply-templates/>
</BookName>
</xsl:template>
<xsl:template match="ns:library/ns:Name">
<LibraryName>
<xsl:apply-templates/>
</LibraryName>
</xsl:template>
</xsl:stylesheet>
I declared the namespace with and without prefix. Therefore all new created elements will belong to the default namespace. Also excluded the prefixed one since it is not used.
You can write several templates for matching the nodes you want to change. For example read this tutorial: http://www.xmlplease.com/xsltidentity

Change XML element name using XSLT

I am trying to change XML node name but it doesn't allow me to do so. In my below code I I have two templates 1. Change Node name 2.Create parent node for DocumentReference. Please see my XML and XSLT.
My XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<DataArea>
<PurchaseOrder>
<PurchaseOrderLine>
<DocumentReference>
<DocumentID>
<ID>23423</ID>
</DocumentID>
</DocumentReference>
<DocumentReference>
<DocumentID>
<ID>23424</ID>
</DocumentID>
</DocumentReference>
<Item>
<CustomerItemID>
<!-- ArtNr -->
<ID>444</ID>
</CustomerItemID>
</Item>
<Quantity unitCode="PCE">17.3</Quantity>
</PurchaseOrderLine>
</PurchaseOrder>
</DataArea>
Expected Result
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<DataArea>
<PurchaseOrder>
<POL>
<DocumentReference>
<DocumentID>
<ID>23423</ID>
</DocumentID>
</DocumentReference>
<DocumentReference>
<DocumentID>
<ID>23424</ID>
</DocumentID>
</DocumentReference>
<Item>
<CustomerItemID>
<!-- ArtNr -->
<ID>444</ID>
</CustomerItemID>
</Item>
<Quantity unitCode="PCE">17.3</Quantity>
</POL>
</PurchaseOrder>
</DataArea>
My XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="PurchaseOrderLine">
<POL>
<xsl:apply-templates />
</POL>
</xsl:template>
<xsl:template match="PurchaseOrderLine">
<xsl:copy>
<Kiran>
<xsl:apply-templates select="#*|DocumentReference"/>
</Kiran>
<xsl:apply-templates select="#*|Item|Quantity"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Then I think you want the template to look like
<xsl:template match="PurchaseOrderLine">
<POL>
<xsl:apply-templates select="#*"/>
<Kiran>
<xsl:apply-templates select="DocumentReference"/>
</Kiran>
<xsl:apply-templates select="node() except DocumentReference" />
</POL>
</xsl:template>

Resources