diff --git a/compose.yaml b/compose.yaml
index 1162664..d2f366c 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -13,7 +13,7 @@ services:
- ./webapp/custom:/srv/basex/lib/custom # jars
- ./webapp/lsp:/srv/basex/webapp/lsp
- ./webapp/static/clients:/srv/basex/webapp/static/clients
-
+ - ./webapp/lsp-manager:/srv/basex/webapp/lsp-manager
# - ./repo:/srv/basex/repo
environment:
- "BASEX_JVM= -Dorg.basex.RESTXQERRORS=false -Dorg.basex.LOGEXCLUDE=/dba"
diff --git a/docs/wordat.xqbk b/docs/wordat.xqbk
index 50eff76..2fda00f 100644
--- a/docs/wordat.xqbk
+++ b/docs/wordat.xqbk
@@ -1 +1 @@
-{"cells":[{"kind":1,"language":"markdown","value":"XPath name charactors"},{"kind":2,"language":"xquery","value":"let $s:=`[A-Z]\r\n | '_'\r\n | [a-z]\r\n | [#xC0-#xD6]\r\n | [#xD8-#xF6]\r\n | [#xF8-#x2FF]\r\n | [#x370-#x37D]\r\n | [#x37F-#x1FFF]\r\n | [#x200C-#x200D]\r\n | [#x2070-#x218F]\r\n | [#x2C00-#x2FEF]\r\n | [#x3001-#xD7FF]\r\n | [#xF900-#xFDCF]\r\n | [#xFDF0-#xFFFD]\r\n `\r\nlet $sreg:= normalize-space($s)\r\n =>tokenize(\"\\|\")\r\n =!>normalize-space()\r\n =!>replace(\"(#x[0-9A-F]+)\",fn($s,$m){\r\n convert:integer-from-base(substring($m,3),16)\r\n =>codepoints-to-string()\r\n })\r\n =!>translate(\"'\",\"\")\r\n =>string-join(\"|\")\r\nreturn $sreg"},{"kind":1,"language":"markdown","value":"# Functions"},{"kind":2,"language":"xquery","value":"declare namespace fos=\"http://www.w3.org/xpath-functions/spec/namespace\";\r\ndoc(\"C:/Users/mrwhe/git/quodatum/basex-lsp/bundles/grammar/function-catalog.xml\")\r\n//fos:function[@prefix=\"fn\"]/@name"}]}
\ No newline at end of file
+{"cells":[{"kind":1,"language":"markdown","value":"XPath name charactors"},{"kind":2,"language":"xquery","value":"let $s:=`[A-Z]\r\n | '_'\r\n | [a-z]\r\n | [#xC0-#xD6]\r\n | [#xD8-#xF6]\r\n | [#xF8-#x2FF]\r\n | [#x370-#x37D]\r\n | [#x37F-#x1FFF]\r\n | [#x200C-#x200D]\r\n | [#x2070-#x218F]\r\n | [#x2C00-#x2FEF]\r\n | [#x3001-#xD7FF]\r\n | [#xF900-#xFDCF]\r\n | [#xFDF0-#xFFFD]\r\n `\r\nlet $sreg:= normalize-space($s)\r\n =>tokenize(\"\\|\")\r\n =!>normalize-space()\r\n =!>replace(\"(#x[0-9A-F]+)\",fn($s,$m){\r\n convert:integer-from-base(substring($m,3),16)\r\n =>codepoints-to-string()\r\n })\r\n =!>translate(\"'\",\"\")\r\n =>string-join(\"|\")\r\nreturn $sreg"},{"kind":1,"language":"markdown","value":"# Functions"},{"kind":2,"language":"xquery","value":"declare namespace fos=\"http://www.w3.org/xpath-functions/spec/namespace\";\r\ndoc(\"C:/Users/mrwhe/git/quodatum/basex-lsp/bundles/grammar/function-catalog.xml\")\r\n//fos:function[@prefix=\"fn\"]/@name"},{"kind":2,"language":"xquery","value":"let $a:=\"/srv/basex/webapp/lsp/etc/snippets.jsonc\"\r\n\r\nreturn unparsed-text($a)!replace(.,\"^(.*)//.*$\",\"$1\",\"m\")"}]}
\ No newline at end of file
diff --git a/webapp/lsp-manager/api-jobs.xqm b/webapp/lsp-manager/api-jobs.xqm
new file mode 100644
index 0000000..9eaaa58
--- /dev/null
+++ b/webapp/lsp-manager/api-jobs.xqm
@@ -0,0 +1,37 @@
+module namespace joba = 'app/jobs';
+
+declare variable $joba:names:=("id","type","state","user","duration","time","start");
+(:
+ id: job ID
+ type: type of the job (command, query, REST, RESTXQ, etc.)
+ state: current state of the job: scheduled, queued, running, cached
+ user: user who started the job
+ duration: evaluation time (included if a job is running or if the result was cached)
+ start: next start of job (included if a job will be executed repeatedly)
+ time: time when job was registered
+ :)
+declare function joba:jobs()
+as element(job)*
+{
+ job:list()[. ne job:current()] ! job:list-details(.)
+};
+
+declare function joba:table($jobs as element(job)*,$names as xs:string+)
+as element(table)
+{
+
+
+ {$names!{ . } }
+
+
+ {for $j in $jobs
+ return
+ {for $n in $names
+ let $val:= $j/@*[name() eq $n]
+ return {$val/string()}
+ }
+ }
+
+
+
+};
\ No newline at end of file
diff --git a/webapp/lsp-manager/app.xqm b/webapp/lsp-manager/app.xqm
new file mode 100644
index 0000000..a657959
--- /dev/null
+++ b/webapp/lsp-manager/app.xqm
@@ -0,0 +1,80 @@
+(:~
+ app
+ :)
+module namespace app = 'app/common';
+import module namespace cm = "app/cm" at "common.xqm";
+
+
+(:~
+ : Redirects to the start page.
+ : @return redirection
+ :)
+declare %rest:path('/app')
+function app:redirect(
+) as element(rest:response) {
+ web:redirect('/app/home')
+};
+
+(:~
+ : Returns a file.
+ : @param $file file or unknown path
+ : @return rest binary data
+ :)
+declare %rest:path('/app/static/{$file=.+}')
+ %output:method('basex')
+ %perm:allow('public')
+function app:file( $file as xs:string)
+as item()+ {
+ let $path:=file:base-dir() || 'static/' || $file
+ return if(file:exists($path))
+ then cm:response-file($path)
+ else (
+
+
+
+
+
+ ,
+ "The requested resource is not available."
+ )
+};
+
+
+
+(:~ start page. :)
+declare %rest:path('/app/home')
+function app:home() as item()* {
+ rest:init(true()),
+ cm:htmx2("home.htm", map{"version" :"0.0.2"})
+};
+
+
+
+declare %rest:path('/app/profile')
+function app:profile() {
+ cm:htmx2("profile.htm", map{})
+};
+
+declare %rest:path('/app/dev')
+function app:dev() {
+ cm:htmx2("dev/home.htm", map{})
+};
+
+
+declare %rest:path('/app/notifications')
+function app:notifications() {
+ cm:htmx2("notifications.htm", map{})
+};
+
+declare %rest:path('/app/components')
+function app:components() {
+ cm:htmx2("components.htm", map{})
+};
+
+declare %rest:path('/app/tweets')
+function app:tweets() {
+ cm:htmx2("tweets.htm", map{})
+};
+
+
+
diff --git a/webapp/lsp-manager/common.xqm b/webapp/lsp-manager/common.xqm
new file mode 100644
index 0000000..0ea74d9
--- /dev/null
+++ b/webapp/lsp-manager/common.xqm
@@ -0,0 +1,88 @@
+module namespace cm = 'app/cm';
+import module namespace ore = 'urn:quodatum:template:oregano' at "lib.xq/oregano.xqm";
+declare variable $cm:opts:=map{"base": file:resolve-path("views/", file:base-dir()),
+ "layout": true(),
+ "indent": "no"
+ };
+declare variable $cm:model:=map{"_ctx": "app"};
+
+declare function cm:header($path as xs:string?,$data as xs:base64Binary?,$content-type as xs:string?)
+as element(rest:response)
+{ switch(true())
+case exists($path) return
+ web:response-header(
+ map { 'media-type': web:content-type($path) },
+
+ map {
+ 'Cache-Control': 'max-age=3600,public',
+ 'Content-Length': file:size($path),
+ 'ETag': file:last-modified($path)
+ }
+ )
+
+default return
+ web:response-header(
+ map { 'media-type':$content-type },
+ (map:entry('Cache-Control', 'max-age=3600,public'),
+ if(exists($data)) then map:entry('Content-Length', bin:length($data))
+ )=>map:merge()
+ )
+};
+
+(:~ http response from file content:)
+declare function cm:response-file($path as xs:string)
+as item()*{
+if(file:exists($path))
+ then (
+ cm:header($path,(),()),
+ file:read-binary($path)
+ )else error(xs:QName('cm:not-found'),$path)
+ ()
+};
+
+(:~ http response from data content:)
+declare function cm:response-data($data as xs:base64Binary,$content-type as xs:string)
+as item()*
+{
+ cm:header((),$data,$content-type),
+ $data
+
+};
+
+
+(:~ http response for htmx :)
+declare function cm:htmx2($template as xs:string,$model as map(*))
+as item()*
+{
+ cm:header((),(),"text/html"),
+ore:render($template,$cm:opts,$model)=>ore:serialize($cm:opts)
+};
+
+
+(:~ read a relative file :)
+declare function cm:read($path as xs:string)
+as xs:string{
+file:resolve-path( $path,file:base-dir())=>file:read-text()
+};
+
+(:~ write a message to standard BaseX log passthru item :)
+declare function cm:trace($items as item()*,$msg as xs:string)
+as item()*{
+ $items,admin:write-log($msg || ": " || serialize($items,map{"method":"basex"}),"MDUI")
+};
+
+declare function cm:status-404()
+as item()*{
+
+
+
+
+
+ ,
+ "The requested resource is not available."
+};
+
+declare function cm:status($status as xs:integer,$msg as xs:string)
+ as item()*{
+ web:response-header((),(), map { 'status': $status, 'message': "OK"}),$msg
+};
\ No newline at end of file
diff --git a/webapp/lsp-manager/error-reporting.xqm b/webapp/lsp-manager/error-reporting.xqm
new file mode 100644
index 0000000..32a2a02
--- /dev/null
+++ b/webapp/lsp-manager/error-reporting.xqm
@@ -0,0 +1,26 @@
+module namespace _ = 'app/error-reporting';
+import module namespace cm = "app/cm" at "common.xqm";
+
+
+declare
+ %rest:error("*")
+ %rest:error-param("code", "{$code}")
+ %rest:error-param("description", "{$description}")
+ %rest:error-param("value", "{$value}")
+ %rest:error-param("module", "{$module}")
+ %rest:error-param("line-number", "{$line-number}")
+ %rest:error-param("column-number","{$column-number}")
+ %rest:error-param("additional", "{$additional}")
+function _:error($code,$description,$value,
+ $module,$line-number,$column-number,$additional) {
+ let $err:=map{"code":$code, "description":$description,
+ "value": _:format($value), "module": $module,
+ "line-number": $line-number, "column-number": $column-number,
+ "additional": _:format($additional)}
+ return cm:htmx2("error.htm", $err)
+};
+
+declare function _:format($item)
+as xs:string{
+ serialize($item,map{"method":"basex"})
+};
\ No newline at end of file
diff --git a/webapp/lsp-manager/lib.xq/Thyme.xqm b/webapp/lsp-manager/lib.xq/Thyme.xqm
new file mode 100644
index 0000000..00e8a03
--- /dev/null
+++ b/webapp/lsp-manager/lib.xq/Thyme.xqm
@@ -0,0 +1,1279 @@
+xquery version "1.0" encoding "UTF-8";
+
+(: This file was generated on Fri Apr 18, 2025 11:03 (UTC+01) by REx v6.1 which is Copyright (c) 1979-2025 by Gunther Rademacher :)
+(: REx command line: thymeleaf.ebnf -xquery -name Thyme -tree :)
+
+(:~
+ : The parser that was generated for the Thyme grammar.
+ :)
+module namespace p="Thyme";
+declare default function namespace "http://www.w3.org/2005/xpath-functions";
+
+(:~
+ : The index of the lexer state for accessing the combined
+ : (i.e. level > 1) lookahead code.
+ :)
+declare variable $p:lk as xs:integer := 1;
+
+(:~
+ : The index of the lexer state for accessing the position in the
+ : input string of the begin of the token that has been consumed.
+ :)
+declare variable $p:b0 as xs:integer := 2;
+
+(:~
+ : The index of the lexer state for accessing the position in the
+ : input string of the end of the token that has been consumed.
+ :)
+declare variable $p:e0 as xs:integer := 3;
+
+(:~
+ : The index of the lexer state for accessing the code of the
+ : level-1-lookahead token.
+ :)
+declare variable $p:l1 as xs:integer := 4;
+
+(:~
+ : The index of the lexer state for accessing the position in the
+ : input string of the begin of the level-1-lookahead token.
+ :)
+declare variable $p:b1 as xs:integer := 5;
+
+(:~
+ : The index of the lexer state for accessing the position in the
+ : input string of the end of the level-1-lookahead token.
+ :)
+declare variable $p:e1 as xs:integer := 6;
+
+(:~
+ : The index of the lexer state for accessing the code of the
+ : level-2-lookahead token.
+ :)
+declare variable $p:l2 as xs:integer := 7;
+
+(:~
+ : The index of the lexer state for accessing the position in the
+ : input string of the begin of the level-2-lookahead token.
+ :)
+declare variable $p:b2 as xs:integer := 8;
+
+(:~
+ : The index of the lexer state for accessing the position in the
+ : input string of the end of the level-2-lookahead token.
+ :)
+declare variable $p:e2 as xs:integer := 9;
+
+(:~
+ : The index of the lexer state for accessing the code of the
+ : level-3-lookahead token.
+ :)
+declare variable $p:l3 as xs:integer := 10;
+
+(:~
+ : The index of the lexer state for accessing the position in the
+ : input string of the begin of the level-3-lookahead token.
+ :)
+declare variable $p:b3 as xs:integer := 11;
+
+(:~
+ : The index of the lexer state for accessing the position in the
+ : input string of the end of the level-3-lookahead token.
+ :)
+declare variable $p:e3 as xs:integer := 12;
+
+(:~
+ : The index of the lexer state for accessing the token code that
+ : was expected when an error was found.
+ :)
+declare variable $p:error as xs:integer := 13;
+
+(:~
+ : The index of the lexer state that points to the first entry
+ : used for collecting action results.
+ :)
+declare variable $p:result as xs:integer := 14;
+
+(:~
+ : The codepoint to charclass mapping for 7 bit codepoints.
+ :)
+declare variable $p:MAP0 as xs:integer+ :=
+(
+ 42, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 2, 2,
+ 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 2, 2, 16, 2, 2, 17, 18, 18, 18, 18, 19,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 2, 2, 2, 2, 20, 2, 21, 22, 23, 24,
+ 25, 18, 26, 18, 27, 28, 18, 29, 30, 31, 32, 33, 18, 34, 35, 36, 37, 18, 18, 18, 38, 18, 39, 2, 40, 41, 2
+);
+
+(:~
+ : The codepoint to charclass mapping for codepoints below the surrogate block.
+ :)
+declare variable $p:MAP1 as xs:integer+ :=
+(
+ 54, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+ 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 90, 122, 184, 216,
+ 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152,
+ 152, 152, 152, 152, 152, 152, 152, 152, 152, 42, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 2, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 14, 2, 2, 16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 17, 18,
+ 18, 18, 18, 19, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 2, 2, 2, 2, 20, 2,
+ 21, 22, 23, 24, 25, 18, 26, 18, 27, 28, 18, 29, 30, 31, 32, 33, 18, 34, 35, 36, 37, 18, 18, 18, 38, 18, 39, 2, 40, 41,
+ 2
+);
+
+(:~
+ : The codepoint to charclass mapping for codepoints above the surrogate block.
+ :)
+declare variable $p:MAP2 as xs:integer+ :=
+(
+ 57344, 65536, 65533, 1114111, 2, 2
+);
+
+(:~
+ : The token-set-id to DFA-initial-state mapping.
+ :)
+declare variable $p:INITIAL as xs:integer+ :=
+(
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
+);
+
+(:~
+ : The DFA transition table.
+ :)
+declare variable $p:TRANSITION as xs:integer+ :=
+(
+ 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 688, 688, 694, 717, 704, 717, 851,
+ 1112, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 717, 704, 717, 851, 1112, 717, 717, 717, 717, 717, 717,
+ 717, 717, 717, 718, 716, 717, 726, 717, 1115, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 717, 696, 738, 717, 704,
+ 717, 851, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 717, 739, 747, 717, 704, 717, 851, 1112, 717, 717, 717, 717,
+ 717, 717, 717, 717, 717, 748, 707, 717, 756, 717, 872, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 1148, 827, 1151,
+ 717, 704, 717, 851, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 1058, 768, 771, 717, 704, 717, 851, 1112, 717, 717,
+ 717, 717, 717, 717, 717, 717, 717, 708, 784, 717, 704, 717, 851, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 717,
+ 717, 717, 717, 704, 717, 851, 793, 717, 717, 717, 717, 717, 717, 717, 717, 717, 1035, 717, 717, 704, 717, 851, 1112,
+ 717, 717, 717, 717, 717, 717, 717, 717, 717, 886, 717, 887, 704, 717, 851, 793, 717, 717, 717, 717, 717, 717, 717,
+ 717, 904, 804, 811, 796, 820, 717, 851, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 717, 886, 717, 887, 704, 717,
+ 851, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 717, 835, 849, 1211, 843, 717, 760, 859, 1018, 717, 717, 717, 717,
+ 717, 717, 717, 986, 717, 717, 717, 704, 717, 851, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 717, 1152, 870, 717,
+ 704, 717, 851, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 1257, 880, 717, 1005, 704, 717, 760, 1112, 717, 717, 717,
+ 717, 717, 717, 717, 717, 1257, 880, 717, 1005, 895, 717, 760, 901, 717, 717, 717, 717, 717, 717, 717, 717, 1257, 1258,
+ 717, 948, 704, 717, 760, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 944, 880, 730, 912, 704, 717, 760, 1207, 717,
+ 958, 717, 717, 717, 717, 717, 717, 1001, 880, 717, 1092, 704, 717, 760, 1129, 717, 717, 717, 717, 717, 717, 717, 717,
+ 1054, 880, 717, 1005, 704, 717, 760, 1112, 717, 920, 717, 717, 717, 717, 717, 717, 1175, 880, 773, 1005, 704, 717,
+ 760, 1112, 717, 1238, 717, 717, 717, 717, 717, 717, 1257, 880, 717, 1185, 895, 717, 760, 930, 956, 717, 717, 717, 717,
+ 717, 717, 717, 1257, 880, 717, 1005, 704, 717, 760, 1112, 717, 775, 717, 717, 717, 717, 717, 717, 966, 880, 785, 1005,
+ 704, 717, 760, 1112, 984, 717, 717, 717, 717, 717, 717, 717, 1257, 880, 717, 1005, 704, 717, 994, 1112, 717, 717, 717,
+ 717, 717, 717, 717, 717, 1013, 880, 717, 1005, 704, 971, 760, 1143, 717, 717, 717, 717, 717, 717, 717, 717, 1030, 880,
+ 717, 1005, 704, 717, 1047, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 1066, 880, 717, 1005, 704, 717, 760, 1112,
+ 922, 1086, 717, 717, 717, 717, 717, 717, 1100, 880, 1180, 1005, 704, 1106, 760, 1112, 717, 717, 717, 717, 717, 717,
+ 717, 717, 1257, 880, 717, 1005, 704, 772, 760, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 1257, 880, 1072, 1005,
+ 704, 1123, 976, 1112, 717, 776, 717, 717, 717, 717, 717, 717, 1137, 880, 717, 1005, 704, 862, 760, 1112, 717, 717,
+ 717, 717, 717, 717, 717, 717, 1257, 880, 717, 1243, 704, 1039, 1078, 1112, 717, 717, 717, 717, 717, 717, 717, 717,
+ 1257, 880, 717, 1160, 704, 717, 760, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 1257, 880, 717, 1005, 704, 717,
+ 760, 1112, 774, 717, 717, 717, 717, 717, 717, 717, 717, 1168, 717, 717, 1193, 1201, 851, 1112, 717, 717, 717, 717,
+ 717, 717, 717, 717, 812, 937, 717, 717, 704, 717, 1022, 1112, 1232, 717, 717, 717, 717, 717, 717, 717, 717, 1219,
+ 1226, 887, 704, 717, 851, 1112, 717, 717, 717, 717, 717, 717, 717, 717, 1251, 717, 717, 717, 717, 717, 717, 717, 717,
+ 717, 717, 717, 717, 717, 717, 717, 274, 274, 274, 274, 274, 274, 274, 274, 0, 0, 0, 0, 0, 0, 32, 1954, 33, 0, 0, 36,
+ 0, 0, 0, 0, 0, 0, 0, 37, 33, 0, 0, 0, 0, 0, 0, 0, 0, 33, 1590, 0, 0, 36, 0, 0, 0, 0, 44, 45, 0, 0, 1954, 0, 0, 0, 0,
+ 0, 0, 0, 35, 35, 0, 0, 0, 0, 0, 0, 0, 36, 33, 0, 0, 1592, 0, 0, 0, 0, 53, 0, 67, 0, 0, 2304, 0, 2304, 0, 0, 0, 0, 0,
+ 0, 0, 46, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 47, 0, 68, 69, 0, 0, 0, 0, 0, 542, 0, 800, 542, 0, 0, 0, 2688, 2688,
+ 542, 38, 0, 0, 0, 0, 0, 0, 0, 3072, 33, 0, 0, 36, 0, 0, 1337, 0, 0, 2176, 0, 0, 2176, 2176, 2176, 542, 0, 0, 0, 0, 0,
+ 542, 1191, 33, 0, 0, 36, 0, 1337, 1191, 0, 0, 0, 0, 0, 0, 0, 67, 0, 1337, 1476, 69, 0, 0, 0, 0, 0, 1664, 51, 0, 40, 0,
+ 0, 0, 0, 0, 0, 0, 67, 36, 542, 1821, 0, 0, 0, 0, 542, 0, 0, 0, 0, 0, 542, 0, 0, 33, 0, 0, 36, 0, 0, 58, 0, 69, 0, 0,
+ 0, 0, 0, 2688, 0, 0, 48, 0, 0, 0, 1821, 542, 53, 800, 51, 0, 0, 0, 0, 0, 0, 0, 75, 0, 58, 0, 69, 0, 0, 71, 46, 0, 0,
+ 3072, 0, 3072, 3072, 3072, 0, 19, 1821, 0, 0, 0, 0, 0, 1821, 0, 53, 800, 73, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 23, 1821,
+ 0, 0, 0, 0, 0, 62, 0, 0, 0, 0, 66, 53, 0, 67, 0, 0, 74, 0, 0, 0, 0, 0, 0, 2816, 0, 0, 65, 0, 0, 53, 0, 67, 0, 20,
+ 1821, 0, 0, 0, 0, 0, 1821, 542, 53, 800, 0, 24, 1821, 0, 0, 0, 0, 0, 1476, 0, 0, 0, 0, 640, 0, 67, 0, 0, 25, 1821, 0,
+ 0, 0, 0, 0, 2560, 0, 0, 0, 0, 63, 0, 0, 0, 64, 0, 0, 0, 53, 0, 67, 0, 21, 1821, 0, 0, 0, 0, 0, 2304, 0, 0, 0, 0, 26,
+ 1821, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 46, 0, 53, 0, 67, 0, 0, 77, 0, 0, 0, 0, 0, 0, 50, 0, 1821, 542, 53, 800, 0,
+ 27, 1821, 0, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 33, 67, 0, 0, 60, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 72,
+ 0, 28, 1821, 0, 0, 0, 0, 0, 69, 0, 46, 0, 0, 0, 2176, 0, 0, 0, 0, 0, 0, 0, 40, 0, 49, 0, 0, 1821, 542, 53, 800, 31, 0,
+ 0, 0, 0, 0, 31, 0, 22, 1821, 0, 0, 0, 0, 0, 43, 0, 0, 0, 0, 51, 1821, 542, 53, 800, 33, 55, 2048, 36, 2432, 0, 0,
+ 2944, 59, 0, 0, 0, 0, 0, 0, 0, 69, 70, 0, 0, 0, 0, 1821, 542, 0, 800, 542, 0, 0, 0, 0, 0, 542, 41, 0, 0, 0, 0, 0, 0,
+ 0, 384, 0, 896, 0, 0, 0, 78, 0, 0, 0, 0, 0, 52, 1821, 542, 53, 800, 1024, 0, 0, 0, 0, 0, 0, 0, 1821, 0, 0, 0, 0, 0, 0
+);
+
+(:~
+ : The DFA-state to expected-token-set mapping.
+ :)
+declare variable $p:EXPECTED as xs:integer+ :=
+(
+ 20, 24, 28, 32, 36, 60, 60, 42, 53, 46, 59, 60, 60, 50, 57, 60, 38, 59, 60, 38, 130, 4098, 8194, 65538, 131074,
+ 1048578, 2097154, 8388610, 26, 139266, 8454146, 655362, 9437186, 9502722, 8454202, 4575046, 4706118, 2, 4096, 4096, 4,
+ 1024, 8192, 8, 16, 32, 262144, 1536, 1792, 4194304, 16, 2048, 4, 2048, 4, 32768, 2048, 1536, 1024, 64, 4096, 4096,
+ 4096, 4096
+);
+
+(:~
+ : The token-string table.
+ :)
+declare variable $p:TOKEN as xs:string+ :=
+(
+ "%ERROR",
+ "Whitespace",
+ "MessageExpression",
+ "UrlFrag",
+ "UrlVar",
+ "Hash",
+ "FragmentExpression",
+ "EOF",
+ "IntegerLiteral",
+ "DecimalLiteral",
+ "DoubleLiteral",
+ "StringLiteral",
+ "UtilityObject",
+ "Identifier",
+ "'#'",
+ "'${'",
+ "'('",
+ "')'",
+ "'*{'",
+ "','",
+ "'.'",
+ "'='",
+ "'@{'",
+ "'}'"
+);
+
+(:~
+ : Match next token in input string, starting at given index, using
+ : the DFA entry state for the set of tokens that are expected in
+ : the current context.
+ :
+ : @param $input the input string.
+ : @param $begin the index where to start in input string.
+ : @param $token-set the expected token set id.
+ : @return a sequence of three: the token code of the result token,
+ : with input string begin and end positions. If there is no valid
+ : token, return the negative id of the DFA state that failed, along
+ : with begin and end positions of the longest viable prefix.
+ :)
+declare function p:match($input as xs:string,
+ $begin as xs:integer,
+ $token-set as xs:integer) as xs:integer+
+{
+ let $result := $p:INITIAL[1 + $token-set]
+ return p:transition($input,
+ $begin,
+ $begin,
+ $begin,
+ $result,
+ $result mod 128,
+ 0)
+};
+
+(:~
+ : The DFA state transition function. If we are in a valid DFA state, save
+ : it's result annotation, consume one input codepoint, calculate the next
+ : state, and use tail recursion to do the same again. Otherwise, return
+ : any valid result or a negative DFA state id in case of an error.
+ :
+ : @param $input the input string.
+ : @param $begin the begin index of the current token in the input string.
+ : @param $current the index of the current position in the input string.
+ : @param $end the end index of the result in the input string.
+ : @param $result the result code.
+ : @param $current-state the current DFA state.
+ : @param $previous-state the previous DFA state.
+ : @return a sequence of three: the token code of the result token,
+ : with input string begin and end positions. If there is no valid
+ : token, return the negative id of the DFA state that failed, along
+ : with begin and end positions of the longest viable prefix.
+ :)
+declare function p:transition($input as xs:string,
+ $begin as xs:integer,
+ $current as xs:integer,
+ $end as xs:integer,
+ $result as xs:integer,
+ $current-state as xs:integer,
+ $previous-state as xs:integer)
+{
+ if ($current-state eq 0) then
+ let $result := $result idiv 128
+ let $end := if ($end gt string-length($input)) then string-length($input) + 1 else $end
+ return
+ if ($result ne 0) then
+ (
+ $result - 1,
+ $begin,
+ $end
+ )
+ else
+ (
+ - $previous-state,
+ $begin,
+ $current - 1
+ )
+ else
+ let $c0 := (string-to-codepoints(substring($input, $current, 1)), 0)[1]
+ let $c1 :=
+ if ($c0 < 128) then
+ $p:MAP0[1 + $c0]
+ else if ($c0 < 55296) then
+ let $c1 := $c0 idiv 32
+ let $c2 := $c1 idiv 32
+ return $p:MAP1[1 + $c0 mod 32 + $p:MAP1[1 + $c1 mod 32 + $p:MAP1[1 + $c2]]]
+ else
+ p:map2($c0, 1, 2)
+ let $current := $current + 1
+ let $i0 := 128 * $c1 + $current-state - 1
+ let $i1 := $i0 idiv 8
+ let $next-state := $p:TRANSITION[$i0 mod 8 + $p:TRANSITION[$i1 + 1] + 1]
+ return
+ if ($next-state > 127) then
+ p:transition($input, $begin, $current, $current, $next-state, $next-state mod 128, $current-state)
+ else
+ p:transition($input, $begin, $current, $end, $result, $next-state, $current-state)
+};
+
+(:~
+ : Recursively translate one 32-bit chunk of an expected token bitset
+ : to the corresponding sequence of token strings.
+ :
+ : @param $result the result of previous recursion levels.
+ : @param $chunk the 32-bit chunk of the expected token bitset.
+ : @param $base-token-code the token code of bit 0 in the current chunk.
+ : @return the set of token strings.
+ :)
+declare function p:token($result as xs:string*,
+ $chunk as xs:integer,
+ $base-token-code as xs:integer)
+{
+ if ($chunk = 0) then
+ $result
+ else
+ p:token
+ (
+ ($result, if ($chunk mod 2 != 0) then $p:TOKEN[$base-token-code] else ()),
+ if ($chunk < 0) then $chunk idiv 2 + 2147483648 else $chunk idiv 2,
+ $base-token-code + 1
+ )
+};
+
+(:~
+ : Calculate expected token set for a given DFA state as a sequence
+ : of strings.
+ :
+ : @param $state the DFA state.
+ : @return the set of token strings.
+ :)
+declare function p:expected-token-set($state as xs:integer) as xs:string*
+{
+ if ($state > 0) then
+ for $t in 0 to 0
+ let $i0 := $t * 78 + $state - 1
+ let $i1 := $i0 idiv 4
+ return p:token((), $p:EXPECTED[$i0 mod 4 + $p:EXPECTED[$i1 + 1] + 1], $t * 32 + 1)
+ else
+ ()
+};
+
+(:~
+ : Classify codepoint by doing a tail recursive binary search for a
+ : matching codepoint range entry in MAP2, the codepoint to charclass
+ : map for codepoints above the surrogate block.
+ :
+ : @param $c the codepoint.
+ : @param $lo the binary search lower bound map index.
+ : @param $hi the binary search upper bound map index.
+ : @return the character class.
+ :)
+declare function p:map2($c as xs:integer, $lo as xs:integer, $hi as xs:integer) as xs:integer
+{
+ if ($lo > $hi) then
+ 0
+ else
+ let $m := ($hi + $lo) idiv 2
+ return
+ if ($p:MAP2[$m] > $c) then
+ p:map2($c, $lo, $m - 1)
+ else if ($p:MAP2[2 + $m] < $c) then
+ p:map2($c, $m + 1, $hi)
+ else
+ $p:MAP2[4 + $m]
+};
+
+(:~
+ : Parse NumericLiteral.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-NumericLiteral($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] = 8) then (: IntegerLiteral :)
+ let $state := p:consume(8, $input, $state) (: IntegerLiteral :)
+ return $state
+ else if ($state[$p:l1] = 9) then (: DecimalLiteral :)
+ let $state := p:consume(9, $input, $state) (: DecimalLiteral :)
+ return $state
+ else
+ let $state := p:consume(10, $input, $state) (: DoubleLiteral :)
+ return $state
+ let $end := $state[$p:e0]
+ return p:reduce($state, "NumericLiteral", $count, $begin, $end)
+};
+
+(:~
+ : Parse Literal.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-Literal($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] = 11) then (: StringLiteral :)
+ let $state := p:consume(11, $input, $state) (: StringLiteral :)
+ return $state
+ else
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-NumericLiteral($input, $state)
+ return $state
+ let $end := $state[$p:e0]
+ return p:reduce($state, "Literal", $count, $begin, $end)
+};
+
+(:~
+ : Parse UtilityExpression.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-UtilityExpression($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(14, $input, $state) (: '#' :)
+ let $state := p:lookahead1W(1, $input, $state) (: Whitespace | UtilityObject :)
+ let $state := p:consume(12, $input, $state) (: UtilityObject :)
+ let $state := p:lookahead1W(5, $input, $state) (: Whitespace | '.' :)
+ let $state := p:consume(20, $input, $state) (: '.' :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "UtilityExpression", $count, $begin, $end)
+};
+
+(:~
+ : Parse UrlParameter.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-UrlParameter($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(13, $input, $state) (: Identifier :)
+ let $state := p:lookahead1W(6, $input, $state) (: Whitespace | '=' :)
+ let $state := p:consume(21, $input, $state) (: '=' :)
+ let $state := p:lookahead1W(15, $input, $state) (: Whitespace | MessageExpression | FragmentExpression |
+ IntegerLiteral | DecimalLiteral | DoubleLiteral |
+ StringLiteral | '#' | '${' | '(' | '*{' | '@{' :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Expression($input, $state)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "UrlParameter", $count, $begin, $end)
+};
+
+(:~
+ : Parse the 1st loop of production UrlParameters (zero or more). Use
+ : tail recursion for iteratively updating the lexer state.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-UrlParameters-1($input as xs:string, $state as item()+)
+{
+ if ($state[$p:error]) then
+ $state
+ else
+ let $state := p:lookahead1W(11, $input, $state) (: Whitespace | ')' | ',' :)
+ return
+ if ($state[$p:l1] != 19) then (: ',' :)
+ $state
+ else
+ let $state := p:consume(19, $input, $state) (: ',' :)
+ let $state := p:lookahead1W(2, $input, $state) (: Whitespace | Identifier :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-UrlParameter($input, $state)
+ return p:parse-UrlParameters-1($input, $state)
+};
+
+(:~
+ : Parse UrlParameters.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-UrlParameters($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(16, $input, $state) (: '(' :)
+ let $state := p:lookahead1W(9, $input, $state) (: Whitespace | Identifier | ')' :)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] = 13) then (: Identifier :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-UrlParameter($input, $state)
+ let $state := p:parse-UrlParameters-1($input, $state)
+ return $state
+ else
+ $state
+ let $state := p:consume(17, $input, $state) (: ')' :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "UrlParameters", $count, $begin, $end)
+};
+
+(:~
+ : Parse the 1st loop of production LinkExpression (one or more). Use
+ : tail recursion for iteratively updating the lexer state.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-LinkExpression-1($input as xs:string, $state as item()+)
+{
+ if ($state[$p:error]) then
+ $state
+ else
+ let $state := p:lookahead1W(8, $input, $state) (: Whitespace | UrlFrag | UrlVar :)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] = 3) then (: UrlFrag :)
+ let $state := p:consume(3, $input, $state) (: UrlFrag :)
+ return $state
+ else
+ let $state := p:consume(4, $input, $state) (: UrlVar :)
+ return $state
+ let $state := p:lookahead1W(14, $input, $state) (: Whitespace | UrlFrag | UrlVar | Hash | '(' | '}' :)
+ return
+ if ($state[$p:l1] != 3 (: UrlFrag :)
+ and $state[$p:l1] != 4) then (: UrlVar :)
+ $state
+ else
+ p:parse-LinkExpression-1($input, $state)
+};
+
+(:~
+ : Parse LinkExpression.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-LinkExpression($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(22, $input, $state) (: '@{' :)
+ let $state := p:parse-LinkExpression-1($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] = 5) then (: Hash :)
+ let $state := p:consume(5, $input, $state) (: Hash :)
+ return $state
+ else
+ $state
+ let $state := p:lookahead1W(10, $input, $state) (: Whitespace | '(' | '}' :)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] = 16) then (: '(' :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-UrlParameters($input, $state)
+ return $state
+ else
+ $state
+ let $state := p:lookahead1W(7, $input, $state) (: Whitespace | '}' :)
+ let $state := p:consume(23, $input, $state) (: '}' :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "LinkExpression", $count, $begin, $end)
+};
+
+(:~
+ : Parse SelectionExpression.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-SelectionExpression($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(18, $input, $state) (: '*{' :)
+ let $state := p:lookahead1W(2, $input, $state) (: Whitespace | Identifier :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-ObjectAccess($input, $state)
+ let $state := p:consume(23, $input, $state) (: '}' :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "SelectionExpression", $count, $begin, $end)
+};
+
+(:~
+ : Parse Argument.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-Argument($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Expression($input, $state)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "Argument", $count, $begin, $end)
+};
+
+(:~
+ : Parse MethodName.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-MethodName($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(13, $input, $state) (: Identifier :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "MethodName", $count, $begin, $end)
+};
+
+(:~
+ : Parse the 1st loop of production MethodInvocation (zero or more). Use
+ : tail recursion for iteratively updating the lexer state.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-MethodInvocation-1($input as xs:string, $state as item()+)
+{
+ if ($state[$p:error]) then
+ $state
+ else
+ let $state := p:lookahead1W(11, $input, $state) (: Whitespace | ')' | ',' :)
+ return
+ if ($state[$p:l1] != 19) then (: ',' :)
+ $state
+ else
+ let $state := p:consume(19, $input, $state) (: ',' :)
+ let $state := p:lookahead1W(15, $input, $state) (: Whitespace | MessageExpression | FragmentExpression |
+ IntegerLiteral | DecimalLiteral | DoubleLiteral |
+ StringLiteral | '#' | '${' | '(' | '*{' | '@{' :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Argument($input, $state)
+ return p:parse-MethodInvocation-1($input, $state)
+};
+
+(:~
+ : Parse MethodInvocation.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-MethodInvocation($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(20, $input, $state) (: '.' :)
+ let $state := p:lookahead1W(2, $input, $state) (: Whitespace | Identifier :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-MethodName($input, $state)
+ let $state := p:lookahead1W(3, $input, $state) (: Whitespace | '(' :)
+ let $state := p:consume(16, $input, $state) (: '(' :)
+ let $state := p:lookahead1W(16, $input, $state) (: Whitespace | MessageExpression | FragmentExpression |
+ IntegerLiteral | DecimalLiteral | DoubleLiteral |
+ StringLiteral | '#' | '${' | '(' | ')' | '*{' | '@{' :)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] != 17) then (: ')' :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Argument($input, $state)
+ let $state := p:parse-MethodInvocation-1($input, $state)
+ return $state
+ else
+ $state
+ let $state := p:consume(17, $input, $state) (: ')' :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "MethodInvocation", $count, $begin, $end)
+};
+
+(:~
+ : Parse Property.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-Property($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(13, $input, $state) (: Identifier :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "Property", $count, $begin, $end)
+};
+
+(:~
+ : Parse Variable.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-Variable($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(13, $input, $state) (: Identifier :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "Variable", $count, $begin, $end)
+};
+
+(:~
+ : Parse the 1st loop of production ObjectAccess (zero or more). Use
+ : tail recursion for iteratively updating the lexer state.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-ObjectAccess-1($input as xs:string, $state as item()+)
+{
+ if ($state[$p:error]) then
+ $state
+ else
+ let $state := p:lookahead1W(12, $input, $state) (: Whitespace | '.' | '}' :)
+ let $state :=
+ if ($state[$p:l1] eq 20) then (: '.' :)
+ let $state := p:lookahead2W(2, $input, $state) (: Whitespace | Identifier :)
+ let $state :=
+ if ($state[$p:lk] eq 436) then (: '.' Identifier :)
+ let $state := p:lookahead3W(13, $input, $state) (: Whitespace | '(' | '.' | '}' :)
+ return $state
+ else
+ $state
+ return $state
+ else
+ ($state[$p:l1], subsequence($state, $p:lk + 1))
+ return
+ if ($state[$p:lk] != 20916 (: '.' Identifier '.' :)
+ and $state[$p:lk] != 23988) then (: '.' Identifier '}' :)
+ $state
+ else
+ let $state := p:consume(20, $input, $state) (: '.' :)
+ let $state := p:lookahead1W(2, $input, $state) (: Whitespace | Identifier :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Property($input, $state)
+ return p:parse-ObjectAccess-1($input, $state)
+};
+
+(:~
+ : Parse the 2nd loop of production ObjectAccess (zero or more). Use
+ : tail recursion for iteratively updating the lexer state.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-ObjectAccess-2($input as xs:string, $state as item()+)
+{
+ if ($state[$p:error]) then
+ $state
+ else
+ let $state := p:lookahead1W(12, $input, $state) (: Whitespace | '.' | '}' :)
+ return
+ if ($state[$p:l1] != 20) then (: '.' :)
+ $state
+ else
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-MethodInvocation($input, $state)
+ return p:parse-ObjectAccess-2($input, $state)
+};
+
+(:~
+ : Parse ObjectAccess.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-ObjectAccess($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Variable($input, $state)
+ let $state := p:parse-ObjectAccess-1($input, $state)
+ let $state := p:parse-ObjectAccess-2($input, $state)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "ObjectAccess", $count, $begin, $end)
+};
+
+(:~
+ : Parse VariableExpression.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-VariableExpression($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:consume(15, $input, $state) (: '${' :)
+ let $state := p:lookahead1W(2, $input, $state) (: Whitespace | Identifier :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-ObjectAccess($input, $state)
+ let $state := p:consume(23, $input, $state) (: '}' :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "VariableExpression", $count, $begin, $end)
+};
+
+(:~
+ : Parse Expression.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-Expression($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] = 15) then (: '${' :)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-VariableExpression($input, $state)
+ return $state
+ else if ($state[$p:l1] = 18) then (: '*{' :)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-SelectionExpression($input, $state)
+ return $state
+ else if ($state[$p:l1] = 2) then (: MessageExpression :)
+ let $state := p:consume(2, $input, $state) (: MessageExpression :)
+ return $state
+ else if ($state[$p:l1] = 22) then (: '@{' :)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-LinkExpression($input, $state)
+ return $state
+ else if ($state[$p:l1] = 6) then (: FragmentExpression :)
+ let $state := p:consume(6, $input, $state) (: FragmentExpression :)
+ return $state
+ else if ($state[$p:l1] = 14) then (: '#' :)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-UtilityExpression($input, $state)
+ return $state
+ else if ($state[$p:l1] = 16) then (: '(' :)
+ let $state := p:consume(16, $input, $state) (: '(' :)
+ let $state := p:lookahead1W(15, $input, $state) (: Whitespace | MessageExpression | FragmentExpression |
+ IntegerLiteral | DecimalLiteral | DoubleLiteral |
+ StringLiteral | '#' | '${' | '(' | '*{' | '@{' :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Expression($input, $state)
+ let $state := p:lookahead1W(4, $input, $state) (: Whitespace | ')' :)
+ let $state := p:consume(17, $input, $state) (: ')' :)
+ return $state
+ else
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Literal($input, $state)
+ return $state
+ let $end := $state[$p:e0]
+ return p:reduce($state, "Expression", $count, $begin, $end)
+};
+
+(:~
+ : Parse ThymeLeaf.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:parse-ThymeLeaf($input as xs:string, $state as item()+) as item()+
+{
+ let $count := count($state)
+ let $begin := $state[$p:e0]
+ let $state := p:lookahead1W(15, $input, $state) (: Whitespace | MessageExpression | FragmentExpression |
+ IntegerLiteral | DecimalLiteral | DoubleLiteral |
+ StringLiteral | '#' | '${' | '(' | '*{' | '@{' :)
+ let $state := p:whitespace($input, $state)
+ let $state :=
+ if ($state[$p:error]) then
+ $state
+ else
+ p:parse-Expression($input, $state)
+ let $state := p:lookahead1W(0, $input, $state) (: Whitespace | EOF :)
+ let $state := p:consume(7, $input, $state) (: EOF :)
+ let $end := $state[$p:e0]
+ return p:reduce($state, "ThymeLeaf", $count, $begin, $end)
+};
+
+(:~
+ : Create a textual error message from a parsing error.
+ :
+ : @param $input the input string.
+ : @param $error the parsing error descriptor.
+ : @return the error message.
+ :)
+declare function p:error-message($input as xs:string, $error as element(error)) as xs:string
+{
+ let $begin := xs:integer($error/@b)
+ let $context := string-to-codepoints(substring($input, 1, $begin - 1))
+ let $linefeeds := index-of($context, 10)
+ let $line := count($linefeeds) + 1
+ let $column := ($begin - $linefeeds[last()], $begin)[1]
+ return
+ string-join
+ (
+ (
+ if ($error/@o) then
+ ("syntax error, found ", $p:TOKEN[$error/@o + 1])
+ else
+ "lexical analysis failed",
+ "
",
+ "while expecting ",
+ if ($error/@x) then
+ $p:TOKEN[$error/@x + 1]
+ else
+ let $expected := p:expected-token-set($error/@s)
+ return
+ (
+ "["[exists($expected[2])],
+ string-join($expected, ", "),
+ "]"[exists($expected[2])]
+ ),
+ "
",
+ if ($error/@o or $error/@e = $begin) then
+ ()
+ else
+ ("after successfully scanning ", string($error/@e - $begin), " characters beginning "),
+ "at line ", string($line), ", column ", string($column), ":
",
+ "...", substring($input, $begin, 64), "..."
+ ),
+ ""
+ )
+};
+
+(:~
+ : Consume one token, i.e. compare lookahead token 1 with expected
+ : token and in case of a match, shift lookahead tokens down such that
+ : l1 becomes the current token, and higher lookahead tokens move down.
+ : When lookahead token 1 does not match the expected token, raise an
+ : error by saving the expected token code in the error field of the
+ : lexer state.
+ :
+ : @param $code the expected token.
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:consume($code as xs:integer, $input as xs:string, $state as item()+) as item()+
+{
+ if ($state[$p:error]) then
+ $state
+ else if ($state[$p:l1] eq $code) then
+ (
+ subsequence($state, $p:l1, 9),
+ 0, 0, 0,
+ subsequence($state, 13),
+ let $begin := $state[$p:e0]
+ let $end := $state[$p:b1]
+ where $begin ne $end
+ return
+ text
+ {
+ substring($input, $begin, $end - $begin)
+ },
+ let $token := $p:TOKEN[1 + $state[$p:l1]]
+ let $name := if (starts-with($token, "'")) then "TOKEN" else $token
+ let $begin := $state[$p:b1]
+ let $end := $state[$p:e1]
+ return
+ element {$name}
+ {
+ substring($input, $begin, $end - $begin)
+ }
+ )
+ else
+ (
+ subsequence($state, 1, $p:error - 1),
+ element error
+ {
+ attribute b {$state[$p:b1]},
+ attribute e {$state[$p:e1]},
+ if ($state[$p:l1] lt 0) then
+ attribute s {- $state[$p:l1]}
+ else
+ (attribute o {$state[$p:l1]}, attribute x {$code})
+ },
+ subsequence($state, $p:error + 1)
+ )
+};
+
+(:~
+ : Consume whitespace.
+ :
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:whitespace($input as xs:string,
+ $state as item()+) as item()+
+{
+ if ($state[$p:e0] = $state[$p:b1]) then
+ $state
+ else
+ let $begin := $state[$p:e0]
+ let $end := $state[$p:b1]
+ return
+ (
+ 0,
+ $state[$p:b0],
+ $end,
+ subsequence($state, $p:e0 + 1),
+ text
+ {
+ substring($input, $begin, $end - $begin)
+ }
+ )
+};
+
+(:~
+ : Use p:match to fetch the next token, but skip any leading
+ : whitespace.
+ :
+ : @param $input the input string.
+ : @param $begin the index where to start.
+ : @param $token-set the valid token set id.
+ : @return a sequence of three values: the token code of the result
+ : token, with input string positions of token begin and end.
+ :)
+declare function p:matchW($input as xs:string,
+ $begin as xs:integer,
+ $token-set as xs:integer)
+{
+ let $match := p:match($input, $begin, $token-set)
+ return
+ if ($match[1] = 1) then (: Whitespace :)
+ p:matchW($input, $match[3], $token-set)
+ else
+ $match
+};
+
+(:~
+ : Lookahead one token on level 1 with whitespace skipping.
+ :
+ : @param $set the code of the DFA entry state for the set of valid tokens.
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:lookahead1W($set as xs:integer, $input as xs:string, $state as item()+) as item()+
+{
+ if ($state[$p:l1] ne 0) then
+ $state
+ else
+ let $match :=
+ (
+ p:matchW($input, $state[$p:e0], $set),
+ 0, 0, 0
+ )
+ return
+ (
+ $match[1],
+ subsequence($state, $p:b0, 2),
+ $match,
+ subsequence($state, 10)
+ )
+};
+
+(:~
+ : Lookahead one token on level 2 with whitespace skipping.
+ :
+ : @param $set the code of the DFA entry state for the set of valid tokens.
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:lookahead2W($set as xs:integer, $input as xs:string, $state as item()+) as item()+
+{
+ let $match :=
+ if ($state[$p:l2] ne 0) then
+ subsequence($state, $p:l2, 6)
+ else
+ (
+ p:matchW($input, $state[$p:e1], $set),
+ 0, 0, 0
+ )
+ return
+ (
+ $match[1] * 32 + $state[$p:l1],
+ subsequence($state, $p:b0, 5),
+ $match,
+ subsequence($state, 13)
+ )
+};
+
+(:~
+ : Lookahead one token on level 3 with whitespace skipping.
+ :
+ : @param $set the code of the DFA entry state for the set of valid tokens.
+ : @param $input the input string.
+ : @param $state lexer state, error indicator, and result stack.
+ : @return the updated state.
+ :)
+declare function p:lookahead3W($set as xs:integer, $input as xs:string, $state as item()+) as item()+
+{
+ let $match :=
+ if ($state[$p:l3] ne 0) then
+ subsequence($state, $p:l3, 3)
+ else
+ p:matchW($input, $state[$p:e2], $set)
+ return
+ (
+ $match[1] * 1024 + $state[$p:lk],
+ subsequence($state, $p:b0, 8),
+ $match,
+ subsequence($state, 13)
+ )
+};
+
+(:~
+ : Reduce the result stack, creating a nonterminal element. Pop
+ : $count elements off the stack, wrap them in a new element
+ : named $name, and push the new element.
+ :
+ : @param $state lexer state, error indicator, and result stack.
+ : @param $name the name of the result node.
+ : @param $count the number of child nodes.
+ : @param $begin the input index where the nonterminal begins.
+ : @param $end the input index where the nonterminal ends.
+ : @return the updated state.
+ :)
+declare function p:reduce($state as item()+, $name as xs:string, $count as xs:integer, $begin as xs:integer, $end as xs:integer) as item()+
+{
+ subsequence($state, 1, $count),
+ element {$name}
+ {
+ subsequence($state, $count + 1)
+ }
+};
+
+(:~
+ : Parse start symbol ThymeLeaf from given string.
+ :
+ : @param $s the string to be parsed.
+ : @return the result as generated by parser actions.
+ :)
+declare function p:parse-ThymeLeaf($s as xs:string) as item()*
+{
+ let $state := (0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, false())
+ let $state := p:parse-ThymeLeaf($s, $state)
+ let $error := $state[$p:error]
+ return
+ if ($error) then
+ element ERROR {$error/@*, p:error-message($s, $error)}
+ else
+ subsequence($state, $p:result)
+};
+
+(: End :)
diff --git a/webapp/lsp-manager/lib.xq/oregano.xqm b/webapp/lsp-manager/lib.xq/oregano.xqm
new file mode 100644
index 0000000..4fb79e0
--- /dev/null
+++ b/webapp/lsp-manager/lib.xq/oregano.xqm
@@ -0,0 +1,235 @@
+xquery version '4.0';
+(: oregano: A thymeleaf style templating library - INCOMPLETE
+Aims to be a compatable implementation of a subset https://www.thymeleaf.org/
+for use as a HTML5 templating engine with BaseX 10+
+supports: th:text, layout:decorate, layout:fragment
+@author Andy Bunce
+@status INCOMPLETE alpha
+@licence Apache 2
+:)
+module namespace ore = 'urn:quodatum:template:oregano';
+import module namespace p = 'Thyme' at "Thyme.xqm";
+
+declare namespace th="http://www.thymeleaf.org";
+declare namespace layout="http://www.ultraq.net.nz/thymeleaf/layout";
+
+declare variable $ore:default-options:=map{"base": file:base-dir(),
+ "layout": true(),
+ "indent": "no"
+ };
+
+(:~ updated html doc :)
+declare function ore:render($view as xs:string,$opts as map(*),$model as map(*))
+as document-node(){
+ let $opts:=map:merge(($opts,$ore:default-options))
+ let $doc:=(ore:doc($view,$opts)
+ ,message($model,"£ RENDER MODEL(" || $view ||"): "))
+ let $a:= ore:update($doc,$opts,$model)
+ return ore:decorate($a,$opts)
+};
+
+(:~ update doc by evaluating th:each and th:text :)
+declare function ore:update($doc ,$opts as map(*),$model as map(*))
+{
+ $doc update { (
+ for $each in outermost(*//*[@th:each])
+ let $p:=ore:parse-each($each/@th:each)
+ return (
+ replace node $each with (
+ for $item in map:get($model,substring-after($p,","))
+ let $model2:=map:put($model,substring-before($p,","),$item)
+ (:=>trace("MODEL2:"):)
+ let $x:=ore:update($each,$opts,$model2)
+ return $x (:=>trace("DD: "):)
+ ))
+ ,delete node @th:each
+ )
+ }update{
+ for $text in .//*[@th:text]
+ where not($text/ancestor::*/@th:each)
+ return (
+ replace value of node $text with ore:expression($text/@th:text,$model)
+ ,delete node $text/@th:text
+ )
+ , ore:update-attrib(., "value", $model)=>prof:time("£ ore:update-attrib value: ")
+ , ore:update-attrib(., "href", $model)=>prof:time("£ ore:update-attrib href: ")
+ , ore:update-attrib(., "src", $model)=>prof:time("£ ore:update-attrib src: ")
+ }
+};
+
+(:~ update-attrib: use value of expression @th:name to update @name :)
+declare %updating function ore:update-attrib($context as node(),$name as xs:string,$model as map(*))
+as empty-sequence(){
+for $at in $context//*/@th:*[local-name(.) eq $name]
+ where not($at/ancestor::*/@th:each)
+ let $target:=$at/../@*[name() eq $name]=>trace("£ target: ")
+ return (
+ replace value of node $target with
+ ore:expression($at, $model)=>trace("£ ore:expression: ")
+ ,delete node $at
+ )
+};
+
+(: load doc from base folder:)
+declare function ore:doc($file as xs:string,$opts as map(*))
+as document-node(){
+ let $f:=file:resolve-path($file,$opts?base)=>trace("ore:doc")
+ return doc($f)
+};
+
+(:~ wrap with template named in root attribute @layout:decorate :)
+declare function ore:decorate($doc as document-node(),$opts as map(*))
+as document-node(){
+ if(not($opts?layout and $doc/*/@layout:decorate))
+ then $doc
+ else
+ let $wrap:=$doc/*/@layout:decorate=>replace(".+\{(.+)\}","$1")
+ let $wdoc:=ore:doc($wrap,$opts)
+ return $wdoc update{
+
+ replace node html/head/title
+ with $doc/html/head/title,
+
+ insert node $doc/html/head/*[name() ne 'title']
+ as last into html/head,
+
+ (: update matching fragments :)
+ for $frag in //*/@layout:fragment
+ where $doc//*/@layout:fragment=$frag
+ return replace node //*[@layout:fragment=$frag]
+ with ($doc//*[@layout:fragment=$frag] update {delete node @layout:fragment})
+
+ }
+};
+
+(:~ serialize as HTML5 :)
+declare function ore:serialize($doc as document-node(),$opts as map(*))
+as xs:string{
+ serialize($doc ,
+ map { 'method': 'html', 'version': '5.0'}
+ )
+};
+
+(:~ Return value of $atrib from model :)
+declare function ore:expression($atrb as xs:string,$model as map(*))
+as xs:string{
+ let $p:=ore:parse-text($atrb)=>trace("ore:expression: ")
+ let $exp:=substring-after($p,",")
+ return switch(substring-before($p,","))
+
+ case "$" case "#" return
+ (: @TODO assumes simple dotted :)
+ ore:expression-value($exp,$model)
+
+ case "@" return
+ let $x:=ore:expression-link($exp)=>ore:parse-params()
+ return ore:template1($x,$model)
+
+ case "*" return
+ error(xs:QName('ore:expression'),"*" || $exp)
+
+ default return
+ error(xs:QName('ore:expression'),"bad exp start: " || $atrb)
+};
+
+(:~ evaluate link $exp is body from "@{ body }" :)
+declare function ore:expression-link($exp as xs:string)
+as xs:string {
+ switch (true())
+ case starts-with($exp=>trace("EEEE: "), "http")
+ case starts-with($exp, "//")
+ return $exp
+ case starts-with($exp, "/")
+ return concat("/{_ctx}",$exp) (: prepend app:)
+ case starts-with($exp, "~/")
+ return substring($exp, 2)
+
+ default
+ return $exp
+};
+
+(:~ $exp is name or dotted name :)
+declare function ore:expression-value($exp as xs:string, $model as map (*))
+as xs:string {
+ (: @TODO assumes simple dotted :)
+ try {
+ normalize-space($exp)=> tokenize("\.")=>fold-left( $model, map:get#2)=>string()
+ } catch *{
+ error(xs:QName('expression-value'),"bad: " || $exp)
+ }
+};
+
+(: extract param map if present in link
+@result 1st item string updated link , optional 2nd item is map of params
+ :)
+declare function ore:parse-params($link as xs:string)
+as item()*
+{
+ let $rex:="[^(]+\((.+)\)$"
+ let $p:=replace($link,$rex,"$1")
+ return if($p ne $link)
+ then (: params present :)
+ let $params:=(for $i in tokenize($p,",")
+ let $v:=substring-after($i,"=")=>replace("['""](.*)['""]","$1") (: remove quotes :)
+ return map:entry(substring-before($i,"="),$v)
+ )=>map:merge()
+
+ let $ps:=map:keys($params)!concat(.,"=",$params(.))=>string-join("&")
+ let $u:=substring-before($link,"(")
+ let $anchor:=if(contains($u,"#"))
+ then "#" || substring-after($u,"#")
+
+ return (concat(substring-before($u || "#","#"),"?",$ps,$anchor),$params)
+
+ else $link
+};
+
+(:~ return "name,collection" from @th:each="name: ${collection}" or error :)
+declare function ore:parse-each($atrb as xs:string)
+as xs:string{
+ let $rex:="^(\w+):\s*\$\{\s*(\w+)\s*\}\s*$"
+ return if(matches($atrb,$rex))
+ then replace($atrb,$rex,"$1,$2")
+ else error(xs:QName('ore:parse-each'),"bad th:each: " || $atrb)
+};
+
+(:~ return "type,expression" from @th:text or error
+ Variable Expressions: ${...}
+ Selection Variable Expressions: *{...}
+ Message Expressions: #{...}
+ Link URL Expressions: @{...}
+:)
+declare function ore:parse-text($atrb as xs:string)
+as xs:string{
+ let $rex:="^([$*#@])\{\s*([^\s]+)\s*\}$"
+ return if(matches($atrb,$rex))
+ then replace($atrb,$rex,"$1,$2")
+ else error(xs:QName('ore:parse-text'),"bad th:text: " || $atrb)
+};
+
+
+
+(:~ REx parse :)
+declare function ore:parse($exp as xs:string)
+as element(*)
+{
+let $x:=p:parse-ThymeLeaf($exp)
+return if($x/self::ERROR)
+ then error(xs:QName('ore:parse'),"bad: " || $exp)
+ else $x
+};
+
+
+(:~ subst {key} strings in $tmp from model dotted values :)
+declare function ore:template1($tmp as xs:string, $model as map(*))
+as xs:string{
+ (: @todo xquery 4 replace :)
+ let $s:=analyze-string($tmp, "\{([^}]+)\}")
+ update{
+ for $m in fn:match
+
+ let $g:= normalize-space($m/fn:group)=> tokenize("\.")=>fold-left( $model, map:get#2)=>string()
+ return replace value of node $m with $g
+ }
+ return string($s)
+};
diff --git a/webapp/lsp-manager/lib.xq/outlet.xqm b/webapp/lsp-manager/lib.xq/outlet.xqm
new file mode 100644
index 0000000..193a33a
--- /dev/null
+++ b/webapp/lsp-manager/lib.xq/outlet.xqm
@@ -0,0 +1,102 @@
+xquery version '3.1';
+(:~ save conversion outputs to file
+ensures target folders created
+save multiple docs when root found
+optionally serialize XML with default namespace
+@author quodatum Andy Bunce
+:)
+module namespace outlet = 'urn:conversion:outlet';
+
+(: serialization options
+ key is arbitary name, value is BaseX serialization option map
+for xml serializations the non-standard 'ns' option supplies a namespace to use as default ' :)
+declare variable $outlet:serial:=map{
+ "xvrl": map{"method":"xml","ns":"http://www.xproc.org/ns/xvrl"},
+ "docbook":map{"method":"xml","ns":"http://docbook.org/ns/docbook"},
+ "csv": map{"method":"text"},
+ "xml": map{"method":"xml"}
+};
+
+(:~save $doc to $dest unless empty,will create directories as required
+@param $opts serialization options and ns to set a default namespace :)
+declare function outlet:save($doc as item(),$dest as xs:string?,$opts as map(*)?)
+as xs:string?{
+ if (exists($dest))
+ then
+ let $doc:=if($opts?ns)
+ then outlet:change-element-ns-deep($doc,$opts?ns,"")
+ else $doc
+ return (
+ file:create-dir(file:parent($dest)),
+ file:write($dest,$doc,map:remove($opts,"ns")),
+ $dest
+ )
+};
+
+(:~ save file or files if node is wrapper
+@return paths to saved files
+:)
+declare function outlet:save-wrapper($doc as item(),$dest as xs:string?,$opts as map(*)?)
+as xs:string*{
+ if ($doc/wrapper)
+ then $doc/wrapper/result! outlet:save(*,file:resolve-path(@href,$dest),$opts)
+ else outlet:save($doc,$dest,$opts)
+};
+
+(:~ from functx http://www.xqueryfunctions.com/xq/functx_change-element-ns-deep.html :)
+declare function outlet:change-element-ns-deep
+ ( $nodes as node()* ,
+ $newns as xs:string ,
+ $prefix as xs:string ) as node()* {
+
+ for $node in $nodes
+ return if ($node instance of element())
+ then (element
+ {QName ($newns,
+ concat($prefix,
+ if ($prefix = '')
+ then ''
+ else ':',
+ local-name($node)))}
+ {$node/@*,
+ outlet:change-element-ns-deep($node/node(),
+ $newns, $prefix)})
+ else if ($node instance of document-node())
+ then outlet:change-element-ns-deep($node/node(),
+ $newns, $prefix)
+ else $node
+ } ;
+
+(:~ @see https://stackoverflow.com/a/8600987/3210344 :)
+ declare function outlet:remove-prefixes($node as node(), $prefixes as xs:string*)
+ as node(){
+ typeswitch ($node)
+ case element()
+ return
+ if ($prefixes = ('#all', prefix-from-QName(node-name($node)))) then
+ element {QName(namespace-uri($node), local-name($node))} {
+ $node/@*,
+ $node/node()/outlet:remove-prefixes(., $prefixes)
+ }
+ else
+ element {node-name($node)} {
+ $node/@*,
+ $node/node()/outlet:remove-prefixes(., $prefixes)
+ }
+ case document-node()
+ return
+ document {
+ $node/node()/outlet:remove-prefixes(., $prefixes)
+ }
+ default
+ return $node
+};
+
+(:~ relative file paths below folder $path, matching $selector :)
+declare function outlet:select($path as xs:string,$selector as map(xs:string,item()*))
+as xs:string*
+{
+file:list($path,true(),$selector?pattern)
+[some $i in $selector?include satisfies contains(.,$i)]
+[every $x in $selector?exclude satisfies not(contains(.,$x))]
+};
\ No newline at end of file
diff --git a/webapp/lsp-manager/lib.xq/tree.xqm b/webapp/lsp-manager/lib.xq/tree.xqm
new file mode 100644
index 0000000..ffe8417
--- /dev/null
+++ b/webapp/lsp-manager/lib.xq/tree.xqm
@@ -0,0 +1,59 @@
+xquery version "3.1";
+(:~
+ : represant sequence of path strings as sequence of xml trees
+ :)
+module namespace tree = 'quodatum.data.tree';
+
+
+(:~
+ : convert path(s) to tree
+ :)
+declare function tree:build($paths as xs:string*,$del as xs:string)
+{
+fn:fold-right($paths,
+ (),
+ function($this,$acc){ tree:merge($acc,tree:nest($this,$del)) }
+ )
+};
+
+(:~ convert a path to xml :)
+declare function tree:nest($path as xs:string,$del as xs:string)
+as element(*)
+{
+ let $path:=if(starts-with($path,$del)) then $path else $del || $path
+ let $parts:=fn:tokenize(($path),"\" || $del)
+ return fn:fold-right(subsequence($parts,1,count($parts)-1),
+ ,
+ tree:wrap#2
+ )
+};
+
+declare function tree:wrap($this as xs:string,$acc)
+as element(*)
+{
+ {$acc}
+};
+
+
+declare function tree:merge($a1 as element(*)?,$a2 as element(*)?)
+as element(*)*
+{
+ if($a1/@name=$a2/@name) then
+ let $n1:=$a1/*
+ let $n2:=$a2/*
+
+ let $t:=(
+ for $x in fn:distinct-values($n1/@name[.=$n2/@name]) (:both:)
+ return tree:merge($a1/*[@name=$x],$a2/*[@name=$x]),
+
+ for $x in fn:distinct-values($n1/@name[fn:not(.=$n2/@name)]) (:only $a1 :)
+ return $a1/*[@name=$x],
+
+ for $x in fn:distinct-values($n2/@name[fn:not(.=$n1/@name)]) (:only $a2 :)
+ return $a2/*[@name=$x]
+ )
+ return tree:wrap($a1/@name,for $x in $t order by $x/@name return $x)
+ else
+ ($a1,$a2)
+};
+
diff --git a/webapp/lsp-manager/lib.xq/wrangler.xqm b/webapp/lsp-manager/lib.xq/wrangler.xqm
new file mode 100644
index 0000000..570d2ef
--- /dev/null
+++ b/webapp/lsp-manager/lib.xq/wrangler.xqm
@@ -0,0 +1,199 @@
+xquery version '3.1';
+(:~
+Library to manage running multiple jobs using BaseX job:eval,
+where the jobs are the execution of one function for a set of arguments
+
+The function will have signature `fn($key as xs:string) as element()`
+The function will typically create outputs and have side effects.
+
+State information is persisted with the storage module, using the key '_wrangle'
+It is a map{$wid: {"jobs":}}
+requires basex 10+
+@licence BSD
+@author: quodatum
+@date: 2023/02/12
+:)
+module namespace wrangle = 'urn:quodatum:wrangler';
+(:~ semantic version :)
+declare variable $wrangle:version:="1.0.0";
+(:~ used in bindings to indicate a wrangle job and as store key :)
+declare variable $wrangle:id:="_wrangle";
+
+(:~
+submit wrangle jobs for each $item
+@param wrangle data{xq:...,bindings:..}
+@return unique id for the job set
+:)
+declare function wrangle:queue($items as item()*,$wrangle as map(*))
+as xs:string{
+let $wid := random:uuid()
+let $jobs := $items!job:eval($wrangle?xq,
+ map:merge(($wrangle?bindings(.),map:entry($wrangle:id,$wid))),
+ map{"cache":true()}
+ )
+
+let $this := map:entry($wid,map:entry("jobs",$jobs!map:entry(.,
+ map{
+ "complete":false(),
+ "details":job:list-details(.)
+ })))
+
+let $_:=store:put($wrangle:id,map:merge((($this,wrangle:store()))))
+return $wid
+};
+
+(:~ active wrangle ids :)
+declare function wrangle:active()
+as xs:string*{
+ job:list()!job:bindings(.)?($wrangle:id)=>distinct-values()
+};
+
+(:~ known wrangles :)
+declare function wrangle:list()
+as xs:string*{
+ wrangle:store()=>map:keys()
+};
+
+(:~ details for $wid wrangle :)
+declare function wrangle:list-details($wid as xs:string)
+as map(*){
+ wrangle:store()=>map:get($wid)
+};
+
+(:~ all wrangled jobs :)
+declare function wrangle:job-list()
+as xs:string*{
+ job:list()[job:bindings(.)=>map:contains($wrangle:id)]
+};
+
+(:~ jobs for wrangle id :)
+declare function wrangle:job-list($wid as xs:string)
+ as xs:string*{
+ job:list()[job:bindings(.)?($wrangle:id) eq $wid]
+};
+
+(:~ is wrangle id finished (or unknown) :)
+declare function wrangle:finished($wid as xs:string)
+ as xs:string*{
+ every $job in job:list()[job:bindings(.)?($wrangle:id) eq $wid] satisfies job:finished($job)
+};
+
+(:~ wait wrangle id finished (or unknown) :)
+declare function wrangle:wait($wid as xs:string)
+ as empty-sequence(){
+ let $done:=every $job in job:list()[job:bindings(.)?($wrangle:id) eq $wid]
+ satisfies empty(job:wait($job))
+ return if($done) then ()
+};
+
+(:~ cancel wrangle id :)
+declare function wrangle:remove($wid as xs:string)
+as empty-sequence(){
+ job:list()[job:bindings(.)?($wrangle:id) eq $wid]!job:remove(.),
+ store:put($wrangle:id,wrangle:store()=>map:remove($wid))
+};
+
+(:~ tally of non-zero job status for $wid "scheduled", "queued", "running", "cached" :)
+declare function wrangle:status($wid as xs:string)
+as map(*){
+ wrangle:job-list($wid)!job:list-details(.)/@state/string()
+ =>fold-left(map{},wrangle:tally-count#2)
+};
+
+(:~ job-results with no error as sequence:)
+declare function wrangle:results($wid as xs:string)
+as item()*{
+ wrangle:job-list($wid)!wrangle:job-result(.)[not(?error)]?result
+};
+
+(:~ error counts keyed on $err:code :)
+declare function wrangle:errors($wid as xs:string)
+as map(*){
+ wrangle:job-list($wid)!wrangle:job-result(.)[?error]?result?code!string()
+ =>fold-left(map{},wrangle:tally-count#2)
+};
+
+(:~ key is $err:code values are joblists :)
+declare function wrangle:jobs-by-error($wid as xs:string)
+as map(*){
+ (for $jobId in wrangle:job-list($wid)
+ let $result:=wrangle:job-result($jobId)[?error]
+ where exists($result)
+ return map:entry($result?result?code!string(),$jobId)
+ )
+=> map:merge( map{"duplicates":"combine"})
+};
+
+(:~ return key for job:)
+declare function wrangle:job-key($jobId as xs:string)
+as xs:string{
+let $b:=job:bindings($jobId)
+return $b?(map:keys($b)[. ne $wrangle:id])
+};
+
+(:~ return map from peek at result:)
+declare function wrangle:job-result($jobId as xs:string)
+as map(*){
+ try{
+ map{
+ "error":false(),
+ "result": job:result($jobId,map{"keep":true()})
+ }
+ }catch *{
+ map{
+ "error":true(),
+ "result": map{"description": $err:description,
+ "code": $err:code,
+ "line": $err:column-number,
+ "additional": $err:additional,
+ "value":$err:value
+ }
+ }
+ }
+};
+
+(:~ XQuery for background service :)
+declare function wrangle:service()
+as xs:string{
+``[
+import module namespace wrangle = 'urn:quodatum:wrangler:v1'; at "`{ static-base-uri() }`";
+let $done:=wrangle:job-list()[job:finished(.)]
+if(exists($done))
+then let $w:=store:get($wrangle:id)
+ for $job in $done
+ group by $wid= job:bindings($job)?($wrangle:id)
+ for $job in $job
+ let $_:=store:put($wrangle:id)
+]``
+
+};
+(:~ schedule as service :)
+declare function wrangle:schedule-service()
+as xs:string{
+ wrangle:service()
+ =>job:eval((), map { 'id':$wrangle:id, 'service':true(),
+ 'interval': 'PT1S','log': $wrangle:id})
+};
+
+(:~ cached data as map :)
+declare function wrangle:store()
+as map(*){
+ store:get-or-put($wrangle:id,function(){map{}})
+};
+
+(:~ @return map string->count for fold-left :)
+declare %private function wrangle:tally-count($r as map(*),$this as xs:string)
+as map(*){
+ map:merge(
+ (map:entry($this,if(map:contains($r,$this)) then $r($this)+1 else 1),$r),
+ map{"duplicates":"use-first"}
+ )
+};
+(:~ @return map string->(string*) for fold-left :)
+declare %private function wrangle:tally-list($r as map(*),$key as xs:string,$value as xs:string)
+as map(*){
+ map:merge(
+ (map:entry($key,$value),$r),
+ map{"duplicates":"combine"}
+ )
+};
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/components.htm b/webapp/lsp-manager/views/components.htm
new file mode 100644
index 0000000..67702c2
--- /dev/null
+++ b/webapp/lsp-manager/views/components.htm
@@ -0,0 +1,58 @@
+
+
+
+
+ Components
+
+
+
+
+
Components (test page) wc
+
You have no direct messages
+
+
+
+ Available Trees
+
+
+
+
+ Slide 1
+ Slide 2
+ Slide 3
+ Slide 4
+ Slide 5
+ Slide 6
+ Slide 21
+ Slide 22
+ Slide 23
+ Slide 24
+ Slide 25
+ Slide 26
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/dev/dba-iframe.htm b/webapp/lsp-manager/views/dev/dba-iframe.htm
new file mode 100644
index 0000000..1f76fb4
--- /dev/null
+++ b/webapp/lsp-manager/views/dev/dba-iframe.htm
@@ -0,0 +1,21 @@
+
+
+
+
+ dev
+
+
+
+
+
+ DEV
+ dba
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/dev/home.htm b/webapp/lsp-manager/views/dev/home.htm
new file mode 100644
index 0000000..729ddc8
--- /dev/null
+++ b/webapp/lsp-manager/views/dev/home.htm
@@ -0,0 +1,40 @@
+
+
+
+
+ Dev home
+
+
+
+
+
+
+
+
+ list tasks etc
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/dev/job.htm b/webapp/lsp-manager/views/dev/job.htm
new file mode 100644
index 0000000..e31942c
--- /dev/null
+++ b/webapp/lsp-manager/views/dev/job.htm
@@ -0,0 +1,28 @@
+
+
+
+
+ Jobs
+
+
+
+
+
+
+ Jobs home
+ jobs 4
+
+ Refresh
+
+
+
Jobs
+
+
+ Nothing Yet!
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/error.htm b/webapp/lsp-manager/views/error.htm
new file mode 100644
index 0000000..3f5c2de
--- /dev/null
+++ b/webapp/lsp-manager/views/error.htm
@@ -0,0 +1,27 @@
+
+
+
+
+ Error
+
+
+
+
+
Error:
+
+ Description
+
+ Value
+
+ Module
+ [ , ]
+ Additional
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/home.htm b/webapp/lsp-manager/views/home.htm
new file mode 100644
index 0000000..13cb0d7
--- /dev/null
+++ b/webapp/lsp-manager/views/home.htm
@@ -0,0 +1,64 @@
+
+
+
+
+Home
+
+
+
+
+
+
+
+
+
+
Contacts
+
+
+
+ Name
+ Email
+
+
+
+
+
+
+
Add A Contact
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/job/home.htm b/webapp/lsp-manager/views/job/home.htm
new file mode 100644
index 0000000..b40c99f
--- /dev/null
+++ b/webapp/lsp-manager/views/job/home.htm
@@ -0,0 +1,56 @@
+
+
+
+
+ Jobs home
+
+
+
+
+
+
+
+ Job
+
+
+
+
+
+
+
+
+
+
+
Something
+ ,covers:
+
+
+
+
+
+
+
+
Job tasks
+
+
+
+
+
Last updated 3 mins ago
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/job/list.htm b/webapp/lsp-manager/views/job/list.htm
new file mode 100644
index 0000000..e31942c
--- /dev/null
+++ b/webapp/lsp-manager/views/job/list.htm
@@ -0,0 +1,28 @@
+
+
+
+
+ Jobs
+
+
+
+
+
+
+ Jobs home
+ jobs 4
+
+ Refresh
+
+
+
Jobs
+
+
+ Nothing Yet!
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/layout.htm b/webapp/lsp-manager/views/layout.htm
new file mode 100644
index 0000000..f33c025
--- /dev/null
+++ b/webapp/lsp-manager/views/layout.htm
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+ MDUI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/login.htm b/webapp/lsp-manager/views/login.htm
new file mode 100644
index 0000000..3eb4da1
--- /dev/null
+++ b/webapp/lsp-manager/views/login.htm
@@ -0,0 +1,25 @@
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/notifications.htm b/webapp/lsp-manager/views/notifications.htm
new file mode 100644
index 0000000..93238a5
--- /dev/null
+++ b/webapp/lsp-manager/views/notifications.htm
@@ -0,0 +1,15 @@
+
+
+
+
+ Notifications
+
+
+
+
+
Notifications
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/pdf/home.htm b/webapp/lsp-manager/views/pdf/home.htm
new file mode 100644
index 0000000..94ed1ae
--- /dev/null
+++ b/webapp/lsp-manager/views/pdf/home.htm
@@ -0,0 +1,78 @@
+
+
+
+
+ PDF home
+
+
+
+
+
+
+
+ PDF
+
+
+
+
+
+
+
+
+
+
+
PDF slugs
+ ,covers:
+
+
+
+
+
+
+
+
Last updated 3 mins ago
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/pdf/item-iframe.htm b/webapp/lsp-manager/views/pdf/item-iframe.htm
new file mode 100644
index 0000000..7e3cea2
--- /dev/null
+++ b/webapp/lsp-manager/views/pdf/item-iframe.htm
@@ -0,0 +1,16 @@
+
+
+
+
+ PDF-view
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/pdf/item.htm b/webapp/lsp-manager/views/pdf/item.htm
new file mode 100644
index 0000000..f1b0b59
--- /dev/null
+++ b/webapp/lsp-manager/views/pdf/item.htm
@@ -0,0 +1,60 @@
+
+
+
+
+ PDF
+
+
+
+
+
+ PDF
+ list
+ #
+
+
+
+
+
+
+
+
+
+
Card title
+
This is a wider card with supporting text below as a natural lead-in to additional
+ content. This card has even longer content than the first to show that equal height action.
+
Last updated 3 mins ago
+
+
+
+
+
+
Tasks
+
+
+
1
+
2
+
+
+
+ Dropdown
+
+
+
+
+
+
Last updated 3 mins ago
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/pdf/list.htm b/webapp/lsp-manager/views/pdf/list.htm
new file mode 100644
index 0000000..be60fff
--- /dev/null
+++ b/webapp/lsp-manager/views/pdf/list.htm
@@ -0,0 +1,53 @@
+
+
+
+
+ Pdfs
+
+
+
+
+
+ PDF
+ list (# )
+
+
+
+
+
+
+
+
+ path
+ {pdf.index}
+
+
+
+
{pdf}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/pdf/settings.htm b/webapp/lsp-manager/views/pdf/settings.htm
new file mode 100644
index 0000000..168c1a3
--- /dev/null
+++ b/webapp/lsp-manager/views/pdf/settings.htm
@@ -0,0 +1,97 @@
+
+
+
+
+ Settings
+
+
+
+
+
+ PDF
+ settings
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/profile.htm b/webapp/lsp-manager/views/profile.htm
new file mode 100644
index 0000000..0d45d02
--- /dev/null
+++ b/webapp/lsp-manager/views/profile.htm
@@ -0,0 +1,17 @@
+
+
+
+
+ Profile
+
+
+
+
+
{uid} profile
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/test.htm b/webapp/lsp-manager/views/test.htm
new file mode 100644
index 0000000..aa7c478
--- /dev/null
+++ b/webapp/lsp-manager/views/test.htm
@@ -0,0 +1,42 @@
+
+
+
+
+ test
+
+
+
+
+
+
+
+
+ path
+ {pdf.index}
+
+
+
{pdf}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp-manager/views/tweets.htm b/webapp/lsp-manager/views/tweets.htm
new file mode 100644
index 0000000..bf3928f
--- /dev/null
+++ b/webapp/lsp-manager/views/tweets.htm
@@ -0,0 +1,67 @@
+
+
+
+
+Tweets
+
+
+
+
Tweets
+
+
+
+
+
+
+
diff --git a/webapp/lsp-manager/views/unknown.htm b/webapp/lsp-manager/views/unknown.htm
new file mode 100644
index 0000000..6c0fa95
--- /dev/null
+++ b/webapp/lsp-manager/views/unknown.htm
@@ -0,0 +1,19 @@
+
+
+
+
+ 404
+
+
+
+
+
Page not found:
+
+ Page: pdf2/${ path }
+ Method: ${ method }
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/lsp/etc/snippets.json b/webapp/lsp/etc/snippets.json
new file mode 100644
index 0000000..3eb5801
--- /dev/null
+++ b/webapp/lsp/etc/snippets.json
@@ -0,0 +1,266 @@
+{
+ "new library module": {
+ "isFileTemplate": true,
+ "prefix": "library module",
+ "body": [
+ "xquery version '3.1';",
+ "(:~",
+ "@author: ",
+ "@date: $CURRENT_YEAR/$CURRENT_MONTH/$CURRENT_DATE",
+ ":)",
+ "module namespace ${1:prefix} = '${2:http://www.example.com/}';",
+ ""
+ ],
+ "description": "New library module template"
+ },
+ "new main module": {
+ "isFileTemplate": true,
+ "prefix": "main module",
+ "body": [
+ "xquery version '3.1';",
+ "(:~",
+ ":)",
+ "${1:expr}",
+ ""
+ ],
+ "description": "New main module template"
+ },
+ "flowr": {
+ "prefix": [
+ "for",
+ "flowr"
+ ],
+ "body": [
+ "for \\$${1:var} at \\$${2:pos} in ${3:expr}",
+ "let \\$${4:var2} := ${5:expr}",
+ "where ${6:boolean}",
+ "order by ${7:expr}",
+ "return ${8:expr2}"
+ ],
+ "description": "Full FLOWR expression"
+ },
+ "return": {
+ "prefix": "return",
+ "body": "return ${1:expr}"
+ },
+ "import": {
+ "prefix": "import",
+ "body": "import module namespace ${1:ns} = '${2:http://www.example.com/}';",
+ "description": "Import module"
+ },
+ "if": {
+ "prefix": "if",
+ "body": [
+ "if (${1:boolean})",
+ "then ${2:expr1}",
+ "else ${3:expr2}"
+ ],
+ "description": "If then else expression"
+ },
+ "module": {
+ "prefix": "module",
+ "body": "module namespace ${1:ns} = '${2:http://www.example.com}';"
+ },
+ "every": {
+ "prefix": "every",
+ "body": "every \\$${1:varname} in ${2:expr} satisfies ${3:expr}"
+ },
+ "some": {
+ "prefix": "some",
+ "body": "some \\$${1:varname} in ${2:expr} satisfies ${3:expr}"
+ },
+ "declare namespace": {
+ "prefix": [
+ "declare",
+ "namespace"
+ ],
+ "body": [
+ "declare ${1:prefix}='${2:namespace}';",
+ ""
+ ],
+ "description": "declare namespace"
+ },
+ "declare base-uri": {
+ "prefix": [
+ "declare",
+ "baseuri"
+ ],
+ "body": [
+ "declare base-uri '${1:uriliteral}';",
+ ""
+ ],
+ "description": "declare base-uri"
+ },
+ "declare option": {
+ "prefix": [
+ "declare",
+ "option"
+ ],
+ "body": [
+ "declare option ${1:eqname} '${2:string}';",
+ ""
+ ],
+ "description": "declare option"
+ },
+ "declare function": {
+ "prefix": [
+ "declare",
+ "function"
+ ],
+ "body": [
+ "(:~ ${1:name} :)",
+ "declare function ${2:ns}:${1:name}()",
+ "as ${3:type}{",
+ "${3:expr}",
+ "};",
+ ""
+ ],
+ "description": "declare function"
+ },
+ "declare variable": {
+ "prefix": [
+ "declare",
+ "variable"
+ ],
+ "body": [
+ "(:~ \\$${1:varname} :)",
+ "declare variable \\$${1:varname} := ${2:expr};",
+ ""
+ ],
+ "description": "declare variable"
+ },
+ "switch": {
+ "prefix": "switch",
+ "body": [
+ "switch(${1:foo})",
+ "case ${2:foo} return ${3:true}",
+ "default return ${4:false}"
+ ],
+ "description": "switch statement"
+ },
+ "typeswitch": {
+ "prefix": "type",
+ "body": [
+ "typeswitch(${1:foo})",
+ "case ${2:foo} return ${3:true}",
+ "default return ${4:false}"
+ ],
+ "description": "typeswitch statement"
+ },
+ "try": {
+ "prefix": "try",
+ "body": [
+ "try {",
+ " ${1:expr}",
+ "} catch ${2:*}",
+ " { ${3:expr}",
+ "}"
+ ],
+ "description": "try catch"
+ },
+ "tumbling": {
+ "prefix": [
+ "for",
+ "tumbling",
+ "window"
+ ],
+ "body": [
+ "for tumbling window \\$${1:varname} in ${2:expr}",
+ "start at \\$${3:start} when ${4:expr}",
+ "end at \\$${5:end} when ${6:expr}",
+ "return ${7:expr}"
+ ],
+ "description": "tumbling window"
+ },
+ "sliding": {
+ "prefix": [
+ "for",
+ "sliding",
+ "window"
+ ],
+ "body": [
+ "for sliding window \\$${1:varname} in ${2:expr}",
+ "start at \\$${3:start} when ${4:expr}",
+ "end at \\$${5:end} when ${6:expr}",
+ "return ${7:expr}"
+ ],
+ "description": "sliding window"
+ },
+ "let": {
+ "prefix": "let",
+ "body": "let \\$${1:varname} := ${2:expr}"
+ },
+ "castable": {
+ "body": "castable as ${1:atomicType}"
+ },
+ "cast": {
+ "body": "cast as ${1:atomicType}"
+ },
+
+ "update insert": {
+ "prefix": [
+ "update",
+ "insert"
+ ],
+ "body": "insert node ${1:expr} into ${2:xpath}"
+ },
+ "update delete": {
+ "prefix": ["delete","update"],
+ "body": "delete node ${1:xpath}"
+ },
+ "update replace node": {
+ "prefix":["update","replace"],
+ "body": "replace node ${1:xpath} with ${2:expr}"
+ },
+ "update replace value": {
+ "prefix": [ "update",
+ "replace",
+ "value"
+ ],
+ "body": "replace value of node ${1:xpath} with ${2:expr}"
+ },
+ "update rename": {
+ "prefix": [
+ "update",
+ "rename"
+ ],
+ "body": "rename node ${1:xpath} as ${2:eqname}"
+ },
+ "copy modify return": {
+ "prefix": [
+ "copy",
+ "modify",
+ "return"
+ ],
+ "body": [
+ "copy \\$${1:varname} := ${2:node}",
+ "modify ${3:updates}",
+ "return \\$${1:varname}"
+ ]
+ },
+ "transform with": {
+ "prefix": [
+ "transform",
+ "with",
+ "update"
+ ],
+ "body": [
+ "${1:node} transform with {",
+ " ${2:update}",
+ "}"
+ ]
+ },
+ "transform update": {
+ "prefix": [
+ "transform",
+ "update"
+ ],
+ "body": [
+ "${1:node} update {",
+ "${2:update}",
+ "}"
+ ]
+ }
+}
+
+
diff --git a/webapp/lsp/lsp-text.xqm b/webapp/lsp/lsp-text.xqm
index 661a2d4..83102be 100644
--- a/webapp/lsp/lsp-text.xqm
+++ b/webapp/lsp/lsp-text.xqm
@@ -50,9 +50,9 @@ declare
function lsp-text:completion($json as map(*))
as map(*)?
{
- let $doc:=$json?params?textDocument?uri
+ let $uri:=$json?params?textDocument?uri
let $context:=$json?params?context (:{"triggerCharacter":":","triggerKind":2.0e0}:)
- let $result:=comp:list($context)
+ let $result:=comp:list($context)=>prof:time("⏱️ completions " || $uri)
return rpc:result($json,array:build($result))
};
diff --git a/webapp/lsp/providers/completions.xqm b/webapp/lsp/providers/completions.xqm
index 9b14a99..d903d40 100644
--- a/webapp/lsp/providers/completions.xqm
+++ b/webapp/lsp/providers/completions.xqm
@@ -2,13 +2,15 @@ module namespace comp = 'lsp-completions';
import module namespace lspt = 'lsp-typedefs' at "../lsp-typedefs.xqm";
import module namespace ctx="lsp/context" at "../context.xqm";
+declare variable $comp:snippet-source:="../etc/snippets.json";
+declare variable $comp:snippets:=json:doc($comp:snippet-source,{"format":"w3"});
(: (:{"triggerCharacter":":","triggerKind":2.0e0}:):)
declare function comp:list($context as map(*))
as lspt:CompletionItem*
{
message($context,"context: "),
- ctx:functions("fn")!ctx:map(.)=>trace("aaa")
+ ctx:functions("fn")!ctx:map(.)
};
declare function comp:dummy($context as map(*))
diff --git a/webapp/lsp/providers/documentSymbols.xqm b/webapp/lsp/providers/documentSymbols.xqm
index 5550161..4c136f4 100644
--- a/webapp/lsp/providers/documentSymbols.xqm
+++ b/webapp/lsp/providers/documentSymbols.xqm
@@ -5,17 +5,18 @@ import module namespace hnd="lsp/handlers" at "../handlers.xqm";
import module namespace lspt = 'lsp-typedefs' at "../lsp-typedefs.xqm";
import module namespace pos="lsp/position" at "../position.xqm";
-declare function syms:list($parse as element(),$text as xs:string)
-as lspt:DocumentSymbol*{
- let $actions as hnd:actionMap :={
+declare variable $syms:actions as hnd:actionMap :={
"ContextValueDecl": syms:action#2,
"VarDecl": syms:VarDecl#2,
"FunctionDecl": syms:FunctionDecl#2,
"ItemTypeDecl": syms:action#2,
- "NamedRecordTypeDecl": syms:action#2
- }
+ "NamedRecordTypeDecl": syms:NamedRecordTypeDecl#2
+ };
+
+declare function syms:list($parse as element(),$text as xs:string)
+as lspt:DocumentSymbol*{
let $state:= hnd:Result(())
- let $result:= hnd:walk($parse,$actions,$state)
+ let $result:= hnd:walk($parse,$syms:actions,$state)
return $result?result
};
@@ -42,7 +43,7 @@ as hnd:Result{
let $name:=syms:localName($parse/VarNameAndType/EQName)
let $length:=string($parse)=>string-length()
let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3))
- let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Variable'),$range,$range,"TODO")
+ let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Variable'),$range,$range,"VAR")
return ($state?result,$sym)=>hnd:Result(true())
};
@@ -50,7 +51,16 @@ declare function syms:FunctionDecl($parse as element(FunctionDecl),$state as hn
as hnd:Result{
let $name:=syms:localName($parse/UnreservedFunctionEQName)
let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3))
- let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Method'),$range,$range,"TODO")
+ let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('Method'),$range,$range,"FUN")
+ return ($state?result,$sym)=>hnd:Result(true())
+};
+
+declare function syms:NamedRecordTypeDecl($parse as element(NamedRecordTypeDecl), $state as hnd:Result )
+as hnd:Result{
+ let $name:=syms:localName($parse/EQName)
+ let $range:=lspt:Range(lspt:Position(0,0),lspt:Position(0,3))
+ let $sym:=lspt:DocumentSymbol($name,$lspt:SymbolKindMap('TypeParameter'),$range,$range,"--RECORD")
+ =>trace("RECORD")
return ($state?result,$sym)=>hnd:Result(true())
};
diff --git a/webapp/static/clients/codemirror/grail.css b/webapp/static/clients/codemirror/grail.css
index 53ed78a..96f37fe 100644
--- a/webapp/static/clients/codemirror/grail.css
+++ b/webapp/static/clients/codemirror/grail.css
@@ -63,6 +63,12 @@ form header {
overflow: auto;
}
+details[open]::details-content {
+ color: dodgerblue;
+ padding: 0.1em;
+ border: thin solid grey;
+ overflow:auto;
+}
::backdrop {
backdrop-filter: blur(2px);
}
@@ -78,6 +84,7 @@ form header {
}
}
+
.page-header {
grid-column: 1 / -1;
background: #ffcdd2;
diff --git a/webapp/static/clients/codemirror/grail.html b/webapp/static/clients/codemirror/grail.html
index 823357a..b57b126 100644
--- a/webapp/static/clients/codemirror/grail.html
+++ b/webapp/static/clients/codemirror/grail.html
@@ -26,7 +26,7 @@
Editor
- LSP UI
+ LSP Manager
Dba
@@ -57,7 +57,11 @@
generator.xquery
+
+
+
+
@@ -144,8 +148,8 @@
xquery
xml
-
-
+
+
@@ -167,21 +171,31 @@
-
+
- TODO
+
+
+
+
diff --git a/webapp/static/clients/codemirror/script.js b/webapp/static/clients/codemirror/script.js
index 1b21721..5aa24f6 100644
--- a/webapp/static/clients/codemirror/script.js
+++ b/webapp/static/clients/codemirror/script.js
@@ -85,15 +85,15 @@ $("symbols2").onclick = e => {
});
};
-$("cmd").onclick = e => {
+$("cmdList").onclick = e => {
let cmds = lsp.listCommands(view);
let result="";
[...cmds.keys()].sort().forEach(key => {
result+=`${key} ${cmds.get(key).key} `
});
$("popHelpInfo").innerHTML=``
- $("popHelp").showPopover()
- console.log(result)
+ $("popCmds").showPopover()
+
};
$("symList").addEventListener("itemSelected", e => {
diff --git a/webapp/static/clients/codemirror/wc-qd-list.js b/webapp/static/clients/codemirror/wc-qd-list.js
index 4257948..3a3b76b 100644
--- a/webapp/static/clients/codemirror/wc-qd-list.js
+++ b/webapp/static/clients/codemirror/wc-qd-list.js
@@ -34,7 +34,7 @@ class ListComponent extends HTMLElement {
/* Render the list items #data */
render() {
const list = document.createElement('ul');
- list.style = "max-height:80cqh;overflow: auto;";
+ list.style = "max-height:45cqh;overflow: auto;";
const select = e => {
if(e.type ==="keyup" && !(e.key==="Enter" )) return;
@@ -65,7 +65,7 @@ class ListComponent extends HTMLElement {
const style = document.createElement('style');
style.textContent = `
@import url("../codicon@0.0.40/codicon.css");
- ul { list-style-type: none; padding:0;margin:0;max-height:80cqh;overflow: auto;
+ ul { list-style-type: none; padding:0;margin:0;overflow: auto;
background-color: #f8f9fa;font-size: 80%;}
li { padding: 0 0 0 2px; border-bottom: 1px solid #ccc; cursor: pointer; width:100%; }
li:not(.selected) :hover { background-color: #ccc; }
@@ -83,4 +83,88 @@ class ListComponent extends HTMLElement {
}
/* Define the new custom element */
-customElements.define('qd-list', ListComponent);
\ No newline at end of file
+customElements.define('qd-list', ListComponent);
+
+class PanelComponent extends HTMLElement {
+ #shadow;
+ #data;
+ #iconKinds; //array from kind integer to codicon name
+ constructor() {
+ super();
+ this.#shadow = this.attachShadow({ mode: "open", delegatesFocus: true });
+ this.#data = [];
+ this.#iconKinds = [];
+ this.render();
+ }
+
+ setData(newData, icons, append = false) {
+ if (!Array.isArray(newData)) {
+ console.warn("Invalid format, expected an array.");
+ return;
+ }
+ this.#iconKinds = icons;
+ this.#data = append ? this.#data.concat(newData) : structuredClone(newData);
+ this.render();
+ }
+
+ set data(value) {
+ this.setData(value, false);
+ }
+ get data() {
+ return this.#data;
+ }
+
+ /* Render the list items #data */
+ render() {
+ const list = document.createElement('ul');
+ list.style = "max-height:45cqh;overflow: auto;";
+
+ const select = e => {
+ if(e.type ==="keyup" && !(e.key==="Enter" )) return;
+ this.#shadow.querySelectorAll('li.selected').forEach(item => { item.className = ''; });
+ e.currentTarget.className = 'selected';
+ const i=e.currentTarget.getAttribute("data-index")
+ console.log('Item index clicked:', i,this.#data[i]);
+ // You can dispatch a custom event here if needed.
+ this.dispatchEvent(new CustomEvent('itemSelected', {
+ detail: this.#data[i],
+ bubbles: true,
+ composed: true
+ }));
+ };
+ this.#data.forEach((item, index) => {
+ const listItem = document.createElement('li');
+ listItem.setAttribute("tabindex", "0")
+ listItem.setAttribute("data-index", index)
+ listItem.innerHTML = `
+ ${item.name} - ${item.detail} `;
+
+ listItem.addEventListener('click', select);
+ listItem.addEventListener('keyup', select);
+ list.appendChild(listItem);
+ });
+
+ this.#shadow.innerHTML = '';
+ const style = document.createElement('style');
+ style.textContent = `
+ @import url("../codicon@0.0.40/codicon.css");
+ ul { list-style-type: none; padding:0;margin:0;overflow: auto;
+ background-color: #f8f9fa;font-size: 80%;}
+ li { padding: 0 0 0 2px; border-bottom: 1px solid #ccc; cursor: pointer; width:100%; }
+ li:not(.selected) :hover { background-color: #ccc; }
+ .selected { background-color: #0d6efd;color: #ffff;}
+ i {vertical-align: middle;}
+ `;
+ this.#shadow.appendChild(style);
+ this.#shadow.appendChild(list);
+ }
+
+ /* Render an error message if JSON fetching fails */
+ renderError(error) {
+ this.#shadow.innerHTML = `Error loading data: ${error.message}
`;
+ }
+}
+
+/* Define the new custom element */
+customElements.define('qd-panel', PanelComponent);
+