smo
smo

Reputation: 89

XQuery: How to add a comma after sequence except for the last element

I have the following xml:

<form>
<bibl>
    <biblScope>1</biblScope>
    <biblScope>a</biblScope>
</bibl>
<bibl>
    <biblScope>2</biblScope>
    <biblScope>b</biblScope>
</bibl>
<bibl>
    <biblScope>38</biblScope>
    <biblScope>c</biblScope>
</bibl>
</form>

Using this XQuery

for $bibl in form/bibl 
return
<div>
{
for $biblScope in $bibl/biblScope/text()
    return
    $biblScope
}
{if ($bibl[position()] ne $bibl[last()]) then ',' else '@'}
</div>  

The <biblScope> contents are printed one after another. After each biblelement, I would like to add a separator (comma / ",") except for the last one, but with the code above, all I get is

<div>
1a
@
</div>
<div>
2b
@
</div>
<div>
38c
@
</div>

and this is definitely wrong, because what I would like to have is

<div>1a,</div>
<div>2b,</div>
<div>38c@</div>

(add a comma after every bibl element content; the last one is supposed to be followed by @ instead of a comma.)

Having tried different things for some time now, I could need some help. What is the proper way to do this?

Upvotes: 2

Views: 819

Answers (2)

Jens Erat
Jens Erat

Reputation: 38682

The problem is that position() and last() work on the current context, which is not set by flwor expressions. If you want to use similar semantics, use the at $position syntax to get a position counter, and define $last as the number of results:

let $last := count(form/bibl)
for $bibl at $position in form/bibl 
return
<div>
{
for $biblScope in $bibl/biblScope/text()
    return
    $biblScope
}
{if ($position ne $last) then ',' else '@'}
</div>  

If you're able to use XQuery 3.0, the application operator ! might be of use here. Replacing the unnecessary loops by axis steps and element constructors in those, you can rely on the positional predicates, as the context is set to to <bibl/> elements:

form/bibl ! <div>
{
  biblScope/text(),
  if (position() ne last()) then ',' else '@'
}
</div>

Upvotes: 4

Michael Kay
Michael Kay

Reputation: 163352

First note that this:

for $biblScope in $bibl/biblScope/text()
    return
    $biblScope

can be replaced by this:

$bibl/biblScope/text()

(This bit of verbosity is a surprisingly common mistake. It suggests you're thinking procedurally - processing items one at a time, rather than processing sets.)

Then this:

<div>
{$bibl/biblScope/text()}
{if ($bibl[position()] ne $bibl[last()]) then ',' else '@'}
</div> 

should be replaced by this:

<div>{string-join($bibl/biblScope, ',')}@</div>

Note that I've also got rid of the unnecessary (and arguably incorrect) use of /text(), which is another XQuery anti-pattern.

Upvotes: 4

Related Questions