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.