I have a XML file:
<books>
<title>Moby-Dick</title>
<author>Herman Melville</author>
<title>Sunrise Nights</title>
<author>Jeff Zentner</author>
<author>Brittany Cavallaro</author>
<price>14.52€</price>
<title>My Salty Mary</title>
<author>Cynthia Hand</author>
<author>Brodi Ashton</author>
<author>Jodi Meadows</author>
</books>
Which I would like to transform as:
<books>
<book>
<title>Moby-Dick</title>
<author>Herman Melville</author>
</book>
<book>
<title>Sunrise Nights</title>
<author>Jeff Zentner</author>
<author>Brittany Cavallaro</author>
<price>14.52€</price>
</book>
<book>
<title>My Salty Mary</title>
<author>Cynthia Hand</author>
<author>Brodi Ashton</author>
<author>Jodi Meadows</author>
</book>
</books>
The logic is to create a new book
every time we encounter a title
and put every following "non‑title" nodes into that book.
Here's what I tried so far:
let $books := (
doc("books.xml")/books/* =>
fold-left((array{}, 0), function($acc, $node) {
let
$arr := $acc[1],
$idx := $acc[2]
return
if (name($node) = "title")
then ($arr => array:append($node), $idx+1)
else ($arr => array:put($idx, ($arr($idx), $node)), $idx)
})
)[1]
return
<books>{
for $book in $books
return <book>{$book}</book>
}</books>
But I get
<books>
<book>
<title>Moby-Dick</title>
<author>Herman Melville</author>
<title>Sunrise Nights</title>
<author>Jeff Zentner</author>
<author>Brittany Cavallaro</author>
<price>14.52€</price>
<title>My Salty Mary</title>
<author>Cynthia Hand</author>
<author>Brodi Ashton</author>
<author>Jodi Meadows</author>
</book>
</books>
ASIDE: group by
doesn't seem to be useful to solve the current problem, so I tried to group the books in an array, but I have no idea if that's the correct way to do it; any tip is welcome.
If you have the option of using XSLT 2.0+, use:
<xsl:template match="booke">
<books>
<xsl:for-each-group select="*"
group-starting-with="title">
<book>
<xsl:copy-of select="current-group()"/>
</book>
</xsl:for-each-group>
</books>
</xsl:template>
In XQuery 3.0+ it can be done using the FLWOR window
clause.
for tumbling window $w in books/*
start $s when $s[self::title]
return <book>{$w}</book>
Not tested.