increment counter in XSLT 1.0 - xslt

This is the input XML:
<Move-Afile>
<Afile>
<Item>
<PackNumber>1234</PackNumber>
</Item>
<Item>
<PackNumber>567</PackNumber>
</Item>
<Item>
<PackNumber>567</PackNumber>
</Item>
<Item>
<PackNumber>126</PackNumber>
</Item>
<Item>
<PackNumber>876</PackNumber>
</Item>
</Afile>
</Move-Afile>
<Item> is an unbounded element which contains <PackNumber> as a child element. For each pack number we need to increment the counter variable, but here one condition is present like if previous <PackNumber> is equal to current <PackNumber> we have to ignore the counter (there is no need to increment) like below output.
Inside the for-each, can we get the counter like below XSLT sample?
This is my XSLT template
<xsl:template match="/">
<A>
<target>
<xsl:for-each select="/inp1:Move-Afile/inp1:Afile/inp1:Item/inp1:PalletNumber">
<xsl:variable name="count">
<!-- get the count here-->
</xsl:variable>
<counter>$count</counter>
<PNumber><xsl:value-of select="."/></PNumber>
</xsl:for-each>
</target>
</A>
</xsl:template>
This is the XML output:
<A>
<target>
<Item>
<counter>1</counter><!-- if previous <PackNumber> is not equal to current <PackNumber> increment the count-->
<PNumber>1234</PNumber>
</Item>
<Item>
<counter>2</counter><!-- if previous <PackNumber> is not equal to current <PackNumber> increment the count-->
<PNumber>567</PNumber>
</Item>
<Item><!-- if previous <PackNumber> is equal to current <PackNumber> ignore the count-->no need to increment
<PNumber>567</PNumber>
</Item>
<Item>
<counter>3</counter><!-- if previous <PackNumber> is not equal to current <PackNumber> increment the count-->
<PNumber>126</PNumber>
</Item>
<Item><!-- if previous <PackNumber> is equal to current <PackNumber> ignore the count-->no need to increment
<PNumber>126</PNumber>
</Item>
</target>
</A>
XML output 2:
<?xml version="1.0"?>
<A>
<target>
<counter>1</counter>
<PNumber>1234</PNumber>
<counter>2</counter>
<PNumber>567</PNumber>
<!-- IF PNumber is equal we have to ignore the Total loop -->
<counter>3</counter>
<PNumber>126</PNumber>
<counter>4</counter>
<PNumber>876</PNumber>
</target>
</A>

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 indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" />
<xsl:template match="PackNumber">
<xsl:if test="not(preceding::PackNumber =.)" >
<!-- if previous <PackNumber> is not equal to current <PackNumber> t-->
<counter>
<xsl:value-of select="count(preceding::PackNumber[not(preceding::PackNumber= .)])+1"/>
</counter>
</xsl:if>
<PNumber>
<xsl:value-of select="."/>
</PNumber>
</xsl:template>
<xsl:template match="/">
<A>
<target>
<xsl:apply-templates select="//Item"/>
</target>
</A>
</xsl:template>
</xsl:stylesheet>
Update according the changed question:
get the count here (inside a for each loop as variable)
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 indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" />
<xsl:template match="PackNumber" mode="count">
<xsl:value-of select="count(preceding::PackNumber
[not(preceding::PackNumber= .)and not( . = current()/. ) ])+1"/>
</xsl:template>
<xsl:template match="/">
<A>
<target>
<xsl:for-each select="//Item/PackNumber">
<xsl:variable name="count">
<xsl:apply-templates select="." mode="count"/>
</xsl:variable>
<counter>
<xsl:value-of select="$count"/>
</counter>
<PNumber>
<xsl:value-of select="."/>
</PNumber>
</xsl:for-each>
</target>
</A>
</xsl:template>
</xsl:stylesheet>
Update Output:
<?xml version="1.0"?>
<A>
<target>
<counter>1</counter>
<PNumber>1234</PNumber>
<counter>2</counter>
<PNumber>567</PNumber>
<counter>2</counter>
<PNumber>567</PNumber>
<counter>3</counter>
<PNumber>126</PNumber>
<counter>4</counter>
<PNumber>876</PNumber>
</target>
</A>

Here is a short and very efficient solution, using Muenchian grouping:
<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:key name="kPackNByVal" match="PackNumber" use="."/>
<xsl:template match="Afile">
<A>
<target>
<xsl:apply-templates select=
"*/*[generate-id() = generate-id(key('kPackNByVal',.)[1])]"/>
</target>
</A>
</xsl:template>
<xsl:template match="PackNumber">
<counter><xsl:value-of select="position()"/></counter>
<PNumber><xsl:value-of select="."/></PNumber>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Move-Afile>
<Afile>
<Item>
<PackNumber>1234</PackNumber>
</Item>
<Item>
<PackNumber>567</PackNumber>
</Item>
<Item>
<PackNumber>567</PackNumber>
</Item>
<Item>
<PackNumber>126</PackNumber>
</Item>
<Item>
<PackNumber>876</PackNumber>
</Item>
</Afile>
</Move-Afile>
the wanted, correct result is produced:
<A>
<target>
<counter>1</counter>
<PNumber>1234</PNumber>
<counter>2</counter>
<PNumber>567</PNumber>
<counter>3</counter>
<PNumber>126</PNumber>
<counter>4</counter>
<PNumber>876</PNumber>
</target>
</A>

If the result you need is what you show in your second XML sample, then this stylesheet is all you need.
All it does is copy the PackNumber from only those Item elements that have no previous matching PackNumber.
The count is calculated as one more than the number of preceding Item elements that have no matching predecessor.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:template match="/">
<A>
<target>
<xsl:apply-templates select="*"/>
</target>
</A>
</xsl:template>
<xsl:template match="Item">
<xsl:if test="not(PackNumber = preceding-sibling::Item/PackNumber)">
<counter>
<xsl:value-of select="1+count(preceding-sibling::Item[not(PackNumber = preceding-sibling::Item/PackNumber)])"/>
</counter>
<xsl:copy-of select="PackNumber"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
output
<A>
<target>
<counter>1</counter>
<PackNumber>1234</PackNumber>
<counter>2</counter>
<PackNumber>567</PackNumber>
<counter>3</counter>
<PackNumber>126</PackNumber>
<counter>4</counter>
<PackNumber>876</PackNumber>
</target>
</A>

Rather than using counter variable and attempting to increment its value every time (Which anyway not going to happen as the variables in xsl are actually constants), try using position(), which can work as a unique incrementing identifier. Following is an example of the syntax using position():
<xsl:variable name="counterAlias" select="position()"/>

Related

How to correctly implement the if-else condition in XSLT?

I have XML in which there is a ContactRecords node:
<Organisations>
<Organisation>
<Tag1>ValueElementTag1</Tag1>
<Tag2>ValueElementTag2</Tag2>
<Tag3>ValueElementTag3</Tag3>
<ContactRecords>
<item>
<ContactRecordType>AAAAA</ContactRecordType>
<ContactValue>ValueAAAAA</ContactValue>
<Address xmlns="http://www.v8.1c.ru/ssl/contactinfo" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AdrTag1 xsi:type="Adr">Example1</AdrTag1>
<AdrTag2>Example2</AdrTag2>
</Address>
</item>
<item>
<ContactRecordType>BBBBB</ContactRecordType>
<ContactValue>ValueBBBBB</ContactValue>
<Address xmlns="http://www.v8.1c.ru/ssl/contactinfo" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AdrTag1 xsi:type="Adr">Example1</AdrTag1>
<AdrTag2>Example2</AdrTag2>
</Address>
</item>
<item>
<ContactRecordType>CCCCC</ContactRecordType>
<ContactValue>ValueCCCCC</ContactValue>
</item>
</ContactRecords>
</Organisation>
<Organisation>
<Tag1>ValueElementTag1</Tag1>
<Tag2>ValueElementTag2</Tag2>
<Tag3>ValueElementTag3</Tag3>
<ContactRecords>
<item>
<ContactRecordType>AAAAA</ContactRecordType>
<ContactValue>ValueAAAAA</ContactValue>
<Address xmlns="http://www.v8.1c.ru/ssl/contactinfo" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AdrTag1 xsi:type="Adr">Example1</AdrTag1>
<AdrTag2>Example2</AdrTag2>
</Address>
</item>
<item>
<ContactRecordType>BBBBB</ContactRecordType>
<ContactValue>ValueBBBBB</ContactValue>
<Address xmlns="http://www.v8.1c.ru/ssl/contactinfo" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AdrTag1 xsi:type="Adr">Example1</AdrTag1>
<AdrTag2>Example2</AdrTag2>
</Address>
</item>
<item>
<ContactRecordType>CCCCC</ContactRecordType>
<ContactValue>ValueCCCCC</ContactValue>
</item>
</ContactRecords>
</Organisation>
</Organisations>
I am writing an XSLT which has a handling of a ContactRecords node:
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:element name="Organisations">
<xsl:for-each select="Organisations/Organisation">
<xsl:element name="{name(.)}">
<xsl:for-each select="*[not(name()='ContactRecords')]">
<xsl:copy select="*">
<xsl:value-of select="normalize-space(.)"/>
</xsl:copy>
</xsl:for-each>
<xsl:for-each select="ContactRecords/item">
<xsl:choose>
<xsl:when test="Address">
<h2>mooooooooooooo</h2>
</xsl:when>
<xsl:otherwise>
<h2>dooooooooooooo</h2>
</xsl:otherwise>
</xsl:choose>
​​</xsl:for-each>
​</xsl:element>
​</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:transform>
I am now getting the following result:
<h2>dooooooooooooo</h2>
<h2>dooooooooooooo</h2>
​​<h2>dooooooooooooo</h2>
I expect to receive:
<h2>mooooooooooooo</h2>
<h2>mooooooooooooo</h2>
​​<h2>dooooooooooooo</h2>
What am I doing wrong?
If I explain the algorithm in words, then I need the following: if there is an Address element in the item element, then we do logic number 1. If there is no Address element in the item element, then we do logic number 2.
If we describe the algorithm in pseudocode, then this is how:
if (item.includes(Address)) {
do logic #1
} else {
do logic #2
}
UPD1: Updated XML and XSLT code
UPD2: Add namespaces in tags Address (maybe the reason is in them)
The Address element is in a namespace, so your test:
<xsl:when test="Address">
returns false every time. Try it this way (minimized to the current problem):
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://www.v8.1c.ru/ssl/contactinfo"
exclude-result-prefixes="ns0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/Organisations">
<Organisations>
<xsl:for-each select="Organisation">
<xsl:copy>
<!-- omitted -->
<xsl:for-each select="ContactRecords/item">
<h2>
<xsl:choose>
<xsl:when test="ns0:Address">mooooooooooooo</xsl:when>
<xsl:otherwise>dooooooooooooo</xsl:otherwise>
</xsl:choose>
</h2>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</Organisations>
</xsl:template>
</xsl:stylesheet>

xslt order and grouping with for-each-group

I'm trying to get my head over nested grouping and sorting while using for-each-group.
My idea is to order and group items at first by producer. Then when I have this producer groups I'd like to sort each of them by code. However currently the order of code doesn't work as I'd like to. In following example the problem is with item with code=01001-064-03. It should be grouped together with all other items whose code starts with 01001 but it isn't. If I move entire item/code[text()='01001-064-03'] (the last one) to the beginning of xml then grouping works ok.
Please what is my issue here?
Thanks
<items>
<change_date>#11.11.2020 7:42:13</change_date>
<result>
<item>
<code>01001-064-01</code>
<producer>prod1</producer>
</item>
<item>
<code>01001-064-02</code>
<producer>prod1</producer>
</item>
<item>
<code>def</code>
<producer>prod1</producer>
</item>
<item>
<code>ghi</code>
<producer>prod2</producer>
</item>
<item>
<code>jkl</code>
<producer>prod3</producer>
</item>
<item>
<code>abc</code>
<producer>prod3</producer>
</item>
<item>
<code>def</code>
<producer>prod4</producer>
</item>
<item>
<code>ghi</code>
<producer>prod4</producer>
</item>
<item>
<code>jkl</code>
<producer>prod5</producer>
</item>
<item>
<code>01001-064-03</code>
<producer>prod1</producer>
</item>
</result>
</items>
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes" html-version="5"/>
<xsl:function name="mf:same-product" as="xs:boolean">
<xsl:param name="left" as="xs:string"/>
<xsl:param name="right" as="xs:string"/>
<xsl:variable name="leftParsed" select="mf:get-regexp-group($left, 1)"/>
<xsl:variable name="rightParsed" select="mf:get-regexp-group($right, 1)"/>
<xsl:sequence select="matches($leftParsed, $rightParsed)"/>
</xsl:function>
<xsl:function name="mf:get-regexp-group" as="xs:string">
<xsl:param name="text" as="xs:string"/>
<xsl:param name="groupNumber" as="xs:integer"/>
<xsl:variable name="result">
<xsl:analyze-string select="$text" regex="(^[a-zA-Z0-9]+)(.*)">
<xsl:matching-substring>
<xsl:value-of select="regex-group($groupNumber)"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:sequence select="$result"/>
</xsl:function>
<xsl:template match="items">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="change_date"/>
<xsl:template match="result">
<data>
<xsl:for-each-group select="item" group-by="producer">
<xsl:sort select="producer"/>
<xsl:for-each-group select="current-group()" group-starting-with="item[not(mf:same-product(code, preceding-sibling::item[1]/code))]">
<xsl:sort select="code"/>
<group>
<xsl:apply-templates select="current-group()" />
</group>
</xsl:for-each-group>
</xsl:for-each-group>
</data>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
fiddle example here
I'm using xslt 2.0 with saxon-he 10.3.
EDIT:
So as #michael.hor257k asked for better explanation I'll try to do my best:
Each item is product. This product has producer and has code (product code). I want to group all products of producer by code. However codes are not same for similar products so the similarity is matched by function mf:same-product. For example two similar products could be 01001-064-01 and 01001-064-02 here I check the first prefix 01001 and if it matches it means both products should be added to same group.
expected result should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<group>
<item>
<code>01001-064-01</code>
<producer>prod1</producer>
</item>
<item>
<code>01001-064-02</code>
<producer>prod1</producer>
</item>
<item>
<code>01001-064-03</code>
<producer>prod1</producer>
</item>
</group>
<group>
<item>
<code>def</code>
<producer>prod1</producer>
</item>
</group>
<group>
<item>
<code>ghi</code>
<producer>prod2</producer>
</item>
</group>
<group>
<item>
<code>abc</code>
<producer>prod3</producer>
</item>
</group>
<group>
<item>
<code>jkl</code>
<producer>prod3</producer>
</item>
</group>
<group>
<item>
<code>def</code>
<producer>prod4</producer>
</item>
</group>
<group>
<item>
<code>ghi</code>
<producer>prod4</producer>
</item>
</group>
<group>
<item>
<code>jkl</code>
<producer>prod5</producer>
</item>
</group>
</data>
Perhaps a composite group-by suffices:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="change_date"/>
<xsl:template match="result">
<data>
<xsl:for-each-group select="item" composite="yes" group-by="producer, code => replace('[^a-z0-9].*$', '', 'i')">
<xsl:sort select="producer"/>
<xsl:sort select="code"/>
<group>
<xsl:apply-templates select="current-group()" />
</group>
</xsl:for-each-group>
</data>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ei5R4uT/10
That is XSLT 3 which Saxon 9.8 and later (e.g. Saxon 10) support, if you really need to do it with an XSLT 2.0 processor then a nested for-each-group group-by or a concatenated grouping key can achieve the same as the composite grouping key in the above XSLT 3.
My idea is to order and group items at first by producer. Then when I have this producer groups I'd like to sort each of them by code.
If that's all you want to do, why isn't it sufficient to do:
XSLT 3.0
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="result">
<xsl:for-each-group select="item" group-by="producer">
<xsl:sort select="producer"/>
<group>
<xsl:apply-templates select="current-group()">
<xsl:sort select="code"/>
</xsl:apply-templates>
</group>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I'm using xslt 2.0 with saxon-he 10.3.
Actually, you are using XSLT 3.0.

Find unique child nodes of an element

Need to write an xslt file. Below is the input file:
<assets>
<item>
<child1>some text</child1>
<child2>some text</child2>
<child3>some text</child3>
<child4>some text</child4>
</item>
<item>
<child1>some text</child1>
<child2>some text</child2>
<childx>some text</childx>
</item>
<item>
<child1>some text</child1>
<childx>some text</childx>
<childy>some text</childy>
<childz>some text</childz>
</item>
</assets>
I need to find out all the unique child names of assets/item. The number of children and child name is dynamic under the element (item)
The Output should be as below:
<item>
<columns>
<columnname>child1</columnname>
<columnname>child2</columnname>
<columnname>child3</columnname>
<columnname>child4</columnname>
<columnname>childx</columnname>
<columnname>childy</columnname>
<columnname>childz</columnname>
</columns>
</item>
You can use Muenchian grouping on the element names - something like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="elements" match="*" use="local-name()" />
<xsl:template match="/">
<item>
<xsl:apply-templates select="assets/item" />
</item>
</xsl:template>
<xsl:template match="item">
<xsl:for-each select="*[count(.|key('elements', local-name())[1]) = 1]">
<columnname>
<xsl:value-of select="name()"/>
</columnname>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Will get you this:
<item>
<columnname>child1</columnname>
<columnname>child2</columnname>
<columnname>child3</columnname>
<columnname>child4</columnname>
<columnname>childx</columnname>
<columnname>childy</columnname>
<columnname>childz</columnname>
</item>
you can use this
<xsl:key name="child" match="item/*" use="name()"/>
<xsl:template match="/">
<item>
<columns>
<xsl:for-each select="//item/*[count(.|key('child', name())[1]) = 1]">
<columnname><xsl:value-of select="name()"/></columnname>
</xsl:for-each>
</columns>
</item>
</xsl:template>
output
<item>
<columns>
<columnname>child1</columnname>
<columnname>child2</columnname>
<columnname>child3</columnname>
<columnname>child4</columnname>
<columnname>childx</columnname>
<columnname>childy</columnname>
<columnname>childz</columnname>
</columns>
</item>

Remove parent node if child node is empty

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.

Doing a pivot using XSLT

I have an xml file like this:
<root>
<item>
<name>one</name>
<status>good</status>
</item>
<item>
<name>two</name>
<status>good</status>
</item>
<item>
<name>three</name>
<status>bad</status>
</item>
<item>
<name>four</name>
<status>ugly</status>
</item>
<item>
<name>five</name>
<status>bad</status>
</item>
</root>
I want to transform this using XSLT to get something like:
<root>
<items><status>good</status>
<name>one</name>
<name>two</name>
</items>
<items><status>bad</status>
<name>three</name>
<name>five</name>
</items>
<items><status>ugly</status>
<name>four</name>
</items>
</root>
In other words, I get a list of items, each with a status, and I want to turn it into a list of statuses, each with a list of items.
My initial thought was to do apply-templates matching each status type in turn, but that means I have to know the complete list of statuses. Is there a better way to do it?
Thanks for any help.
Muench to the rescue!
<?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:key name="muench" match="/root/item/status" use="."/>
<xsl:template match="/">
<root>
<xsl:for-each select="/root/item/status[generate-id() = generate-id(key('muench',.)[1])]">
<xsl:call-template name="pivot">
<xsl:with-param name="status" select="."/>
</xsl:call-template>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="pivot">
<xsl:param name="status"/>
<items>
<status><xsl:value-of select="$status"/></status>
<xsl:for-each select="/root/item[status=$status]">
<name><xsl:value-of select="name"/></name>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>
Yes, this can be done in XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<!-- -->
<xsl:key name="kStatByVal"
match="status" use="."/>
<!-- -->
<xsl:key name="kItemByStat"
match="item" use="status"/>
<!-- -->
<xsl:variable name="vDoc" select="/"/>
<xsl:template match="/">
<top>
<xsl:for-each select=
"/*/*/status[generate-id()
=
generate-id(key('kStatByVal',.)[1])
]">
<items>
<status><xsl:value-of select="."/></status>
<xsl:for-each select="key('kItemByStat', .)">
<xsl:copy-of select="name"/>
</xsl:for-each>
</items>
</xsl:for-each>
</top>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the original XML document:
<root>
<item>
<name>one</name>
<status>good</status>
</item>
<item>
<name>two</name>
<status>good</status>
</item>
<item>
<name>three</name>
<status>bad</status>
</item>
<item>
<name>four</name>
<status>ugly</status>
</item>
<item>
<name>five</name>
<status>bad</status>
</item>
</root>
The wanted result is produced:
<top>
<items>
<status>good</status>
<name>one</name>
<name>two</name>
</items>
<items>
<status>bad</status>
<name>three</name>
<name>five</name>
</items>
<items>
<status>ugly</status>
<name>four</name>
</items>
</top>
Do note the use of:
The Muenchian method for grouping
The use of <xsl:key> and the key() function
It depends about your xslt engine. If you're using xslt 1.0 without any extension, then your approach is certainly the best.
On the other side, if you're allowed to use exslt (especially the node-set extension) or xslt 2.0, then you could do it in a more generic way:
Collect all the available statuses
Create a node-set from the obtained result
Iterating on this node set, create your pivot by filtering you status base on the current element in your iteration.
But before doing that, consider that it may be overkill if you only have a few set of statuses and that adding another status is quite rare.
In XSLT 2.0 you can replace the muenchian grouping by its standard grouping mechanism. Applied to the given answer the xslt would look like:
<?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" indent="yes" encoding="UTF-8"/>
<xsl:key name="muench" match="/root/item/status" use="."/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="/root/item/status" group-by="key('muench', .)">
<xsl:call-template name="pivot">
<xsl:with-param name="status" select="."/>
</xsl:call-template>
</xsl:for-each-group>
</root>
</xsl:template>
<xsl:template name="pivot">
<xsl:param name="status"/>
<items>
<status><xsl:value-of select="$status"/></status>
<xsl:for-each select="/root/item[status=$status]">
<name><xsl:value-of select="name"/></name>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>