Skip to content

Instantly share code, notes, and snippets.

@line-o
Last active May 19, 2021 21:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save line-o/f3683052951fb9eec28651638f779720 to your computer and use it in GitHub Desktop.
Save line-o/f3683052951fb9eec28651638f779720 to your computer and use it in GitHub Desktop.
retrieve all XPaths to nodes within an element

what is the shortest XQuery that returns all XPaths in the current context

  1. //(node()|@*)!path()
  2. //node()!(.,@*)!path(.)

Special thanks to Reece H. Dunn for pointing me towards fn:path

Working Demos (previous versions)

(: thanks to Reece H. Dunn for hinting at the fn:path function :)
//node()!(.,@*)!path()
//node()!(.,@*)!string-join(tail((ancestor::*,.))!(let$n:=name(.)let$t:=typeswitch(.)case attribute()
return -2 caseelement() return -1 casecomment() return 1 casetext() return 2 default return 3 return
if($t>0)then let$e:=(["comment",preceding-sibling::comment()],["text",preceding-sibling::text()],["node",1])[$t]return$e?1||"()["||count($e?2)+1||"]"else
[$n||"["||count(preceding-sibling::*[name(.)=$n])+1||"]","@"||name(.)](-$t)),"/")!concat("/",.)
//node()!(.,@*)!string-join(tail((ancestor::*,.))!(let$n:=name(.)let$t:=typeswitch(.)case
attribute()return(-2)case element()return(-1)case comment()return(1)case text()return(2)default return(3)return if($t>0)then
let$e:=(["comment",preceding-sibling::comment()],["text",preceding-sibling::text()],["node",1])[$t]return$e?1||"()["||count($e?2)+1||"]"else
[$n||"["||count(preceding-sibling::*[name(.)=$n])+1||"]","@"||name(.)](-$t)),"/")!concat("/",.)
xquery version "3.1";
declare namespace xpaths="http://line-o.de/ns/xpaths";
declare namespace tei="http://www.tei-c.org/ns/1.0";
(:~
: A pure XQuery implementation of fn:path
:)
declare function xpaths:path ($node as node()) as xs:string {
(
($node/ancestor::*, $node)
! (
let $t :=
typeswitch(.)
case attribute() return -2
case element() return -1
case comment() return 1
case text() return 2
default return 3
return
if ($t > 0)
then
let $e := (
["comment", preceding-sibling::comment()],
["text", preceding-sibling::text()],
["node", 1]
)[$t]
return ``[`{$e?1}`()[`{count($e?2)+1}`]]``
else
let $n := name(.)
let $qn := xs:QName(name(.))
let $q := "Q{" || namespace-uri-from-QName($qn) || "}" || local-name-from-QName($qn)
let $i := -$t (: see https://github.com/eXist-db/exist/issues/3892 :)
return (
``[`{$q}`[`{count(preceding-sibling::*[name(.)=$n])+1}`]]``,
"@" || $q )[$i]
) => string-join("/")
)
! concat("/", .)
};
let $xml := document {
<xml>
<a><![CDATA[<b/>]]></a>
<c><d>N<!-- no comment --><e/></d></c>
<c xml:id="s123" a="1"><d>E<e/></d></c>
<c><d>M<e/></d></c>
<c><d>O<e/></d></c>
<tei:TEI />
</xml>
}
let $test :=
function ($xpath as xs:string) as item()* {
util:eval(concat("$xml", $xpath))
}
return
$root//node()
! (., @*)
! xpaths:path(.)
! $test(.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment