Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-57804

QXmlPatterns position() sticks or uses the wrong value (current context instead of parent context)

    XMLWordPrintable

Details

    • Bug
    • Resolution: Out of scope
    • P3: Somewhat important
    • None
    • 5.5.1
    • XML: QtXmlPatterns
    • None
    • Ubuntu 16.04, although I'm sure this is a problem on all platforms.

    Description

      Variables get calculated the first time they get used. Only, when the variable makes use of the position() function, it uses the current context position instead of the position in the context where the variable is defined, as expected by the XSLT 2.0 specification.

      There is an XML as input. We want to select the <value> tag using the index of the <name> tag. As we can see, the input is well formed.

      <?xml version="1.0"?>
      <top>
        <left>
          <name>One</name>
          <name>Two</name>
          <name>Three</name>
          <name>Four</name>
        </left>
        <right>
          <value>1</value>
          <value>2</value>
          <value>3</value>
          <value>4</value>
        </right>
      </top>
      

      The XSLT code to transform the input in a list like this (removing many spaces which are not important here):

      <division><name>One</name><value>1</value></division>
      <division><name>Two</name><value>2</value></division>
      <division><name>Three</name><value>3</value></division>
      <division><name>Four</name><value>4</value></division>
      

      Looks like this:

      <?xml version='1.0'?>
      <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                      xmlns:xs="http://www.w3.org/2001/XMLSchema"
                      xmlns:fn="http://www.w3.org/2005/xpath-functions">
      <xsl:template match="top">
      <test>
      <xsl:for-each select="top/left/name">
          <division>
              <xsl:copy-of select="."/> 
              <xsl:variable name="index" select="position()"/>
              <xsl:copy-of select="../../right/value[$index]"/>
          </division>
      </xsl:for-each>
      </test>
      </xsl:template>
      </xsl:stylesheet>
      

      The output I get with xmlpattern file.xsl file.xml running against that correct XSLT 2.0 code is:

      <test>
          <division>
              <name>One</name>
              <value>1</value>
              <value>2</value>
              <value>3</value>
              <value>4</value>
          </division>
          <division>
              <name>Two</name>
              <value>1</value>
              <value>2</value>
              <value>3</value>
              <value>4</value>
          </division>
          <division>
              <name>Three</name>
              <value>1</value>
              <value>2</value>
              <value>3</value>
              <value>4</value>
          </division>
          <division>
              <name>Four</name>
              <value>1</value>
              <value>2</value>
              <value>3</value>
              <value>4</value>
          </division>
      </test>
      

      As we can see, all the <value> tags get included each time.

      To try to see what is going wrong, I try to see what the position() value is set to. So I change the XSLT code to this:

      <?xml version='1.0'?>
      <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                      xmlns:xs="http://www.w3.org/2001/XMLSchema"
                      xmlns:fn="http://www.w3.org/2005/xpath-functions">
      <xsl:template match="top">
      <test>
      <xsl:for-each select="left/name">
          <division>
              Name: <xsl:copy-of select="."/> 
              <xsl:variable name="index" select="position()"/>
              Position: <xsl:copy-of select="position()"/>
              Index: <xsl:copy-of select="$index"/>
              Value: <xsl:copy-of select="../../right/value[$index]"/>
          </division>
      </xsl:for-each>
      </test>
      </xsl:template>
      </xsl:stylesheet>
      

      The output becomes:

      <test>
          <division>
              Name: <name>One</name>
              Position: 1
              Index: 1
              Value: <value>1</value>
          </division>
          <division>
              Name: <name>Two</name>
              Position: 2
              Index: 1
              Value: <value>1</value>
          </division>
          <division>
              Name: <name>Three</name>
              Position: 3
              Index: 1
              Value: <value>1</value>
          </division>
          <division>
              Name: <name>Four</name>
              Position: 4
              Index: 1
              Value: <value>1</value>
          </division>
      </test>
      

      Now the result is very interesting. The $index variable always shows 1. As if it were frozen (I suppose it is cached and the software thinks the value is correct on the second iteration.) However, the position() parameter works as expected when accessed directly.

      Now that we accessed the $index variable from the outside and it got set to 1, the only value appear in the output is <value>1</value>. As expected if $index is set to 1, so that works in this special case...

      It also proves that when you do not first access the $index variable outside of the <xsl:copy-of select="../../right/value[$index]"/>, then the position() function uses the current position of the current context (that very select=... in the xsl:copy) and not the context where the variable was defined (i.e. the select=... of the xsl:for-each).


      For those blocked by this problem, there is a tedious work around which is to create a named template with the content of the xsl:for-each and call it with a parameter.

      <xsl:template name="inside-loop">
        <xsl:param name="index"/>
        ...here $index is correct...
      </xsl:template>
      
      <xsl:for-each select="...">
         <xsl:call-template name="inside-loop">
            <xsl:with-param name="index" value="position()"/>
         </xsl:call-template>
      </xsl:for-each>
      

      Problem existed in 4.8.x:

      https://bugreports.qt.io/browse/QTBUG-40518

      External reference:

      http://stackoverflow.com/questions/41317656/should-position-as-used-here-work-to-retrieve-node-with-same-position-in-two-s/41317832#41317832

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            Unassigned Unassigned
            alexiswilke Alexis Wilke
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes