Remove parent node if child node is empty - xslt

I am trying to transform a given XML using xslt. The caveat is that I would have to delete a parent node if a given child node is not present. I did do some template matching, but I am stuck. Any help would be appreciated.
The input xml :
<?xml version="1.0" encoding="UTF-8"?>
<main>
<item>
<value>
<item>
<value>ABC</value>
<key>test1</key>
</item>
<item>
<value>XYZ</value>
<key>test2</key>
</item>
<item>
<value></value>
<key>test3</key>
</item>
</value>
</item>
<item>
<value />
<key>test4</key>
</item>
<item>
<value>PQR</value>
<key>test5</key>
</item>
</main>
Expected Output:
<?xml version="1.0" encoding="UTF-8"?>
<main>
<item>
<value>
<item>
<value>ABC</value>
<key>test1</key>
</item>
<item>
<value>XYZ</value>
<key>test2</key>
</item>
</value>
</item>
<item>
<value>PQR</value>
<key>test5</key>
</item>
</main>
The issue is if I use template matching e.g.
<xsl:template match="item[not(value)]"/> as mentioned in deleting the parent node if child node is not present in xml using xslt, then it completely removes everything as main/item/value is also empty.
What I need is remove if element is empty but only do if element has no child element.

You should first start with the XSLT identity template
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Then, all you need is a template that matches the item element where all descendent leaf value elements are empty.
<xsl:template match="item[not(descendant::value[not(*)][normalize-space()])]" />
So, the template matches it, but doesn't output it.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="item[not(descendant::value[not(*)][normalize-space()])]" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

I think you want to remove the element of it has no children at all (whether those children be elements or text nodes). Try inserting this template:
<xsl:template match="item">
<xsl:if test="exists(value/node())">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:if>
</xsl:template>

If I read this correctly, you want to do:
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="item[not(value[node()])]"/>
</xsl:stylesheet>
This will remove any item that does not have a value child with some content.

Related

copy a descendant node and place it after the current node

In the below xml. I want to do following
match wrapper/item whose child or descendant is <olr>.
If found, copy <olr> and all its following-siblings, and paste it
after the current node i.e. end of wrapper/item. (other word,<olr> and its following-siblings will always be direct child of <wrapper>)
rest should be copied as it is.
Input XML:
<root>
<wrapper>
<item>
<item>1.1</item>
<item>
<olr>outlier1</olr>
</item>
</item>
<item>
<item>2.1</item>
<item>
<item>
<item>
<item>preceedingsibling1</item>
<item>preceedingsibling2</item>
<olr>outlier2</olr>
<item>followingsibling1</item>
<item>followingsibling2</item>
</item>
</item>
</item>
</item>
<item>
<item>3.1</item>
<item>
<item>
<item>3.3.1</item>
</item>
</item>
</item>
</wrapper>
<root>
<wrapper>
<item>
<item>1.1</item>
<item>
</item>
</item>
<olr>outlier1</olr>
<item>
<item>2.1</item>
<item>
<item>
<item>
<item>preceedingsibling1</item>
<item>preceedingsibling2</item>
</item>
</item>
</item>
</item>
<olr>outlier2</olr>
<item>followingsibling1</item>
<item>followingsibling2</item>
<item>
<item>3.1</item>
<item>
<item>
<item>3.3.1</item>
</item>
</item>
</item>
</wrapper>
I am trying something:
<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:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[descendant::olr]">
<xsl:apply-templates select="node() except descendant::olr"/>
<!-- ? not sure what to do here -->
</xsl:template>
The below XSLT-2.0 solution would do this:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<!-- identity transform template with mode='olr' -->
<xsl:template match="#* | node()" mode="olr">
<xsl:copy>
<xsl:apply-templates select="#*, node()[1]" mode="olr"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]" mode="olr"/>
</xsl:template>
<!-- identity transform template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!-- copy the olr and it's following-elements in the current element -->
<xsl:template match="wrapper/item[descendant::olr]">
<xsl:copy>
<xsl:apply-templates select="#*, node()[1]" />
</xsl:copy>
<xsl:apply-templates select="descendant::olr[not(preceding-sibling::olr)]" mode="olr"/>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<!-- "do nothing for olr" template -->
<xsl:template match="olr[ancestor::item/parent::wrapper]"/>
</xsl:stylesheet>
There are two identity transform templates(1st and 2nd) to copy the elements as-is, recursively.
The third template matches the wrapper/item with descendant::olr and specially processes olr(and its following-siblings) by copying it as its own following-sibling.
The fourth template is to do nothing for olr in the normal process.
One way to achieve this is with
<?xml version="1.0" encoding="UTF-8" ?>
<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="wrapper/item[descendant::olr]">
<xsl:next-match/>
<xsl:copy-of select="descendant::olr/(., following-sibling::node())"/>
</xsl:template>
<xsl:template match="wrapper/item//olr | wrapper/item//node()[preceding-sibling::olr]"/>
</xsl:transform>
http://xsltransform.net/bFWR5Fg. I am not sure what happens if you have several olr elements.

how to modify one xml input using variable from second xml input in XSLT?

My question is how to change values in <color> in sample1.xml based on the same <id> in sample2.xml
sample1.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<id>1</id>
<color>red</color>
</item>
<item>
<id>2</id>
<color>blue</color>
</item>
<item>
<id>3</id>
<color>green></color>
</item>
</root>
sample 2.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<id>1</id>
<color>yellow</color>
</item>
<item>
<id>3</id>
<color>white</color>
</item>
</root>
expected output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<id>1</id>
<color>yellow</color>
</item>
<item>
<id>2</id>
<color>blue</color>
</item>
<item>
<id>3</id>
<color>white></color>
</item>
</root>
I only know how to copy entire sample1.xml to output, but I don't know how to remember ids from sample2.xml, and by that values make changes to sample1.
Don't know if is possible, but probably I must use variables on some unknown way.
Here is my code:
<?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" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"
version="2.0">
<xsl:output method="xml" indent="yes" media-type="text/xml" />
<xsl:param name="sample1"/>
<xsl:param name="sample1_xml" select="saxon:parse($sample1)"/>
<xsl:param name="sample2"/>
<xsl:param name="sample2_xml" select="saxon:parse($sample2)"/>
<xsl:template match="/" name="initial">
<xsl:apply-templates select="$sample1_xml/node()"/> <!-- this is only for copying entire sample1 file -->
</xsl:template>
<!-- copy all nodes and values -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I really don't have idea what is the right way to do that, because I am new to XSLT 2.0. Any help will be much appreciated.
I was beaten to this by Martin Honnen, but here's my solution:
<?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" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"
version="2.0">
<xsl:output method="xml" indent="yes" media-type="text/xml"/>
<xsl:param name="sample1"/>
<xsl:param name="sample1_xml" select="saxon:parse($sample1)"/>
<xsl:param name="sample2"/>
<xsl:param name="sample2_xml" select="saxon:parse($sample2)"/>
<xsl:template match="/" name="initial">
<xsl:apply-templates select="$sample1_xml/node()"/>
<!-- this is only for copying entire sample1 file -->
</xsl:template>
<!-- copy all nodes and values -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root/item/color">
<xsl:param name="id" select="parent::item/id/text()"/>
<xsl:copy>
<xsl:choose>
<xsl:when test="$sample2_xml/root/item[id=$id]/color">
<xsl:value-of select="$sample2_xml/root/item[id=$id]/color/text()"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="text()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
Define a key <xsl:key name="id" match="item" use="id"/> and then add a template
<xsl:template match="item[key('id', id, $sample2_xml)]/color">
<xsl:copy-of select="key('id', ../id, $sample2_xml)/color"/>
</xsl:template>
So the complete sample is
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs saxon"
xmlns:saxon="http://saxon.sf.net/"
version="2.0">
<xsl:output method="xml" indent="yes" media-type="text/xml" />
<xsl:param name="sample1" as="xs:string"><![CDATA[<root>
<item>
<id>1</id>
<color>red</color>
</item>
<item>
<id>2</id>
<color>blue</color>
</item>
<item>
<id>3</id>
<color>green></color>
</item>
</root>]]></xsl:param>
<xsl:param name="sample1_xml" select="saxon:parse($sample1)"/>
<xsl:param name="sample2" as="xs:string"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<id>1</id>
<color>yellow</color>
</item>
<item>
<id>3</id>
<color>white</color>
</item>
</root>]]></xsl:param>
<xsl:param name="sample2_xml" select="saxon:parse($sample2)"/>
<xsl:key name="id" match="item" use="id"/>
<xsl:template match="/" name="initial">
<xsl:apply-templates select="$sample1_xml/node()"/> <!-- this is only for copying entire sample1 file -->
</xsl:template>
<!-- copy all nodes and values -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[key('id', id, $sample2_xml)]/color">
<xsl:copy-of select="key('id', ../id, $sample2_xml)/color"/>
</xsl:template>
</xsl:stylesheet>

how to check and replace the null date using xslt

I want to check if my expiry date is null than i want to take the value from CreationDate.
My XML is like
<CreationDate>2017-03-18</CreationDate> <ExpiresDate>20170318</ExpiresDate>
and in my xsl
<xsl:element name="NewDeliveryDueDate">
<xsl:call-template name="FormatDate">
<xsl:with-param name="DateTime" select="Product/ExpiresDate"/>
</xsl:call-template>
</xsl:element>
&comma;
Please suggest.
Your question is incomplete. Perhaps this could work for you:
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="ExpiresDate[not(string())]">
<xsl:copy>
<xsl:value-of select="translate(../CreationDate, '-', '')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this is applied to the following example input:
<root>
<item>
<CreationDate>2017-03-18</CreationDate>
<ExpiresDate>20170318</ExpiresDate>
</item>
<item>
<CreationDate>2017-03-09</CreationDate>
<ExpiresDate/>
</item>
</root>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<CreationDate>2017-03-18</CreationDate>
<ExpiresDate>20170318</ExpiresDate>
</item>
<item>
<CreationDate>2017-03-09</CreationDate>
<ExpiresDate>20170309</ExpiresDate>
</item>
</root>

XSLT Add nested element to the last node based on the existing element

I have the following XML, the XML has multiple occurance of 'item' elements, and there will be single occurance of 'info' element
<?xml version="1.0" encoding="utf-8" ?>
<root xmlns="http://temo.com/tempe.xsd">
<di>
<md>2013-07-09T09:43:00</md>
</di>
<list>
<item>
<Name>test</Name>
<section block1="true">
<block1>
<move>1</move>
<info>
<item1>test item 1</item1>
<item2>false</item2>
<item3>1</item3>
</info>
</block1>
<block2>
...
</block2>
</section>
</item>
</list>
<option>
...
</option>
</root>
and I want to convert it to the below format, i.e., if 'move' element is present then add new elements in the last position of 'info' element should be created
<item4>
<item5>1</item5>
</item4>
<?xml version="1.0" encoding="utf-8" ?>
<root xmlns="http://temo.com/tempe.xsd">
<di>
<md>2013-07-09T09:43:00</md>
</di>
<list>
<item>
<Name>test</Name>
<section block1="true">
<block1>
<move>1</move>
<info>
<item1>test item 1</item1>
<item2>false</item2>
<item3>1</item3>
<item4>
<item5>1</item5>
</item4>
</info>
</block1>
<block2>
...
</block2>
</section>
</item>
<item>
...
</item>
</list>
<option>
...
</option>
</root>
I am using the following XSLT to convert to the above format
<?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" encoding="utf-8"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/info/*[position()=last()]">
<xsl:copy>
<xsl:choose>
<xsl:when test="/section/block/move">
<!--Add new element item4-->
<xsl:element name="item4">
<xsl:element name="item5">
<xsl:value-of select="section/block/move"/>
</xsl:element>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="identity" />
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Could you please Help me to find the issue in the XSLT?
I am new to XSLT
Thank you :)
You have a default namespace declaration on the input XML and the output XML also needs that namespace for the elements to be inserted so you need
xmlns:tp="http://temo.com/tempe.xsd"
on your xsl:stylesheet root element, then you need to adapt your paths and patterns to use that prefix e.g. instead of info you need tp:info and instead of section/block/move you need tp:section/tp:block/tp:move and so on.
The paths in your XSLT sample seem wrong however you use /section/block/move but your root element is root so /section will never select anything, even if you add the namespace prefix. And your path has block, the input has block1.
Instead of trying to fix your code I wrote a new stylesheet, it is
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://temo.com/tempe.xsd"
xmlns:tp="http://temo.com/tempe.xsd"
exclude-result-prefixes="tp"
version="1.0">
<xsl:param name="to-insert" xml:space="preserve">
<item4>
<item5><xsl:value-of select="//tp:section/tp:block1/tp:move"/></item5>
</item4>
</xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="tp:item[.//tp:move[. = 1]]//tp:info">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:copy-of select="$to-insert"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It outputs
<root xmlns="http://temo.com/tempe.xsd">
<di>
<md>2013-07-09T09:43:00</md>
</di>
<list>
<item>
<Name>test</Name>
<section block1="true">
<block1>
<move>1</move>
<info>
<item1>test item 1</item1>
<item2>false</item2>
<item3>1</item3>
<item4 xmlns:tp="http://temo.com/tempe.xsd">
<item5>1</item5>
</item4>
</info>
</block1>
<block2>
...
</block2>
</section>
</item>
</list>
<option>
...
</option>
</root>

Insert XML element using XSLT in exisiting node

I have the following XML document:
<root someAttribute="someValue" />
Now I want to add a tag using XSLT, so that the document will look like this:
<root someAttribute="someValue">
<item>TEXT</item>
</root>
If I repeat to use the XSLT once more it should just add another item:
<root someAttribute="someValue">
<item>TEXT</item>
<item>TEXT</item>
</root>
It sound's so easy, does it not? Here is the best I got after trying a ton of things:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" >
<xsl:param name="message" />
<xsl:output method="xml" encoding="utf-8"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="*"/>
<item>
<xsl:value-of select="$message" />
</item>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It does /nearly/ what I have asked for, except that it "forgets" the attributes of the root element. I have found a number of other solutions here on stackoverflow and elsewhere that have in common with my solution that they loose the attributes of the root element. How can I fix that?
You're currently transforming only child nodes, not attributes.
<xsl:template match="root">
<xsl:copy>
<xsl:copy-of select="node()|#*"/> <!-- now does attrs too -->
<item>
<xsl:value-of select="$message" />
</item>
</xsl:copy>
</xsl:template>