1

In XQuery 3.1 I am constructing an HTML table. In one <td> element I'm outputting a series of <a ref="">.

So, currently this simple for:

 <td>
   {
     for $b in collection($globalvar:URIdata)/tei:TEI/tei:text//tei:seg[@type="dep_event" 
                           and @corresp = $a/data(@corresp)
                           and @xml:id != $a/data(@xml:id)] 

     order by $b/data(@xml:id)

     return <a href="{concat($globalvar:URLdoc,$b/ancestor::tei:TEI/tei:text/@xml:id)}">{$b/data(@xml:id)}</a>                        
    }
 </td>

Outputs this:

     <td>
      <a href="http://localhost:8081/exist/apps/deheresi/doc/MS609-0006">MS609-0006-8</a>
      <a href="http://localhost:8081/exist/apps/deheresi/doc/MS609-0419">MS609-0419-5</a>
      <a href="http://localhost:8081/exist/apps/deheresi/doc/MS609-0613">MS609-0613-4</a>
    </td>

But I'd like it to output the list of <a href=""> separated by commas:

     <td>
      <a href="http://localhost:8081/exist/apps/deheresi/doc/MS609-0006">MS609-0006-8</a>, 
      <a href="http://localhost:8081/exist/apps/deheresi/doc/MS609-0419">MS609-0419-5</a>, 
      <a href="http://localhost:8081/exist/apps/deheresi/doc/MS609-0613">MS609-0613-4</a>
    </td>

EDIT: below works...but does not output the results in desired order and I cannot get order by $b/data(@xml:id) to work with it due to an "cardinality" problem (that did not pop up in the original).

    let $coll := collection($globalvar:URIdata)/tei:TEI/tei:text//tei:seg[@type="dep_event" 
                     and @corresp = $a/data(@corresp)
                     and @xml:id != $a/data(@xml:id)] 

     let $last := count($coll)

     for $b at $position in $coll 

     return (<a href="{concat($globalvar:URLdoc,$b/ancestor::tei:TEI/tei:text/@xml:id)}">{$b/data(@xml:id)}</a>,
           if ($position ne $last) then ', ' else '')

Many thanks in advance for any advice.

jbrehr
  • 775
  • 6
  • 19

1 Answers1

3

I am not sure whether there is a common idiom in XQuery to do that but I think using

 for $b in collection($globalvar:URIdata)/tei:TEI/tei:text//tei:seg[@type="dep_event" 
                       and @corresp = $a/data(@corresp)
                       and @xml:id != $a/data(@xml:id)] 

 order by $b/data(@xml:id)
 count $p
 let $a := <a href="{concat($globalvar:URLdoc,$b/ancestor::tei:TEI/tei:text/@xml:id)}">{$b/data(@xml:id)}</a>
 return 
   if ($p > 1)
   then (',', $a)
   else $a

is a possible way, much aline the old XSLT approach to have <xsl:for-each select="$nodes"><xsl:if test="position() > 1">,</xsl:if><xsl:copy-of select="."/></xsl:for-each>. Closer to that you could also try

 (
 for $b in collection($globalvar:URIdata)/tei:TEI/tei:text//tei:seg[@type="dep_event" 
                       and @corresp = $a/data(@corresp)
                       and @xml:id != $a/data(@xml:id)] 

 order by $b/data(@xml:id)

 return <a href="{concat($globalvar:URLdoc,$b/ancestor::tei:TEI/tei:text/@xml:id)}">{$b/data(@xml:id)}</a>
 ) ! (if (position() > 1) then (',', .) else .)  

That could also be written as

 (
 for $b in collection($globalvar:URIdata)/tei:TEI/tei:text//tei:seg[@type="dep_event" 
                       and @corresp = $a/data(@corresp)
                       and @xml:id != $a/data(@xml:id)] 

 order by $b/data(@xml:id)

 return <a href="{concat($globalvar:URLdoc,$b/ancestor::tei:TEI/tei:text/@xml:id)}">{$b/data(@xml:id)}</a>
 ) ! (if (position() > 1) then ',' else (), .) 

a bit closer to your second attempt.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • I just edited into my question something similar using xquery's `for $x at $position in $y ` - `count` is a function that goes into assigning variable. The `order by` is now strangely being rejected by XQuery as a cardinality problem. – jbrehr Nov 06 '18 at 15:38
  • That would be another problem then, you haven't shown where you put the order by in your new attempt. Both my suggestion have not altered your original code, just incorporated them into larger expressions. – Martin Honnen Nov 06 '18 at 15:46
  • I just tried your second one and it works. It's a fascinating solution that I only half-understand. What is the `!` doing that allows `(if (position() > 1) then (',', .) else .) ` to operate? – jbrehr Nov 06 '18 at 15:53
  • `!` is the so called "map" operator https://www.w3.org/TR/xquery-31/#id-map-operator introduced in XPath 3 respectively XQuery 3 to map each item of the sequence on the left to the expression on the right hand side. – Martin Honnen Nov 06 '18 at 15:57
  • So, in plain language, the `query result` is a sequence (left of `!`) and to that result is mapped (on the right) the XSLT `position()` to determined if something should be done based on that ? – jbrehr Nov 06 '18 at 16:07
  • Your `for .. return ...` expression creates a sequence of `a` element nodes and the map expression `for .. return ... ! (if (position() > 1) then (',', .) else .)` evaluates the right hand side, the `if` expression, for each `a` element and returns a new sequence with the result of the `if` expression applied to each `a` element. For the first `a` element the `if` expression returns that `a` element, for the other elements it evaluates to a two item sequence of a string with a comma and the `a` element. – Martin Honnen Nov 06 '18 at 18:30
  • @jbrehr You may've stumbled into eXist's lack of support (yet) for the `count` clause. The map operator + `position()` function is one substitute; another would be to use the `at` clause in the FLWOR. Hopefully we can get the `count` clause added soon! – Joe Wicentowski Nov 08 '18 at 20:48