|
>Home>Learn XQuery> XQuery Tips and Tricks>Grouping Elements
Print
Can I group elements based on their relative positions?
My XML is structured this way:
<books>
<title>title 1</title>
<price>100</price>
<date>01012007</date>
<title>title 2</title>
<price>90</price>
<publisher>pub 1</publisher>
</books>
The XML file contains a long list of books, about which only the <title> element is guaranteed to be always present.
How can I modify the XML document using XQuery to group each book's properties under a single element, like this:
<books>
<book>
<title>title 1</title>
<price>100</price>
<date>01012007</date>
</book>
<book>
<title>title 2</title>
<price>90</price>
<publisher>pub 1</publisher>
</book>
</books>
The simplest solution we can think of is this:
<books> {
for $title in /books/title
return
<book> {
$title,
let $nextTitle := $title/following-sibling::title[1]
for $prop in $title/following-sibling::*[local-name() != "title"]
where empty($nextTitle) or $prop << $nextTitle
return $prop
} </book>
} </books>
In this solution, we basically iterate over all the <title> elements. For each of them we find the next <title> element (if any), and then we iterate through all the elements contained in between the two <title> elements.
The code can become a bit easier to read and more resuable if we introduce a function, like this:
declare function local:getRelatedSiblings($item) {
let $nextItem := $item/following-sibling::*[local-name()=$item/local-name()][1]
for $related in $item/following-sibling::*[local-name()!=$item/local-name()]
where empty($nextItem) or $related << $nextItem
return $related
};
<books> {
for $title in /books/title
return
<book> {
$title,
for $prop in local:getRelatedSiblings($title)
return $prop
} </book>
} </books>
Next Question!
What's the proper way to use the node sequence (<< and >>) operators?
|