Search code examples
xsltsaxon

XSLT 3.0 Template rule is not streamable


I am creating csv file using the below xsl. I need to handle a condition check that if the XML does not contain an item element then I need to add a default row otherwise it needs to add the elements from the item.

My XML is large and I am using SAXON streaming. When I use both if and for-each elements it throws errors. I need your help to handle this.

Error occurred: Error occurred while transforming: Template rule is not streamable

  • There is more than one consuming operand:
  • The result of the template rule can contain streamed nodes

XSLT

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
    <xsl:output method="text" encoding="UTF-8" indent="no" omit-xml-declaration="yes"/>
    <xsl:mode streamable="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:variable name="dq" select="'&quot;'"/>
    <xsl:variable name="qcq" select="'&quot;,&quot;'"/>
    <xsl:variable name="lf" select="'&#10;'"/>   

    <xsl:variable name="email" select="'[email protected]'"/>
    <xsl:variable name="product_id" select="'product1'"/>
    <xsl:template match="items">
        <xsl:text>product_id,email,key</xsl:text>
        <xsl:value-of select="'&#10;'"/>
        <xsl:choose>
            <xsl:when test="not(item)">
                <xsl:value-of disable-output-escaping="yes" select="concat($dq,$qcq,$email,$qcq,$dq,$lf)"/>
            </xsl:when>
            <xsl:when test="item">
                <xsl:for-each select="item ! copy-of(.)">
                    <xsl:variable name="p_key" select="id"/>
                    <xsl:value-of disable-output-escaping="yes" select="concat($dq,$product_id,$qcq,$email,$qcq,$p_key,$dq,$lf)"/>
                </xsl:for-each>
            </xsl:when>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet> 

Sample XML


<?xml version="1.0" encoding="UTF-8"?>
<items><item><id><![CDATA[040411c47702b29acb4e4120040ee6b937fbb8]]></id></item></items>

no item in xml
<?xml version="1.0" encoding="UTF-8"?>
<items></items>

Solution

  • If the items element can be empty you can use e.g.

       <xsl:template match="items">
            <xsl:text>product_id,email,key</xsl:text>
            <xsl:value-of select="'&#10;'"/>
            <xsl:choose>
                <xsl:when test="not(has-children())">
                    <xsl:value-of select="concat($dq,$qcq,$email,$qcq,$dq,$lf)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:for-each select="item ! copy-of(.)">
                        <xsl:variable name="p_key" select="id"/>
                        <xsl:value-of select="concat($dq,$product_id,$qcq,$email,$qcq,$p_key,$dq,$lf)"/>
                    </xsl:for-each>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    

    I have removed the disable-output-escaping as it doesn't make sense for output method text in my view.

    Another option would be on-empty:

    <xsl:template match="items">
        <xsl:text>product_id,email,key</xsl:text>
        <xsl:value-of select="'&#10;'"/>
        <div>
          <xsl:for-each select="item ! copy-of(.)">
                    <xsl:variable name="p_key" select="id"/>
                    <xsl:value-of select="concat($dq,$product_id,$qcq,$email,$qcq,$p_key,$dq,$lf)"/>
          </xsl:for-each>
          <xsl:on-empty>
             <xsl:value-of select="concat($dq,$qcq,$email,$qcq,$dq,$lf)"/>
          </xsl:on-empty>
        </div>
    </xsl:template>
    

    With output method text, the div element of course doesn't produce output, probably could use an xsl:sequence instead as well.