This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 30060 - [XSLT30]xsl:iterate example "Collecting Multiple Values in a Single Pass" not working
Summary: [XSLT30]xsl:iterate example "Collecting Multiple Values in a Single Pass" not...
Status: CLOSED FIXED
Alias: None
Product: XPath / XQuery / XSLT
Classification: Unclassified
Component: XSLT 3.0 (show other bugs)
Version: Member-only Editors Drafts
Hardware: PC Windows NT
: P2 editorial
Target Milestone: ---
Assignee: Michael Kay
QA Contact: Mailing list for public feedback on specs from XSL and XML Query WGs
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2017-02-07 11:52 UTC by Martin Honnen
Modified: 2017-04-03 18:19 UTC (History)
1 user (show)

See Also:


Attachments

Description Martin Honnen 2017-02-07 11:52:48 UTC
Today I tried to run the example "Collecting Multiple Values in a Single Pass" in https://www.w3.org/XML/Group/qtspecs/specifications/xslt-30/html/#iterate, it has the source code

<xsl:source-document streamable="yes" href="employees.xml">
  <xsl:iterate select="employees/employee">
    <xsl:param name="highest" as="element(employee)*"/>
    <xsl:param name="lowest" as="element(employee)*"/>
    <xsl:on-completion>
      <highest-paid-employees>
        <xsl:value-of select="$highest/name"/>
      </highest-paid-employees>
      <lowest-paid-employees>
        <xsl:value-of select="$lowest/name"/>
      </lowest-paid-employees>  
    </xsl:on-completion>   
    <xsl:variable name="is-new-highest" as="xs:boolean"
                  select="empty($highest[@salary ge current()/@salary])"/>
    <xsl:variable name="is-equal-highest" as="xs:boolean" 
                  select="exists($highest[@salary eq current()/@salary])"/> 
    <xsl:variable name="is-new-lowest" as="xs:boolean" 
                  select="empty($lowest[@salary le current()/@salary])"/>
    <xsl:variable name="is-equal-lowest" as="xs:boolean" 
                  select="exists($lowest[@salary eq current()/@salary])"/> 
    <xsl:variable name="new-highest-set" as="element(employee)*"
                  select="if ($is-new-highest) then .
                          else if ($is-equal-highest) then ($highest, .)
                          else $highest"/>
    <xsl:variable name="new-lowest-set" as="element(employee)*"
                  select="if ($is-new-lowest) then .
                          else if ($is-equal-lowest) then ($lowest, .)
                          else $lowest"/>
    <xsl:next-iteration>
      <xsl:with-param name="highest" select="$new-highest-set"/>
      <xsl:with-param name="lowest" select="$new-lowest-set"/>
    </xsl:next-iteration>
   </xsl:iterate>
 </xsl:source-document>

Reading it I wondered whether it is possible to pass on streamed nodes on as parameters and furthermore whether it would be possible to use "." at several places in the body of the xsl:iterate.

So I constructed a sample input document 

<employees>
	<employee salary="3000">
		<name>Employee a</name>
	</employee>
	<employee salary="1000">
		<name>Employee b</name>
	</employee>
	<employee salary="3000">
		<name>Employee c</name>
	</employee>
	<employee salary="2000">
		<name>Employee d</name>
	</employee>
</employees>

and a complete stylesheet with 

<?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" exclude-result-prefixes="xs math"
	version="3.0">

	<xsl:template name="main">
		<xsl:source-document streamable="yes" href="test2017020702.xml">
			<xsl:iterate select="employees/employee">
				<xsl:param name="highest" as="element(employee)*"/>
				<xsl:param name="lowest" as="element(employee)*"/>
				<xsl:on-completion>
					<highest-paid-employees>
						<xsl:value-of select="$highest/name"/>
					</highest-paid-employees>
					<lowest-paid-employees>
						<xsl:value-of select="$lowest/name"/>
					</lowest-paid-employees>
				</xsl:on-completion>
				<xsl:variable name="is-new-highest" as="xs:boolean"
					select="empty($highest[@salary ge current()/@salary])"/>
				<xsl:variable name="is-equal-highest" as="xs:boolean"
					select="exists($highest[@salary eq current()/@salary])"/>
				<xsl:variable name="is-new-lowest" as="xs:boolean"
					select="empty($lowest[@salary le current()/@salary])"/>
				<xsl:variable name="is-equal-lowest" as="xs:boolean"
					select="exists($lowest[@salary eq current()/@salary])"/>
				<xsl:variable name="new-highest-set" as="element(employee)*"
					select="
						if ($is-new-highest) then
							.
						else
							if ($is-equal-highest) then
								($highest, .)
							else
								$highest"/>
				<xsl:variable name="new-lowest-set" as="element(employee)*"
					select="
						if ($is-new-lowest) then
							.
						else
							if ($is-equal-lowest) then
								($lowest, .)
							else
								$lowest"/>
				<xsl:next-iteration>
					<xsl:with-param name="highest" select="$new-highest-set"/>
					<xsl:with-param name="lowest" select="$new-lowest-set"/>
				</xsl:next-iteration>
			</xsl:iterate>
		</xsl:source-document>
	</xsl:template>

</xsl:stylesheet>

however when I try to run that with Saxon-EE 9.7.0.14J it refuses to run it, telling me 

Static error on line 8 column 67 of test2017020702.xsl:
  XTSE3430: The body of the xsl:stream instruction is not streamable
  *  Operand if($is-new-highest) then ... else ... of let $new-highest-set := ... selects
  streamed nodes in a context that allows arbitrary navigation (line 36)


I looked into the test cases of the test suite whether it has some input/xslt sample similar to that spec example but I couldn't find anything. 

So I tried fixing the example based on what I have learned in the past trying to get code working as streamable with Saxon 9.7 and I had to make sure I construct a copy-of() first of the context node in a variable and use that copy then in all occasions:

<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" exclude-result-prefixes="xs math"
	version="3.0">
	
	<xsl:output indent="yes"/>
	
	<xsl:template name="main">
		<xsl:source-document streamable="yes" href="test2017020702.xml">
			<xsl:iterate select="employees/employee">
				<xsl:param name="highest" as="element(employee)*"/>
				<xsl:param name="lowest" as="element(employee)*"/>
				<xsl:on-completion>
					<highest-paid-employees>
						<xsl:value-of select="$highest/name" separator=","/>
					</highest-paid-employees>
					<lowest-paid-employees>
						<xsl:value-of select="$lowest/name" separator=","/>
					</lowest-paid-employees>
				</xsl:on-completion>
				<xsl:variable name="copy" select="copy-of()"/>
				<xsl:variable name="is-new-highest" as="xs:boolean"
					select="empty($highest[@salary ge $copy/@salary])"/>
				<xsl:variable name="is-equal-highest" as="xs:boolean"
					select="exists($highest[@salary eq $copy/@salary])"/>
				<xsl:variable name="is-new-lowest" as="xs:boolean"
					select="empty($lowest[@salary le $copy/@salary])"/>
				<xsl:variable name="is-equal-lowest" as="xs:boolean"
					select="exists($lowest[@salary eq $copy/@salary])"/>
				<xsl:variable name="new-highest-set" as="element(employee)*"
					select="
					if ($is-new-highest) then
					$copy
					else
					if ($is-equal-highest) then
					($highest, $copy)
					else
					$highest"/>
				<xsl:variable name="new-lowest-set" as="element(employee)*"
					select="
					if ($is-new-lowest) then
					$copy
					else
					if ($is-equal-lowest) then
					($lowest, $copy)
					else
					$lowest"/>
				<xsl:next-iteration>
					<xsl:with-param name="highest" select="$new-highest-set"/>
					<xsl:with-param name="lowest" select="$new-lowest-set"/>
				</xsl:next-iteration>
			</xsl:iterate>
		</xsl:source-document>
	</xsl:template>
	
</xsl:stylesheet>

I am not sure that rewrite is the intended implementation of that example but I think the example in the spec should be fixed to work with an implementation like Saxon.
Comment 1 Michael Kay 2017-02-14 14:04:55 UTC
Thank you for pointing this out. The WG reviewed the bug and agreed. 

You are correct that the example as written is not guaranteed streamable (this emerges from the rules on streamability of xsl:variable, which can be paraphrased as saying that a streamed node cannot be bound to a variable), and that copying the context node fixes the problem. 

I've added it as a test case to the test suite (si-iterate-035/-036), and I have modified the example in the spec.