Should it be possible to declare a function in the client?
Thursday, 27 February 2020 · 16 messages · last reply 3 Mar 2020
BE
Ben Engbers27 Feb 2020 12:54 #
Hi,
My RBaseX client is finally stable enough to use it for real development. All regular commands are executed without errors. But now I am facing another problem.
In a client-session, I want to use the following function:
fn_get_words_txt <- "declare function local:cloudWords( $Veld as
xs:string) as xs:string* {
let $base := collection('IncidentRemarks/Incidents')/csv/record
let $txt := string-join( $base/*[name() = $Veld]/text(), ' ')
let $words := tokenize($txt,'(\\s|[,.!:;]|[n][b][s][p][;])+')
return ($words)};"
(Doubling the '\' in the regular expression-string is R-specific.)Session$Execute(fn_get_words_txt) returns:
Gestopt bij , 1/8:
Onbekend commando: declare. Probeer 'help'.
Error in Session$Execute(fn_get_words_txt) : Gestopt bij , 1/8: Onbekend commando: declare. Probeer 'help'.
fn_get_words_Query <- Session$Query(fn_get_words_txt)
fn_get_words_Query$queryObject$ExecuteQuery() returns:
Error in private$default_query_pattern(match.call()[[1]]) :
Gestopt bij ., 5/20:
[XPST0003] Expecting expression.Since fn_get_words_txt neither represents a regular command nor a regular function-all, I understand these errors.
Before I even start trying to implement this in my package, my question is if it should be able to create local functions for that session? If so, any idea how to tackle this problem? Could the problem be genaralized to the question how a prolog can be added or changed?
Cheers,
Ben
CG
Christian Grün27 Feb 2020 16:41 #
Hi Ben,
Quoted text · Ben Engbers · 1 line
> Session$Execute(fn_get_words_txt) returns:
If you want to evaluate XQuery, you will either need to prefix your query string with the XQUERY command, or, as you’ve already done…
Quoted text · Ben Engbers · 1 line
> fn_get_words_Query <- Session$Query(fn_get_words_txt)
…create a query object, and attach the actual function call to your query string.
If you want to make XQuery code persistent for future invocations, you can include your function in an XQuery library module and install this module in the repository [1].
Best,
Christian
[1] http://docs.basex.org/wiki/Repository
BE
Ben Engbers27 Feb 2020 19:14 #
Quoted text · Christian Grün · 5 lines
Op 27-02-2020 om 16:41 schreef Christian Grün: > Hi Ben, > > …create a query object, and attach the actual function call to your > query string.
I already thougth about that but what would be the benefit of repeating the function-definition, every time I want to call the function ;-( ?
Quoted text · Christian Grün · 3 lines
> If you want to make XQuery code persistent for future invocations, you > can include your function in an XQuery library module and install this > module in the repository [1].
I will probably go for this.
While experimenting (I try to speed up the querys), I compared the results from these 2 querys:
Word_Inc_Rm_Stop_txt <- "import module namespace functx =
'http://www.functx.com';
let $txt :=
collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text()
let $INC_RM := tokenize(lower-case(string-join($txt)),
'(\\s|[,.!:;]|[n][b][s][p][;])+')
let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text()
let $Stop := functx:value-except($INC_RM, $Stoppers)
return $Stop"
Word_Inc_Rm_Stop <- Session$Execute(as.character(glue("xquery
{Word_Inc_Rm_Stop_txt}")))$result[[1]]
Word_Inc_Rm_Stop_Count <- length(Word_Inc_Rm_Stop)
Word_Inc_Rm_Stop_txt_2 <- "import module namespace functx =
'http://www.functx.com';
let $txt :=
collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text()
let $INC_RM := tokenize(lower-case(string-join($txt)),
'(\\s|[,.!:;]|[n][b][s][p][;])+')
let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text()
let $Stop := functx:value-except($INC_RM, $Stoppers)
return count($Stop)"
Word_Inc_Rm_Stop_Count_2 <- Session$Execute(as.character(glue("xquery
{Word_Inc_Rm_Stop_txt_2}")))$result[[1]]These are the processing-times:
Version 1:
Quoted text · Christian Grün · 1 line
> print(proc.time() - ptm)
user system elapsed
2.903 0.022 3.160
Version 2:
Quoted text · Christian Grün · 1 line
> print(proc.time() - ptm)
user system elapsed
0.041 0.004 1.089
I guess it makes sense to put effort in speeding up my code. But what bothers me is the following.
The first query computes the length from the vector that is returned, The result is 5842.
The second query returns the length as computed by basex. This result is
5843. The GUI also returns 5843 as result.
I copied the output from
..
return $Stop
to a new LibreOffice-document. That document counts 5842 words.
Who is right?
Cheers,
Ben
CG
Christian Grün27 Feb 2020 19:19 #
Quoted text · Ben Engbers · 4 lines
> > While experimenting (I try to speed up the querys), I compared the > results from these 2 querys: >
It’s difficult to understand what’s going on here. Could you please provide us self-contained queries without the R wrapper code?
Quoted text · Ben Engbers · 59 lines
> Word_Inc_Rm_Stop_txt <- "import module namespace functx = > 'http://www.functx.com'; > let $txt := > collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text() > let $INC_RM := tokenize(lower-case(string-join($txt)), > '(\\s|[,.!:;]|[n][b][s][p][;])+') > let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text() > let $Stop := functx:value-except($INC_RM, $Stoppers) > return $Stop" > Word_Inc_Rm_Stop <- Session$Execute(as.character(glue("xquery > {Word_Inc_Rm_Stop_txt}")))$result[[1]] > Word_Inc_Rm_Stop_Count <- length(Word_Inc_Rm_Stop) > > Word_Inc_Rm_Stop_txt_2 <- "import module namespace functx = > 'http://www.functx.com'; > let $txt := > collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text() > let $INC_RM := tokenize(lower-case(string-join($txt)), > '(\\s|[,.!:;]|[n][b][s][p][;])+') > let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text() > let $Stop := functx:value-except($INC_RM, $Stoppers) > return count($Stop)" > Word_Inc_Rm_Stop_Count_2 <- Session$Execute(as.character(glue("xquery > {Word_Inc_Rm_Stop_txt_2}")))$result[[1]] > > These are the processing-times: > > Version 1: > > print(proc.time() - ptm) > user system elapsed > 2.903 0.022 3.160 > Version 2: > > print(proc.time() - ptm) > user system elapsed > 0.041 0.004 1.089 > > I guess it makes sense to put effort in speeding up my code. But what > bothers me is the following. > > The first query computes the length from the vector that is returned, > The result is 5842. > The second query returns the length as computed by basex. This result is > 5843. The GUI also returns 5843 as result. > > I copied the output from > .. > return $Stop > > to a new LibreOffice-document. That document counts 5842 words. > > Who is right? > > Cheers, > Ben > > > > >
BE
Ben Engbers27 Feb 2020 20:48 #
Quoted text · Christian Grün · 3 lines
Op 27-02-2020 om 19:19 schreef Christian Grün: > It’s difficult to understand what’s going on here. Could you please > provide us self-contained queries without the R wrapper code?
Version 1:
import module namespace functx = 'http://www.functx.com';
(: Extract the text :)
let $txt := collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text()
(: Convert to lower-case and tokenize :)
let $INC_RM := tokenize(lower-case(string-join($txt)),
'(\\s|[,.!:;]|[n][b][s][p][;])+')
(: Read Stopwords :)
let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text()
(: Remove Stopwords :)
let $Stop := functx:value-except($INC_RM, $Stoppers)
return $Stop"My R-code first executes this as XQUERY and then calculates the length of the returned list (=5842).
Version 2:
import module namespace functx = 'http://www.functx.com';
let $txt := collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text()
let $INC_RM := tokenize(lower-case(string-join($txt)),
'(\\s|[,.!:;]|[n][b][s][p][;])+')
let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text()
let $Stop := functx:value-except($INC_RM, $Stoppers)
return count($Stop)Returns the length of the sequence (counts 5843 words).
The '\\' in the regular expression is intentional (R-specific). With a single '\' the query can be executed in BaseXGUI.
Does this help?
Ben
MS
Majewski, Steven Dennis (sdm7g)27 Feb 2020 21:43 #
So, if the counts are different depending on who is counting ( R or BaseX ), The first question is : who is correct ?
( And the 2nd question is probably: what do you mean by correct ? as the semantics of XQuery sequences and whatever destination R datatype is being counted may be slightly different. I don’t know R that well, but semantics of XQuery sequences and arrays are rather different, for example. )
— Steve M.
Quoted text · Ben Engbers · 42 lines
> On Feb 27, 2020, at 2:48 PM, Ben Engbers <Ben.Engbers at Be-Logical.nl> wrote: > > Op 27-02-2020 om 19:19 schreef Christian Grün: >> It’s difficult to understand what’s going on here. Could you please >> provide us self-contained queries without the R wrapper code? > > Version 1: > > import module namespace functx = 'http://www.functx.com'; > (: Extract the text :) > let $txt := collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text() > (: Convert to lower-case and tokenize :) > let $INC_RM := tokenize(lower-case(string-join($txt)), > '(\\s|[,.!:;]|[n][b][s][p][;])+') > (: Read Stopwords :) > let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text() > (: Remove Stopwords :) > let $Stop := functx:value-except($INC_RM, $Stoppers) > return $Stop" > > My R-code first executes this as XQUERY and then calculates the length > of the returned list (=5842). > > Version 2: > > import module namespace functx = 'http://www.functx.com'; > let $txt := collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text() > let $INC_RM := tokenize(lower-case(string-join($txt)), > '(\\s|[,.!:;]|[n][b][s][p][;])+') > let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text() > let $Stop := functx:value-except($INC_RM, $Stoppers) > return count($Stop) > > Returns the length of the sequence (counts 5843 words). > > The '\\' in the regular expression is intentional (R-specific). With a > single '\' the query can be executed in BaseXGUI. > > Does this help? > > Ben >
MS
Majewski, Steven Dennis (sdm7g)27 Feb 2020 21:52 #
I also note, that when I try to mock up something similar with one of my texts, the tokenize Seems to give me a zero length string at the start.
It’s there in the output window of basexgui, in the first line, but easy to miss the fact that it’s significant whitespace in this context:
(tokenize(string-join(collection('BOV')[ends-with(
db:path(.), '.tei' )][1]/TEI/text/body/p//text() ), '\W+' ) => distinct-values())[not(. = ( "of", "the", "in", "and", "at","by","to" ))]
August
16
17
2015
Members
Board
Visitors
University
Virginia
met
Retreat
Open
Executive
Session
Forum
…
But visible if I apply string-length to the sequence:
(tokenize(string-join(collection('BOV')[ends-with( db:path(.), '.tei' )][1]/TEI/text/body/p//text() ), '\W+' ) => distinct-values())[not(. = ( "of", "the", "in", "and", "at","by","to" ))] ! string-length(.)
0
6
2
2
4
7
5
8
10
8
3
7
4
9
7
…
I wonder if that’s the semantic difference here.
— Steve M.Quoted text · Steven Dennis (sdm7g) · 52 lines
> On Feb 27, 2020, at 3:43 PM, Majewski, Steven Dennis (sdm7g) <sdm7g at virginia.edu> wrote: > > So, if the counts are different depending on who is counting ( R or BaseX ), > The first question is : who is correct ? > ( And the 2nd question is probably: what do you mean by correct ? as the semantics of XQuery sequences and whatever destination R datatype is being counted may be slightly different. I don’t know R that well, but semantics of XQuery sequences and arrays are rather different, for example. ) > > — Steve M. > > >> On Feb 27, 2020, at 2:48 PM, Ben Engbers <Ben.Engbers at Be-Logical.nl> wrote: >> >> Op 27-02-2020 om 19:19 schreef Christian Grün: >>> It’s difficult to understand what’s going on here. Could you please >>> provide us self-contained queries without the R wrapper code? >> >> Version 1: >> >> import module namespace functx = 'http://www.functx.com'; >> (: Extract the text :) >> let $txt := collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text() >> (: Convert to lower-case and tokenize :) >> let $INC_RM := tokenize(lower-case(string-join($txt)), >> '(\\s|[,.!:;]|[n][b][s][p][;])+') >> (: Read Stopwords :) >> let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text() >> (: Remove Stopwords :) >> let $Stop := functx:value-except($INC_RM, $Stoppers) >> return $Stop" >> >> My R-code first executes this as XQUERY and then calculates the length >> of the returned list (=5842). >> >> Version 2: >> >> import module namespace functx = 'http://www.functx.com'; >> let $txt := collection('IncidentRemarks/Incidents')/csv/record/INC_RM/text() >> let $INC_RM := tokenize(lower-case(string-join($txt)), >> '(\\s|[,.!:;]|[n][b][s][p][;])+') >> let $Stoppers := doc('TextMining/Stopwoorden.txt')/text/line/text() >> let $Stop := functx:value-except($INC_RM, $Stoppers) >> return count($Stop) >> >> Returns the length of the sequence (counts 5843 words). >> >> The '\\' in the regular expression is intentional (R-specific). With a >> single '\' the query can be executed in BaseXGUI. >> >> Does this help? >> >> Ben >> >
BE
Ben Engbers28 Feb 2020 11:45 #
Quoted text · Majewski, Steven Dennis (sdm7g) · 6 lines
Op 27-02-2020 om 22:03 schreef Majewski, Steven Dennis (sdm7g): > Also: is ‘(&nbsp;)’ what you want as part of you regex to also catch the ampersand ? > I’m just guessing your intent here. > You could also try ‘(\W|&nbsp;)+’ - i.e. non-word, but I’m kind of assuming that it handles non-normalized unicode accented characters correctly and reads them as word chars and not delimiters. That would be, of course, the right thing, but I’ld probably test it first. > > — Steve.
I just copied the regex-expression from this page
"https://en.wikibooks.org/wiki/XQuery/Tag_Cloud" (using regex always gives me headaches ;-( ). But even after removing the "|[n][b][s][p][;]" from the regex, basexgui still returns 5843.
Ben
CG
Christian Grün28 Feb 2020 14:39 #
I was wondering about nbsp as well. Maybe you don’t need it at all, but we’d need to have a look at your files.
Could you additionally provide us with minimized instances of your Incidents and Stopwoorden.txt XML documents? They should have the same structure, but contain only a few lines of contents.
Quoted text · Ben Engbers · 15 lines
On Fri, Feb 28, 2020 at 11:45 AM Ben Engbers <Ben.Engbers at be-logical.nl> wrote: > > Op 27-02-2020 om 22:03 schreef Majewski, Steven Dennis (sdm7g): > > Also: is ‘(&nbsp;)’ what you want as part of you regex to also catch the ampersand ? > > I’m just guessing your intent here. > > You could also try ‘(\W|&nbsp;)+’ - i.e. non-word, but I’m kind of assuming that it handles non-normalized unicode accented characters correctly and reads them as word chars and not delimiters. That would be, of course, the right thing, but I’ld probably test it first. > > > > — Steve. > > I just copied the regex-expression from this page > "https://en.wikibooks.org/wiki/XQuery/Tag_Cloud" (using regex always > gives me headaches ;-( ). But even after removing the "|[n][b][s][p][;]" > from the regex, basexgui still returns 5843. > > Ben
MS
Please see my earlier message to the list, as I’m betting it’s the zero-length string that isn’t getting counted in R! — Steve M.
Quoted text · Ben Engbers · 18 lines
> On Feb 28, 2020, at 5:45 AM, Ben Engbers <Ben.Engbers at be-logical.nl> wrote: > > Op 27-02-2020 om 22:03 schreef Majewski, Steven Dennis (sdm7g): >> Also: is ‘(&nbsp;)’ what you want as part of you regex to also catch the ampersand ? >> I’m just guessing your intent here. >> You could also try ‘(\W|&nbsp;)+’ - i.e. non-word, but I’m kind of assuming that it handles non-normalized unicode accented characters correctly and reads them as word chars and not delimiters. That would be, of course, the right thing, but I’ld probably test it first. >> >> — Steve. > > I just copied the regex-expression from this page > "https://en.wikibooks.org/wiki/XQuery/Tag_Cloud" (using regex always > gives me headaches ;-( ). But even after removing the "|[n][b][s][p][;]" > from the regex, basexgui still returns 5843. > > Ben > > >
BE
Quoted text · Christian Grün · 7 lines
Op 28-02-2020 om 14:39 schreef Christian Grün: > I was wondering about nbsp as well. Maybe you don’t need it at all, > but we’d need to have a look at your files. > > Could you additionally provide us with minimized instances of your > Incidents and Stopwoorden.txt XML documents? They should have the same > structure, but contain only a few lines of contents.
It should be relatively easy to create a database with the
(approximately 500) stopwords and another database with with the Incidents. Shall I send you a backup of those two databases?
Ben
CG
Christian Grün28 Feb 2020 16:57 #
A few incident and stories entries should be sufficient. Just attach the two XML documents to your next reply.
Ben Engbers <Ben.Engbers at be-logical.nl> schrieb am Fr., 28. Feb. 2020, 16:20:
Quoted text · Ben Engbers · 15 lines
> Op 28-02-2020 om 14:39 schreef Christian Grün: > > I was wondering about nbsp as well. Maybe you don’t need it at all, > > but we’d need to have a look at your files. > > > > Could you additionally provide us with minimized instances of your > > Incidents and Stopwoorden.txt XML documents? They should have the same > > structure, but contain only a few lines of contents. > > It should be relatively easy to create a database with the > (approximately 500) stopwords and another database with with the Incidents. > Shall I send you a backup of those two databases? > > Ben > >
CG
Christian Grün2 Mar 2020 13:27 #
Hi Ben,
I had a look at the attached documents you sent to me in private. I couldn’t find out why the XQuery and R results differ (I’m pretty sure it’s due to the zero-length string, as guessed by Steve), but I noticed that your query may not necessarily do what you are trying to achieve.
Here is an alternative version that, as I believe, should match your requirements better:
let $words := distinct-values(
for $text in db:open('Incidents')/csv/record/INC_RM
return ft:tokenize($text)
)
let $stopwords := db:open('Stopwords')/text/line
let $result := $words[not(. = $stopwords)]
return sort($result)There is no need to remove nbsp substrings as they’ll never occur in your input, and the
ft:tokenize function will ensure that your input (case, special characters, diacritics) will be normalized (see [1,2] for more details). Using functx is perfectly valid; I only removed the reference to make the code a bit shorter.
Hope this helps,
Christian
[1] http://docs.basex.org/wiki/Full-Text_Module#ft:tokenize
[2] http://docs.basex.org/wiki/Full-TextQuoted text · Christian Grün · 23 lines
On Fri, Feb 28, 2020 at 4:57 PM Christian Grün <christian.gruen at gmail.com> wrote: > > A few incident and stories entries should be sufficient. Just attach the two XML documents to your next reply. > > > > Ben Engbers <Ben.Engbers at be-logical.nl> schrieb am Fr., 28. Feb. 2020, 16:20: >> >> Op 28-02-2020 om 14:39 schreef Christian Grün: >> > I was wondering about nbsp as well. Maybe you don’t need it at all, >> > but we’d need to have a look at your files. >> > >> > Could you additionally provide us with minimized instances of your >> > Incidents and Stopwoorden.txt XML documents? They should have the same >> > structure, but contain only a few lines of contents. >> >> It should be relatively easy to create a database with the >> (approximately 500) stopwords and another database with with the Incidents. >> Shall I send you a backup of those two databases? >> >> Ben >>
BE
Ben Engbers3 Mar 2020 11:55 #
Quoted text · Christian Grün · 25 lines
Op 02-03-2020 om 13:27 schreef Christian Grün: > Hi Ben, > > Here is an alternative version that, as I believe, should match your > requirements better: > > let $words := distinct-values( > for $text in db:open('Incidents')/csv/record/INC_RM > return ft:tokenize($text) > ) > let $stopwords := db:open('Stopwords')/text/line > let $result := $words[not(. = $stopwords)] > return sort($result) > > There is no need to remove nbsp substrings as they’ll never occur in > your input, and the ft:tokenize function will ensure that your input > (case, special characters, diacritics) will be normalized (see [1,2] > for more details). Using functx is perfectly valid; I only removed the > reference to make the code a bit shorter. > > Hope this helps, > Christian > > [1] http://docs.basex.org/wiki/Full-Text_Module#ft:tokenize > [2] http://docs.basex.org/wiki/Full-Text
Hi Christian,
Since my primary goal for this is moment is to see how basex/XQuery can be used for full text analysis (and compare the results or needed efforts with similar tasks in R), I am very glad that you brought the
fn:tokenize() function to my attention!
Ben
PS,
Just for fun, I created a repository with this tiny function:declare function tidyTM:wordFreqs(
$Words as xs:string*)
{
for $w in $Words
let $f := $w
group by $f
order by count($w) descending
return ($f, count($w))
} ;It took less than 10 minutes to create a repository and populate with this function.
Creating a R-package takes much longer time!!!
BE
Quoted text · Christian Grün · 14 lines
Op 02-03-2020 om 13:27 schreef Christian Grün: > Hi Ben, > > Here is an alternative version that, as I believe, should match your > requirements better: > > let $words := distinct-values( > for $text in db:open('Incidents')/csv/record/INC_RM > return ft:tokenize($text) > ) > let $stopwords := db:open('Stopwords')/text/line > let $result := $words[not(. = $stopwords)] > return sort($result) >
Hi Christian,
I don't have a separate database 'Stopwords'. The file 'Stopwoorden.txt' was used as option while creating the 'Incidents'-database. Since I have several lists with stopwords and several lists that can be used with sentiment-analysts, I have stored all those files in a 'Textmining' database.
Without caring about stopwords, this query works:
let $words :=
for $text in collection('IncidentRemarks/Incidents')/csv/record/INC_RM
return ft:tokenize($text)
return $words("sort($words)" returns a long list of numbers)
In an article, ("Full-Text Search in XML Databases" by Skoglund, Robin, 2009), I saw this example on page 23:
1 (: will match "propagating few errors" :)
2 /books /book [@number="1"]//p ftcontains" propagation of errors" 3 with stemming with stop words ("a" , "the" , "of")
The query may be changed to "stemming without stop words".
What I would like to see in BaseX, is that similar as in xquery, 'Stopwords' could be used as if it were a separate resource in the 'Incidents'-database and that it could be used as follows in the query:
let $words :=
for $text in collection('IncidentRemarks/Incidents')/csv/record/INC_RM
with stemming without stop words
return ft:tokenize($text)
return $wordsAs far as I understand, 'stemming' has alrady been made available in the ft:module.
Would it also be possible to use
STOPWORDS in a similar way?
Cheers,
BenCG
Christian Grün3 Mar 2020 16:43 #
Quoted text · Ben Engbers · 3 lines
> What I would like to see in BaseX, is that similar as in xquery, > 'Stopwords' could be used as if it were a separate resource in the > 'Incidents'-database and that it could be used as follows in the query:
That’s not supported by the spec (see [1] for all the details). You need to write your stop words to a local file (e.g. via
file:write); after that, you can reference this file in your fulltext expression:
/books/book [@number="1"]//p contains text "propagation of errors" using stop words at "stopwords.txt"
However, the more common and more efficient approach is to supply a stop words file when creating the full-text index. This will reduce the size of your full-text index (which is the major advantage in practice). If you call ft:search or ...[text() contains text ...] later on, this stop words file will be used to filter out terms that occur in your search terms.Quoted text · Ben Engbers · 3 lines
> As far as I understand, 'stemming' has alrady been made available in the > ft:module. > Would it also be possible to use STOPWORDS in a similar way?
Which function would you like to see extended?
[1] https://www.w3.org/TR/xpath-full-text-10/#ftstopwordoption
Written for BaseX 9.3. BaseX may have changed since: please check the current documentation.
⚡Generated with XQuery