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.")

Reply via email to