Jayce444
Jayce444

Reputation: 9063

XQuery get position of node relative only to siblings with same tag name

I am trying to add an attribute to an tag in XQuery that corresponds to its position, but only concerning siblings of the same tag name. Specifically, items in a list. So here's 2 potential outputs of the query:

<list>
    <title>foo</title>
    <item>bar1</item>
    <item>bar2</item>
</list>

 <list>
     <item>bar1</item>
     <item>bar2</item>
</list>

And for both, here's the desired outcome when adding the attribute:

<list>
    <title>foo</title>
    <item position="1">bar1</item>
    <item position="2">bar2</item>
</list>

 <list>
     <item position="1">bar1</item>
     <item position="2">bar2</item>
</list>

The current query I'm using is:

count($item/preceding-sibling::*)+1

But then the count includes every tag, not just item ones. So in the example above with the title tag, it counts that, so I get:

<list>
    <title>foo</title>
    <item position="2">bar1</item>
    <item position="3">bar2</item>
</list>

I've tried a few things, such as subtracting the count of all children of the list that don't have the item tag name, such as:

    count($item/preceding-sibling::*)+1-count($list/*[local-name() != 'item'])

and a bunch of other similar methods but to no avail. How can I achieve this?

EDIT:

Here's a contextual example of how I'm doing it. Input data:

<LIST1>
    <TITLE>REASON FOR REVISION</TITLE>
    <L1ITEM KEY="L1I10254244923043">
        <PARA>Summary - Background: Updated and added related references.</PARA>
    </L1ITEM>
    <L1ITEM KEY="L1I20300231297755">
        <PARA>Summary - Action: Updated to add more work and new groups and configurations.</PARA>
    </L1ITEM>
</LIST1>

Then I pass the LIST1 root element into this function:

declare function local:listx($listx as element()){
   <list level="{substring(name($listx),5)}">
   {
     for $lxitem in $listx/*
     return if (name($lxitem) = "TITLE") then (
       <title>{local:concatText($lxitem)}</title>
     )
     else local:extract($lxitem, "item", [
       ["position", count($lxitem/preceding-sibling::*)+1]
     ])
   }
   </list>
};

The concatText function just joins all the text together with a space delimiter, no issues with that. The extract() function is as follow:

declare function local:extract(
  $root as element(), $tag as xs:string, $props as array(*)
) {
  element {$tag}{

    for $prop at $i in $props
    where array:size($prop) != 0
    return attribute {$props($i)(1)} {$prop($i)(2)},

    for $child in $root/*
    return switch(name($child))
      case "TXTGRPHC" return local:plaintext($child)
      case "PARA" return local:plaintext($child)
      case ...
      default return ()

  }
};

As a side note, the way I add attributes here, with the whole 2D array $props thing, only ever adds the first attribute, the rest are ignored. Though that's another problem.

Upvotes: 0

Views: 888

Answers (1)

Martin Honnen
Martin Honnen

Reputation: 167541

As you use BaseX, you could use XQuery Update http://docs.basex.org/wiki/XQuery_Update, for instance

copy $items := <root>
<list>
    <title>foo</title>
    <item>bar1</item>
    <item>bar2</item>
</list>

 <list>
     <item>bar1</item>
     <item>bar2</item>
</list>
</root>
modify (
for $item in $items/list/item
return insert node attribute {'id'} {count($item/(., preceding-sibling::item))} into $item
)
return $items

Result is

<root>
  <list>
    <title>foo</title>
    <item id="1">bar1</item>
    <item id="2">bar2</item>
  </list>
  <list>
    <item id="1">bar1</item>
    <item id="2">bar2</item>
  </list>
</root>

Upvotes: 2

Related Questions