On Fri, May 20, 2022 at 09:40:49AM +0300, Mark Bordelon scripsit:
> Perhaps I have just discovered my own answer. Would you all agree this is the
> best way?
>
> xquery /text[starts-with(@id,
> 'Bible.N')]//verse[@id='Rev.22.5']/following-sibling::node()[following-sibling::verse][1]
Generally speaking, any time you're relying on understanding the
implicit context of position() in a complex XPath expression, it's going
to hurt. For example, following-sibling::node() can come to grief on a
processing instruction or a comment.
XPath expressions are powerful but XQuery is more powerful and has some
less-brain-melting options.
I have, in query below, wrapped the text of the verse in the verse
element because that is much more natural to XML. In a production
environment I'd adovcate for using something to pre-wrap all the data,
but if you can't do that, that wrapper step ought to be smarter and
handle processing instructions and comments creating multiple text nodes
in a verse.
let $example as element(text) :=
<text id="Rev">
<verse id="Rev.22.14"/>
Beati, qui lavant stolas suas in sanguine Agni : ut sit potestas eorum
in ligno vit=C3=A6, et per portas intrent in civitatem.
<verse id="Rev.22.15"/>
Foris canes, et venefici, et impudici, et homicid=C3=A6, et idolis
servientes, et omnis qui amat et facit mendacium.
<verse id="Rev.22.16"/>
</text>
(: we want to be able to return a range of verses relative to any particular
verse :)
(: create map entries for the information we expect to want about the verses;
relative position, id value, and the actual verse :)
let $mapRefs as map(*)+ :=
for $verse at $index in $example/verse
let $id as xs:string := $verse/@id/string()
return (map:entry($index,$id),
map:entry($id,$index),
map:entry($id,element {name($verse)}
{($verse/@*,normalize-space($verse/following-sibling::text()[1]))})
)
(: pull the sequence of map entries into three merged maps:)
(: position to id :)
let $posIdMap as map(xs:integer,xs:string) :=
$mapRefs[map:keys(.) instance of xs:integer] => map:merge()
(: id to position :)
let $idPosMap as map(xs:string,xs:integer) :=
$mapRefs[not(map:keys(.) instance of xs:integer) and not(.?* instance of
element(verse))] => map:merge()
(: id to verse :)
let $idVerseMap as map(xs:string,element(verse)) :=
$mapRefs[.?* instance of element(verse)] => map:merge()
(: which verse do we start with? How many verses after this do we want? :)
(: in production these would presumably be external variables/parameters :)
let $initial as xs:string := "Rev.22.14"
let $offset as xs:integer := 1
(: where is that intial verse in the positional sequence of verses? :)
let $initialPos as xs:integer := $idPosMap($initial)
(: return the range of verses in order :)
for $found in ($initialPos to $initialPos + $offset)
return $posIdMap($found) => $idVerseMap()
Much longer, not itself production ready, but potentially reliable.
Clever XPath is rarely reliable.
--
Graydon Saunders | [email protected]
Þæs oferéode, ðisses swá mæg.
-- Deor ("That passed, so may this.")