Support #4431
closed<xsl:on-non-empty> and for-each
Added by Vincent Norbert over 4 years ago. Updated about 4 years ago.
0%
Description
Hi,
I've an XML with the following structure
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<DATA>
<categories>
<category ID="1">
<value>A</value>
<value>B</value>
<value>C</value>
</category>
<category ID="2">
<value/>
</category>
</categories>
</DATA>
</xsl:template>
I made a XSLT with a for each like this
<xsl:for-each select="DATA/categories/category/value">
<ROW MODID="" RECORDID="">
<COL><FIELD><xsl:value-of select="."/></FIELD ></COL>
</ROW>
</xsl:for-each>
But this returns the empty value of <category ID="2">
I need this to be streamable because the file is 1.46 GB
So I've read about <xsl:on-non-empty>
but I'm a noob and I can't find how to make it work with for each
Thanks
Updated by Michael Kay over 4 years ago
It's not clear from your question what output you are trying to produce. If the aim is to produce no ROW
when the value
is empty, one way would be to access the text nodes directly:
.<xsl:for-each select="DATA/categories/category/value/text()">
<ROW MODID="" RECORDID="">
<COL><FIELD><xsl:value-of select="."/></FIELD ></COL>
</ROW>
</xsl:for-each>
We don't generally recommend use of text()
because it's messy in the case where value
contains comments, but you probably know that comments aren't going to appear.
If you do want to use the "conditional content construction" features in XSLT 3.0, I think you could write it as:
.<xsl:for-each select="DATA/categories/category/value">
<xsl:where-populated>
<ROW MODID="" RECORDID="">
<xsl:where-populated>
<COL>
<xsl:where-populated>
<FIELD><xsl:value-of select="."/></FIELD >
</xsl:where-populated>
</COL>
</xsl:where-populated>
</ROW>
</xsl:where-populated>
</xsl:for-each>
Another streamable solution would be
.<xsl:for-each select="DATA/categories/category/string(value)[. != '')">
<ROW MODID="" RECORDID="">
<COL><FIELD><xsl:value-of select="."/></FIELD ></COL>
</ROW>
</xsl:for-each>
Updated by Vincent Norbert over 4 years ago
Excellent, many thanks Michael for your terrific support. I took the text() one (There are no comments in my source)
The last one there's a typo I guess saxon expected a ] but even correcting that I had an error "Required item type of the context item for the parent axis is node(); supplied value has item type xs:string" but that's certainly due to my actual more complex source.
Thanks again
Updated by Vincent Norbert over 4 years ago
Actually my row was more complex (with attributes not depicted in the sample)
<ROW MODID="" RECORDID="">
<COL><DATA><xsl:value-of select="../@CategoryFeature_ID"/></DATA></COL>
<COL><DATA><xsl:value-of select="../@ID"/></DATA></COL>
<COL><DATA><xsl:value-of select="."/></DATA></COL>
</ROW>
So text() didn't work for that as I probable kills the structure
I tried
.<xsl:for-each select="DATA/categories/category/value">
<xsl:where-populated>
<ROW MODID="" RECORDID="">
<COL><DATA><xsl:value-of select="../@CategoryFeature_ID"/></DATA></COL>
<COL><DATA><xsl:value-of select="../@ID"/></DATA></COL>
<COL><DATA><xsl:value-of select="."/></DATA></COL>
</ROW>
</xsl:where-populated>
</xsl:for-each>
But it didn't work, as it output the empty value, because the row would exist due the data fetch from the attributes of the upper nodes always exists. It seems to me that the where-populated checks if what's under it is not empty, rather than to check if what's above it is empty
Updated by Vincent Norbert over 4 years ago
as a matter of cat my source data is more like that
<DATA>
<categories>
<category ID="1">
<name>Toto</name>
<name>Titi</name>
<value>A</value>
<value>B</value>
<value>C</value>
</category>
<category ID="2">
<name>truc</name>
<name>machin</name>
<value/>
</category>
</categories>
</DATA>
Sorry, I oversimplified my sample source
Updated by Michael Kay over 4 years ago
You still haven't said what output you actually want...
Updated by Vincent Norbert over 4 years ago
Sorry, i’id Mike no row to be outputted if value is empty
Updated by Vincent Norbert over 4 years ago
so, follwing my sample I would like to get
<ROW MODID="" RECORDID="">
<COL><FIELD>A</FIELD></COL>
<COL><FIELD>B</FIELD></COL>
<COL><FIELD>C</FIELD></COL>
</ROW>
an that's it, no row from category id 2 because it has no value
Updated by Martin Honnen over 4 years ago
If you only want to process non-empty elements in a streamable way then the function has-children
allows that: value[has-children()]
is streamable.
So perhaps
<xsl:mode on-no-match="shallow-skip" streamable="yes"/>
<xsl:output indent="yes"/>
<xsl:template match="DATA/categories/category">
<xsl:where-populated>
<ROW MODID="" RECORDID="">
<xsl:apply-templates select="value[has-children()]"/>
</ROW>
</xsl:where-populated>
</xsl:template>
<xsl:template match="value">
<COL>
<FIELD>{.}</FIELD>
</COL>
</xsl:template>
is a possible approach.
Updated by Vincent Norbert over 4 years ago
Thanks, Martin, but it doesn't seem to work. Maybe because "value" has no real children, there's multiple populated "value" but "value" aren't a node.
Updated by Martin Honnen over 4 years ago
When I run Saxon 9.9 EE against the input
<?xml version="1.0" encoding="UTF-8"?>
<DATA>
<categories>
<category ID="1">
<name>Toto</name>
<name>Titi</name>
<value>A</value>
<value>B</value>
<value>C</value>
</category>
<category ID="2">
<name>truc</name>
<name>machin</name>
<value/>
</category>
</categories>
</DATA>
with the 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="#all"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-skip" streamable="yes"/>
<xsl:output indent="yes"/>
<xsl:template match="DATA/categories/category">
<xsl:where-populated>
<ROW MODID="" RECORDID="">
<xsl:apply-templates select="value[has-children()]"/>
</ROW>
</xsl:where-populated>
</xsl:template>
<xsl:template match="value">
<COL>
<FIELD>{.}</FIELD>
</COL>
</xsl:template>
</xsl:stylesheet>
I get the result
<?xml version="1.0" encoding="UTF-8"?>
<ROW MODID="" RECORDID="">
<COL>
<FIELD>A</FIELD>
</COL>
<COL>
<FIELD>B</FIELD>
</COL>
<COL>
<FIELD>C</FIELD>
</COL>
</ROW>
Is that not the result you want? Which result do you get instead when "it doesn't seem to work"?
Updated by Michael Kay over 4 years ago
- Status changed from New to AwaitingInfo
Updated by Vincent Norbert over 4 years ago
Hi Martin, sorry for the late reply, I've been busy elsewhere. So yes, your code works, I made some mistakes applying it to my real word case.
But, I didn't manage to completely apply it to mya case, as I need to have some xml header and wrapper around it
actual result shout be
<whole_stuff> <some_xml_tag> <another_one>
A B CI tried to put those extras tags at several places but then it breaks or those are not outputed
Updated by Vincent Norbert over 4 years ago
Vincent Norbert wrote:
Hi Martin, sorry for the late reply, I've been busy elsewhere. So yes, your code works, I made some mistakes applying it to my real word case.
But, I didn't manage to completely apply it to mya case, as I need to have some xml header and wrapper around it
actual result shout be
<whole_stuff> <some_xml_tag> <another_one>
A B CI tried to put those extras tags at several places but then it breaks or those are not outputed
Updated by Vincent Norbert over 4 years ago
Hi Martin, sorry for the late reply, I've been busy elsewhere. So yes, your code works, I made some mistakes applying it to my real word case.
But, I didn't manage to completely apply it to mya case, as I need to have some xml header and wrapper around it
actual result shout be
<whole_stuff> <some_xml_tag> <another_one>
A B CI tried to put those extras tags at several places but then it breaks or those are not outputed
Updated by Vincent Norbert over 4 years ago
Hi Martin, sorry for the late reply, I've been busy elsewhere. So yes, your code works, I made some mistakes applying it to my real word case.
But, I didn't manage to completely apply it to mya case, as I need to have some xml header and wrapper around it
actual result shout be
<whole_stuff>
<some_xml_tag\>
<another_one\>
<METADATA>
<FIELD NAME="my_field1" TYPE="TEXT" EMPTYOK="YES" MAXREPEAT=""/>
<FIELD NAME="my_field2" TYPE="TEXT" EMPTYOK="YES" MAXREPEAT=""/>
<FIELD NAME="my_field3" TYPE="TEXT" EMPTYOK="YES" MAXREPEAT=""/>
</METADATA>
<records>
<ROW MODID="" RECORDID="">
<COL>
<FIELD>A</FIELD>
</COL>
<COL>
<FIELD>B</FIELD>
</COL>
<COL>
<FIELD>C</FIELD>
</COL>
</ROW>
</records>
</whole_stuff>
I tried to put those extras tags at several places but then it breaks or those are not outputed
Updated by Martin Honnen over 4 years ago
Is the source as in https://saxonica.plan.io/issues/4431?pn=1#note-5?
It is not clear whether you have the source as posted earlier and solely need the output to contain the additional elements shown in https://saxonica.plan.io/issues/4431?pn=1#note-16 or whether you have a more complex input and some of the extra output you have now shown is supposed to be transformed or copied from the input as well.
Updated by Michael Kay about 4 years ago
- Status changed from AwaitingInfo to Closed
Closing this as the thread has gone quiet and there seems no need for action by Saxonica.
Please register to edit this issue