Re: Saxon 9.5 Not Emitting Expected Messages Within xsl:iterate

Eliot Kimber <ekimber-xb9TSTQ6enFWk0Htik3J/[email protected]> Fri, 16 Oct 2020 16:21:43 -0500
Newsgroups gmane.text.xml.saxon.help
Message-ID <[email protected]>
I was able to make my code work with 9.5 by first making it work with 9.8, which reported some of my coding errors 9.5 had not, and corrected some other logic bugs I had.

So still not sure what was going on with the messages and such, but I made it work...

Cheers,

E.

--
Eliot Kimber
http://contrext.com
 

On 10/16/20, 1:20 PM, "Eliot Kimber" <[email protected]> wrote:

    I fully understand the support challenge here.

    As far as I can tell map:merge() is map:new() in Saxon 9.5, which makes it hard to run as-is in the latest version.

    Digging further into my code I'm finding that there's a silent failure on what seems to be innocuous code that's operating on a sequence of strings:

        <xsl:variable name="rowkeys" as="xs:string*" 
          select="
          for $key in $mapkeys
          return 
          if (starts-with(string($key), $rowkeypart)) 
          then string($key) 
          else ()
          "/>
        <xsl:message>+ [DEBUG] === local:getEffectiveColumnPosition(): rowkeys: <xsl:sequence select="$rowkeys"/></xsl:message>

    Where $mapkeys is the result of map:keys($mymap) and has the expected value (i.e., a sequence of strings).

    The message for $rowkeys is never emitted when the value of $rowkeypart is *not* reflected in any of the strings $mapkeys, i.e., the keys are all "1,1", "1,2", but rowkeypart is "2," (so on the first cell of the second row, where all the keys are "1,*" and I'm looking for "2,*".

    My original version of this particular bit was just:

    select="map:keys($mymap)[starts-with(., $rowkeypart)]

    Which failed in the same way, so I replaced it with the  more explicit version here just to see if it was something with keys() but it still failed.

    So I'm still at a loss as to what might be causing this particular failure--some long-corrected bug in 9.5 no doubt.

    My challenge is that I have no way to replace the use of 9.5 so I can only work around bugs, and generally sticking to XSLT 2-only constructs gets me there, but for this problem in particular an XSLT 3 solution should be much easier...

    I won't trouble you more with this--it's not really a problem for Saxonica to solve--I was mostly wondering if it *should* work at all with the early support for xsl:iterate and maps in that version of Saxon.

    Thanks,

    Eliot

    --
    Eliot Kimber
    http://contrext.com


    On 10/16/20, 12:55 PM, "Michael Kay" <[email protected]> wrote:



            For my day job I'm stuck on Saxon 9.5. I've read the 9.5 documentation and I think I have correctly understood the 9.5 support for xsl:iterate and maps but I'm getting unexpected behavior so I wanted to make sure I'm not trying to do the impossible.

            I  know it's hard (and painful) to support a version this old, but I have to ask.





        Well it's technically unsupported. If you can supply a repro, and if the code works correctly on 10.2 but fails on 9.5, then there's no way we're going to produce a 9.5 fix.

        * Have you tried it on a later release?

        * Do you have a repro? It's very hard for us to debug user code unless we can run it. My standard diagnostic approach when someone comes with a problem like this is:

        (a) run it and see if I get the same result

        (b) if so, look at the code to see if there's anything obvious (see below)

        (c) try running it with -T tracing to see if that gives any insights

        (d) if I'm still baffled, try executing under the Java debugger

        On a second look, I notice that the body of several xsl:for-each instructions can be statically determined to return an empty sequence. If the body of an xsl:for-each is empty, then the result will be empty, so the optimizer is going to avoid the effort of evaluating it. That means there's a lot of dead code here.

        I also noticed that it's using a function map:new() which is no longer in the spec. I think it should be using map:merge(). I don't recall what map:new() did.

        Michael Kay
        Saxonica


            The data processing problem I'm trying to solve is working out the correct column positions of CALS table cells in the face of row spans. I'm solving this by iterating over the <entry> elements in the input thead or tbody and constructing map entries where the key is the row/column position of all the unit cells, which means that for any given cell, there will be map entries for any preceding unit cells covered by preceding horizontal or vertical row spans, allowing me to then calculate the effective starting column for any given <entry> element. This is an ideal candidate for xsl:iterate.

            What I'm seeing is that my code is not giving me the correct answer (basically, the bit that should be constructing row-span-reflecting map entries is not doing so).

            I'm working in Oxygen but I've got Saxon 9.5 set up as an external transform engine so I can run it rather than the newest version. This means I can't use Oxygen's debugger and must rely on messages.

            What I'm seeing is that messages that should not be conditional or deferred by late variable binding are not being emitted at all.

            I think my question is: should I expect xsl:iterate and maps to work reliably in Saxon 9.5 and if the answer is yes, what could cause messages to not be emitted? 

            I don't think I have any coding errors--the transform runs and for the case where there are no spans in a row it gives the correct answer. It does not give the correct answer for spans because the necessary map entries are not being created, suggesting that the iterations are short circuiting, which I'm trying to verify using debugging messages.

            In the function given below, what I see, for example, is that for this block of code: 

                    <xsl:variable name="childpos" as="xs:integer" select="count(($cell, preceding-sibling::*[contains(@class, ' topic/entry ')]))"/>
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions():      childpos="<xsl:value-of select="$childpos"/>"</xsl:message>

                    <xsl:variable name="rownum" as="xs:integer" select="count(($cell/parent::*, $cell/parent::*/preceding-sibling::*[contains(@class, ' topic/row ')]))"/>
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions():      rownum="<xsl:value-of select="$rownum"/>"</xsl:message>

            The first message (childpos=...) is emitted but the next one (rownum=...) is not, even though both are just doing xpaths and not using some other function.

            Because this is inside xsl:iterate I suspect there's some early-implementation issue going on.

            Should I not be trying to use xsl:iteration in Saxon 9.5 and go back to recursive functions? 

            Thanks,

            Eliot

            Here's the function I'm running:


            <xsl:function name="local:calculateColumnPositions" as="map(*)">
                <xsl:param name="context" as="element()"/>
                <xsl:param name="colspecs" as="element()*"/>

                <xsl:message>+ [DEBUG] *** local:calculateColumnPositions(): Starting. Colspecs: <xsl:sequence select="$colspecs"/></xsl:message>

                <!-- Construct a set of map entries where the key is the x/y position of a given
                     unit cell in the table and the value is the entry that occupies that cell.

                     Use xsl:iterate to examine every entry in a set of rows (thead or tbody)
                  -->
                <xsl:variable name="result" as="map(*)">
                  <xsl:message>+ [DEBUG]   Iteration to construct entry map:</xsl:message>
                  <xsl:iterate select="$context//*[contains(@class, ' topic/entry ')]">
                    <xsl:param name="matrix-map" as="map(*)" select="map{}"/>
                    <xsl:on-completion>
                      <xsl:sequence select="$matrix-map"/>
                    </xsl:on-completion>
                    <xsl:variable name="cell" as="element()" select="."/>
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions(): [Iteration <xsl:value-of select="position()"/>] <xsl:value-of select="$cell"/>:</xsl:message>

                    <xsl:variable name="childpos" as="xs:integer" select="count(($cell, preceding-sibling::*[contains(@class, ' topic/entry ')]))"/>
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions():      childpos="<xsl:value-of select="$childpos"/>"</xsl:message>

                    <xsl:variable name="rownum" as="xs:integer" select="count(($cell/parent::*, $cell/parent::*/preceding-sibling::*[contains(@class, ' topic/row ')]))"/>
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions():      rownum="<xsl:value-of select="$rownum"/>"</xsl:message>
                    <!-- Number of rows including current row that this cell spans (minimum is 1): -->
                    <xsl:variable name="rows-spanned" as="xs:integer"
                      select="
                      if (exists(@morerows))
                      then xs:integer(@morerows) + 1
                      else 1
                      "
                    />
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions():      rows-spanned="<xsl:value-of select="$rows-spanned"/>"</xsl:message>
                    <xsl:variable name="starting-column" as="xs:integer"
                      select="local:getEffectiveColumnPosition($matrix-map, $rownum, $childpos)"
                    />
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions():      starting-column="<xsl:value-of select="$starting-column"/>"</xsl:message>
                    <!-- Number of columns spanned, including the starting column (minimum is 1) -->
                    <xsl:variable name="cols-spanned" as="xs:integer"
                      select="local:getColSpanForCell($cell, $colspecs)"
                    />
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions():      cols-spanned="<xsl:value-of select="$cols-spanned"/>"</xsl:message>
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions(): Calculating cell entries for cell "<xsl:value-of select="$cell"/>"</xsl:message>
                    <xsl:variable name="ending-row" as="xs:integer" select="$rownum + ($rows-spanned - 1)"/>      
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions(): rownum: <xsl:value-of select="$rownum"/>, ending-row: <xsl:value-of select="$ending-row"/></xsl:message>
                    <xsl:variable name="ending-column" as="xs:integer" select="$starting-column + ($cols-spanned - 1)"/>          
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions(): starting-column: <xsl:value-of select="$starting-column"/>, ending-column: <xsl:value-of select="$ending-column"/></xsl:message>
                    <xsl:for-each select="$rownum to $ending-row">
                      <xsl:variable name="row" as="xs:integer" select="."/>
                      <xsl:message>+ [DEBUG] local:calculateColumnPositions(): row: <xsl:value-of select="$row"/>  </xsl:message>
                      <xsl:for-each select="$starting-column to $ending-column">              
                        <xsl:variable name="col" as="xs:integer" select="."/> 
                        <xsl:message>+ [DEBUG] local:calculateColumnPositions(): col: <xsl:value-of select="$col"/></xsl:message>
                        <xsl:variable name="key" as="xs:string" select="$row || ',' || $col"/>
                        <xsl:message>+ [DEBUG] local:calculateColumnPositions(): Creating map entry {<xsl:value-of select="$key"/>: <xsl:value-of select="$cell"/>}</xsl:message>
                      </xsl:for-each>
                    </xsl:for-each>

                    <xsl:variable name="cells-entries" as="map(*)*">
                      <xsl:for-each select="$rownum to $ending-row">
                        <xsl:variable name="row" as="xs:integer" select="."/>
                        <xsl:message>+ [DEBUG] local:calculateColumnPositions(): row: <xsl:value-of select="$row"/>  </xsl:message>
                        <xsl:for-each select="$starting-column to $ending-column">              
                          <xsl:variable name="col" as="xs:integer" select="."/> 
                          <xsl:message>+ [DEBUG] local:calculateColumnPositions(): col: <xsl:value-of select="$col"/></xsl:message>
                          <xsl:variable name="key" as="xs:string" select="$row || ',' || $col"/>
                          <xsl:sequence select="map:entry($key, $cell)"></xsl:sequence>
                        </xsl:for-each>
                      </xsl:for-each>
                    </xsl:variable>
            <!--        
                    <xsl:variable name="cells-entries" as="map(*)*"
                      select="
                      for $p in $starting-column to ($starting-column + ($cols-spanned - 1))
                      return
                      for $q in $rownum to ($rownum + ($rows-spanned - 1))
                      return map:entry($q || ',' || $p, $cell)
                      "
                    />-->
                    <xsl:message>+ [DEBUG] local:calculateColumnPositions():      cells-entries:</xsl:message>
                    <xsl:for-each select="$cells-entries">
                      <xsl:variable name="key" as="xs:string" select="map:keys(.)"/>
                      <xsl:message>+ [DEBUG] local:calculateColumnPositions():       [Cell <xsl:value-of select="$key"/>] <xsl:value-of select="map:get(., $key)"/></xsl:message>          
                    </xsl:for-each>

                    <xsl:next-iteration>
                      <xsl:with-param name="matrix-map" select="map:new(($matrix-map, $cells-entries))"/>
                    </xsl:next-iteration>
                  </xsl:iterate>
                </xsl:variable>
                <xsl:message>+ [DEBUG] Result map:</xsl:message>
                <xsl:for-each select="map:keys($result)">
                  <xsl:sort select="."/>
                  <xsl:variable name="key" as="xs:string" select="."/>
                  <xsl:variable name="cell" as="element()" select="map:get($result, $key)"/>
                  <xsl:message>[<xsl:value-of select="$key"/>] [entry <xsl:value-of select="for $att in ($cell/@morerows, $cell/@namest, $cell/@nameend) return (' ' || name($att) || '=' || string($att))"/>]<xsl:value-of select="map:get($result, $key)"/></xsl:message>
                </xsl:for-each>
                <xsl:sequence select="$result"/>
              </xsl:function>
            --
            Eliot Kimber
            http://contrext.com





            _______________________________________________
            saxon-help mailing list archived at http://saxon.markmail.org/
            [email protected]
            https://lists.sourceforge.net/lists/listinfo/saxon-help 





        _______________________________________________
        saxon-help mailing list archived at http://saxon.markmail.org/
        [email protected]
        https://lists.sourceforge.net/lists/listinfo/saxon-help 





        _______________________________________________
        saxon-help mailing list archived at http://saxon.markmail.org/
        [email protected]
        https://lists.sourceforge.net/lists/listinfo/saxon-help 




    _______________________________________________
    saxon-help mailing list archived at http://saxon.markmail.org/
    [email protected]
    https://lists.sourceforge.net/lists/listinfo/saxon-help 




_______________________________________________
saxon-help mailing list archived at http://saxon.markmail.org/
[email protected]
https://lists.sourceforge.net/lists/listinfo/saxon-help