Project

Profile

Help

Support #4431

closed

<xsl:on-non-empty> and for-each

Added by Vincent Norbert about 4 years ago. Updated about 4 years ago.

Status:
Closed
Priority:
Low
Assignee:
-
Category:
-
Sprint/Milestone:
-
Start date:
2020-01-17
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
Fix Committed on Branch:
Fixed in Maintenance Release:
Platforms:

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

Actions #1

Updated by Michael Kay about 4 years ago

  • Description updated (diff)
Actions #2

Updated by Michael Kay about 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>
Actions #3

Updated by Vincent Norbert about 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

Actions #4

Updated by Vincent Norbert about 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

Actions #5

Updated by Vincent Norbert about 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

Actions #6

Updated by Michael Kay about 4 years ago

You still haven't said what output you actually want...

Actions #7

Updated by Vincent Norbert about 4 years ago

Sorry, i’id Mike no row to be outputted if value is empty

Actions #8

Updated by Vincent Norbert about 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

Actions #9

Updated by Martin Honnen about 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.

Actions #10

Updated by Vincent Norbert about 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.

Actions #11

Updated by Martin Honnen about 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"?

Actions #12

Updated by Michael Kay about 4 years ago

  • Status changed from New to AwaitingInfo
Actions #13

Updated by Vincent Norbert about 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 C

I tried to put those extras tags at several places but then it breaks or those are not outputed

Actions #14

Updated by Vincent Norbert about 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 C

I tried to put those extras tags at several places but then it breaks or those are not outputed

Actions #15

Updated by Vincent Norbert about 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 C

I tried to put those extras tags at several places but then it breaks or those are not outputed

Actions #16

Updated by Vincent Norbert about 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

Actions #17

Updated by Martin Honnen about 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.

Actions #18

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

Also available in: Atom PDF