J. Nicholas
J. Nicholas

Reputation: 105

Consuming the same node twice in Streaming XSLT

I am trying to convert some XML to the interstitial JSON representation specified in XSLT 3.0 (for later conversion to JSON via xml-to-json). My current XSLT works well as a non-streaming stylesheet, but I'm running into issues during the conversion to streaming. Specifically, there are situations where I need to consume the same node twice, especially in the case of repeating tags in XML, which I am converting to arrays in equivalent JSON representation.

<xsl:if test="boolean(cdf:AdjudicatorName)">
    <array key="AdjudicatorName">
        <xsl:for-each select="cdf:AdjudicatorName">
            <string>
                <xsl:value-of select="."/>
            </string>
        </xsl:for-each>
    </array>
</xsl:if>

boolean(cdf:AdjudicatorName) tests for the existance of the tag, and creates an array if so.

This code fails in Oxygen (Saxon-EE) with the following message

Template rule is declared streamable but it does not satisfy the streamability rules. * There is more than one consuming operand: {fn:exists(...)} on line {X}, and {<string {(attr{key=...}, ...)}/>} on line {Y}

I am aware of the copy-of workaround, however, many items in the source file can repeat at the highest level, so use of this approach would yield minimal memory savings.

Upvotes: 1

Views: 302

Answers (1)

Michael Kay
Michael Kay

Reputation: 163458

This looks like the perfect use case for xsl:where-populated:

<xsl:where-populated>
    <array key="AdjudicatorName">
        <xsl:for-each select="cdf:AdjudicatorName">
            <string>
                <xsl:value-of select="."/>
            </string>
        </xsl:for-each>
    </array>
</xsl:where-populated>

The xsl:where-populated instruction (which was invented for exactly this purpose) logically evaluates its child instructions, and then eliminates any items in the resulting sequence that are "deemed empty", where an element is deemed empty if it has no children. In a streaming implementation the start tag (<array>) will be "held back" until its first child is generated, and when the corresponding end tag (</array>) is emitted, the pair of tags will be discarded if there were no intervening children emitted.

Upvotes: 3

Related Questions