app.xqm 16.9 KB
Newer Older
Mathias Goebel's avatar
Mathias Goebel committed
1
xquery version "3.1";
Mathias Goebel's avatar
Mathias Goebel committed
2
3
4
5
6
(:~
 : generic and simple functions used by templates and other modules.
 : also an incubator of new modules.
 :
:)
Mathias Goebel's avatar
Mathias Goebel committed
7

Mathias Goebel's avatar
Mathias Goebel committed
8
module namespace app="https://sade.textgrid.de/ns/app";
Mathias Goebel's avatar
Mathias Goebel committed
9

Mathias Goebel's avatar
Mathias Goebel committed
10
import module namespace config="https://sade.textgrid.de/ns/config" at "config.xqm";
Mathias Goebel's avatar
Mathias Goebel committed
11
import module namespace templates="http://exist-db.org/xquery/templates" ;
Mathias Goebel's avatar
Mathias Goebel committed
12
13
import module namespace functx="http://www.functx.com";

14
declare namespace rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
Mathias Goebel's avatar
Mathias Goebel committed
15
declare namespace tei="http://www.tei-c.org/ns/1.0";
Mathias Goebel's avatar
Mathias Goebel committed
16
declare namespace test="http://exist-db.org/xquery/xqsuite";
17
18
declare namespace tgmd="http://textgrid.info/namespaces/metadata/core/2010";
declare namespace tgrel="http://textgrid.info/relation-ns#";
19
declare namespace expath="http://expath.org/ns/pkg";
Mathias Goebel's avatar
Mathias Goebel committed
20
declare namespace xqdoc="http://www.xqdoc.org/1.0";
21

22
23
declare variable $app:expath := doc($config:app-root || "/expath-pkg.xml");

24
(:~
Mathias Goebel's avatar
Mathias Goebel committed
25
 : Get the title of this app by using the config.xml.
Mathias Goebel's avatar
Mathias Goebel committed
26
27
28
 : @param $node the HTML node with the attribute which triggered this call
 : @param $model a map containing arbitrary data - used to pass information between template calls
 :)
Mathias Goebel's avatar
Mathias Goebel committed
29
30
31
declare function app:title($node as node(), $model as map(*)) {
    config:get("project-title")
};
Mathias Goebel's avatar
Mathias Goebel committed
32

Mathias Goebel's avatar
Mathias Goebel committed
33
34
35
36
37
38
39
40
41
(:~
 : Get the target of this app by using the repo.xml.
 : @param $node the HTML node with the attribute which triggered this call
 : @param $model a map containing arbitrary data - used to pass information between template calls
 :)
declare function app:target($node as node(), $model as map(*)) {
    config:repoget("target")
};

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
(:~
 : Get the internal title of this app that is used as prefix for charmaps.txt
 : and synonyms.txt. :)
declare function app:get-prefix() {
   let $abbrev := $app:expath/expath:package/@abbrev/string() => replace("SADE", "sade")

   return
       (: in case the abbreviation is SADE-develop we have a generic instance
       which has the prefix 'sade-' :)
       if($abbrev = "sade-develop") then
           "sade"
       else
           $abbrev
};

Mathias Goebel's avatar
Mathias Goebel committed
57
58
59
60
61
(:~
 : Provides dynamic CSS based on the requested resource name :)
declare function app:css-injection($node as node(), $model as map(*), $exist-resource) {
    switch ($exist-resource)
        case "publish.html" return <link href="~assets/publish-gui/publish-gui.css" rel="stylesheet"/>
Mathias Goebel's avatar
minor    
Mathias Goebel committed
62
63
64
        case "content.html" return
                (<link href="~assets/TEI-Stylesheets/tei.css" rel="stylesheet"/>,
                 <link href="templates/css/content.css" rel="stylesheet"/>)
Mathias Goebel's avatar
Mathias Goebel committed
65
66
67
68
69
70
71
72
73
74
        default return ()
};

(:~
 : Provides dynamic JavaScripts based on the requested resource name :)
declare function app:javascript-injection($node as node(), $model as map(*), $exist-resource) {
    switch ($exist-resource)
        case "publish.html" return <script src="~assets/publish-gui/publish-gui.js"/>
        default return ()
};
Mathias Goebel's avatar
Mathias Goebel committed
75
76
77
78
79

(:~
 : serves a random fontawesome icon :
 :)
declare function app:icon($node as node(), $model as map(*)) {
Mathias Goebel's avatar
minor    
Mathias Goebel committed
80
let $icons := ("bath", "bicycle", "coffee", "heart")
Mathias Goebel's avatar
Mathias Goebel committed
81
82
83
84
85
86
87
88
89
90
let $random := util:random( count($icons) ) + 1
return
    element {local-name($node)} {
        $node/@*
            [not( starts-with(local-name(), "data") )]
            [not( local-name() = "class" )],
        attribute class {
            "fa",
            "fa-"||$icons[$random]
        }
Mathias Goebel's avatar
Mathias Goebel committed
91
    }
Mathias Goebel's avatar
Mathias Goebel committed
92
};
93
94
95
96

(:~
 :
 :)
Mathias Goebel's avatar
Mathias Goebel committed
97
declare function app:recentlyPublished($node as node(), $model as map(*), $howmany) as map(*)* {
98
let $collection-uri := $config:app-root || "/" || config:get("project-id") || "/meta"
Mathias Goebel's avatar
minor    
Mathias Goebel committed
99
return if( not(xmldb:collection-available($collection-uri)) ) then () else
100
101
102
103
104
105
let $last-resources :=
    for $resource in xmldb:get-child-resources($collection-uri)
    let $last-modified := xmldb:last-modified($collection-uri, $resource)
    order by $last-modified descending
    return
        $resource
106

107
let $metadata := $last-resources[1,2,3] ! doc( $collection-uri || "/" || . )
108
return
109
110
111
112
    map { "last-resources": $metadata[1,2,3] }
};

declare function app:recentlyPublished-link($node as node(), $model as map(*), $num as xs:integer) {
113
<a href="./{$model("last-resources")[$num]//tgmd:textgridUri/string() => replace(":", "%3A")}">
Mathias Goebel's avatar
Mathias Goebel committed
114
    <img class="media-object" src="~assets/generic/icons/{replace($model("last-resources")[$num]//tgmd:format, "/", "-")}.svg" alt="{string($model("last-resources")[$num]//tgmd:format)}"/>
115
116
117
118
119
120
121
122
</a>
};

declare function app:recentlyPublished-title($node as node(), $model as map(*), $num as xs:integer) {
    $model("last-resources")[$num]//tgmd:title/text()
};

declare function app:recentlyPublished-description($node as node(), $model as map(*), $num as xs:integer) {
123
124
125
126
let $lastModinLab := $model("last-resources")[$num]//tgmd:lastModified/substring-before(., ".")
let $info := if($lastModinLab = "") then () else
    "This document was last modified in the Lab at "
    || $lastModinLab
Mathias Goebel's avatar
Mathias Goebel committed
127
128
129
130
131
132
133
134
    || "."

let $root-name := ($model("last-resources")[$num]//tgrel:rootElementLocalPart/string(.))[1]
let $namespace := ($model("last-resources")[$num]//tgrel:rootElementNamespace/string(@rdf:resource))[1]

let $additionalInfo :=  if($root-name = "") then () else
        "Its root element ("
    || $root-name
135
    || ") is in the »"
Mathias Goebel's avatar
Mathias Goebel committed
136
    || $namespace
137
    || "« namespace."
Mathias Goebel's avatar
Mathias Goebel committed
138
139

return
140
  $info || $additionalInfo
Mathias Goebel's avatar
Mathias Goebel committed
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
};

declare
function app:featuredWorks($node as node(), $model as map(*)) {
let $collection-uri := $config:app-root || "/" || config:get("project-id") || "/meta"
return if( not(xmldb:collection-available($collection-uri)) ) then () else
let $largest-resources :=
    for $extent in collection($collection-uri)//tgmd:extent
    order by number($extent/text()) descending
    return
        $extent/ancestor::tgmd:MetadataContainerType

return
    map {
        "largest-resources": $largest-resources[1,2,3,4],
        "largest-extent": $largest-resources[1]//tgmd:extent/string()
        }
158
};
Mathias Goebel's avatar
Mathias Goebel committed
159

Mathias Goebel's avatar
Mathias Goebel committed
160
161
162
declare function app:featuredWorks-a($node as node(), $model as map(*)) {
    element { node-name($node) } {
                    $node/@*[local-name() != "href"],
163
                    attribute href {"./"||$model("largest")//tgmd:textgridUri/string() => replace(":", "%3A")},
Mathias Goebel's avatar
Mathias Goebel committed
164
165
166
167
                    templates:process($node/node(), $model)
            }
};
declare function app:featuredWorks-img($node as node(), $model as map(*)) {
Mathias Goebel's avatar
Mathias Goebel committed
168
169
170
171
    element { node-name($node) } {
        attribute src {"~assets/generic/icons/"||replace($model("largest")//tgmd:format, "/", "-")||".svg"},
        attribute alt {$model("largest")//tgmd:format/string()}
    }
Mathias Goebel's avatar
Mathias Goebel committed
172
173
174
175
176
177
178
179
180
181
182
183
};
declare function app:featuredWorks-title($node as node(), $model as map(*)) {
    element { node-name($node) } {$model("largest")//tgmd:title/string()}
};
declare function app:featuredWorks-progress($node as node(), $model as map(*)) {
    element { node-name($node) } {
        attribute max { $model("largest-extent") },
        attribute value { $model("largest")//tgmd:extent/string() }
    }
};
declare function app:featuredWorks-intro($node as node(), $model as map(*)) {
    element { node-name($node) } {
Mathias Goebel's avatar
Mathias Goebel committed
184
        (doc($model("largest")/replace(base-uri(), "/meta/", "/data/"))//tei:text//text())[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
Mathias Goebel's avatar
Mathias Goebel committed
185
186
    }
};
Mathias Goebel's avatar
Mathias Goebel committed
187

Mathias Goebel's avatar
Mathias Goebel committed
188
189
190
191
192
193
194
195
196
197
198
199
200
declare function app:getLanguage() {
    let $http-header := request:get-header("Accept-Language")
    let $http-header := functx:substring-before-if-contains($http-header,"-")
    let $http-header := functx:substring-before-if-contains($http-header,";")
    let $http-header := functx:substring-before-if-contains($http-header,",")
    let $param-lang := request:get-parameter("lang", "")
    let $lang := if ($param-lang != "") then ($param-lang) else ($http-header)
    let $langConfigured := (config:get("lang.default", "multilanguage"), tokenize(config:get("lang.alt", "multilanguage"), ";"))
    let $result := if( $lang = $langConfigured ) then $lang else $langConfigured[1]

    return ($result)
};

Mathias Goebel's avatar
Mathias Goebel committed
201
202
203
declare
    %test:assertEquals("de", "en")
function app:getAllLanguages() {
204
205
206
207
    (
        config:get("lang.default", "multilanguage"),
        config:get("lang.alt", "multilanguage") => tokenize(";")
    )
Mathias Goebel's avatar
Mathias Goebel committed
208
209
};

Mathias Goebel's avatar
Mathias Goebel committed
210
211
212
213
214
215
216
217
218
219
220
221
222
declare function app:switchLanguage($node as node(), $model as map(*), $to as xs:string) as node() {
let $query-string := (
    tokenize(
        request:get-query-string(), "&amp;"
    )[not(starts-with(., "lang="))],
    "lang="||$to)
return
element { name($node) } {
    attribute href { "?" || string-join($query-string, "&amp;") },
    $node/node()
}
};

223
224
225
226
227
228
229
230
(:~
 : link rewriter. takes a URL and returns the URL including the lang
 : parameter.
 :   :)
declare
    %templates:wrap
function app:rewriteLink($ref as xs:string) as xs:string {
let $lang := app:getLanguage()
Mathias Goebel's avatar
Mathias Goebel committed
231
232
233
let $base-url := tokenize($ref, "\?|#")[1]
let $get-parameter := tokenize(substring-after($ref, "?"), "#")[1]
let $get-parameter :=   if(contains($get-parameter, "lang="))
234
235
                        then $get-parameter
                        else
Mathias Goebel's avatar
Mathias Goebel committed
236
237
                            (tokenize($get-parameter, "&amp;")[.!=""],
                            "lang="||$lang)
Mathias Goebel's avatar
Mathias Goebel committed
238
let $anchor := substring-after($ref, "#")
239
240
241
242
243
244

return
    $base-url
    || "?" || string-join($get-parameter, "&amp;")
    || (if($anchor != "") then "#" || $anchor else ())
};
Mathias Goebel's avatar
Mathias Goebel committed
245

Mathias Goebel's avatar
Mathias Goebel committed
246
247
248
249
250
declare
%templates:wrap
function app:list-docs($node as node(), $model as map(*)) {
    for $item in xmldb:get-child-resources( $config:app-root || "/docs" )
    return
Mathias Goebel's avatar
Mathias Goebel committed
251
        <li>{ $item }</li>
Mathias Goebel's avatar
Mathias Goebel committed
252
253
};

mrodzis's avatar
mrodzis committed
254
255
256
257
258
259
260
261
262
263
264
declare
%templates:wrap
function app:get-exist-version($node as node(), $model as map(*)) {
    <li>{system:get-version()}</li>
};

declare
%templates:wrap
function app:get-sade-info($node as node(), $model as map(*)) {
    let $doc := doc($config:app-root || "/expath-pkg.xml")

265
266
267
    let $version := $doc//expath:package/@version/string()
    let $uri := $doc//expath:package/@name/string()
    let $name := $doc//expath:package/@abbrev/string()
mrodzis's avatar
mrodzis committed
268
269
270
271
272
273
274

    return
        (<li>Version: {$version}</li>,
        <li>Package URI: {$uri}</li>,
        <li>Package name: {$name}</li>,
        <li>Dependencies:
            <ul>
275
                {for $dep in $doc//expath:dependency return
mrodzis's avatar
mrodzis committed
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
                    let $dep-name := $dep/(@* except @semver-min)/string()
                    return
                        <li>
                            {if($dep/@semver-min) then
                                $dep-name || " (min. " || $dep/@semver-min || ")"
                            else
                                $dep-name}
                        </li>
                }
            </ul>
        </li>
        )
};

declare
%templates:wrap
function app:get-charsyn-info($node as node(), $model as map(*)) {
293
294
295
296
297
298
299
300
  if( not(local:check-dba()) )
  then
    <div class="row">
        <div class="col-md-12">
          You have to log in to view this resource.
        </div>
    </div>
  else
mrodzis's avatar
mrodzis committed
301
302
303
304
305
306
307
308
309
310
311
312
    let $file-system-path := system:get-exist-home() || util:system-property("file.separator")
    let $dir-content := file:list($file-system-path)
    let $charmap-available := if($dir-content//*[matches(@name, "charmap\.txt")]) then true() else false()
    let $synonyms-available := if($dir-content//*[matches(@name, "synonyms\.txt")]) then true() else false()
    let $yes := <i style="color: green" class="far fa-check-circle"></i>
    let $no := <i style="color: green" class="far fa-times-circle"></i>

    return
        (<li>Charmap available? {if($charmap-available) then $yes else $no}</li>,
        <li>Synonyms available? {if($synonyms-available) then $yes else $no}</li>)
};

313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
declare
%templates:wrap
function app:display-charsyn-info($node as node(), $model as map(*)) {
  if( not(local:check-dba()) )
  then
    <div class="row">
        <div class="col-md-12">
          You have to log in to view this resource.
        </div>
    </div>
  else
    let $file-system-path := system:get-exist-home() || util:system-property("file.separator")
    let $dir-content := file:list($file-system-path)

    let $prefix := app:get-prefix()

    let $charmap-name := $prefix || "-charmap.txt"
    let $charmap-path := $file-system-path || $charmap-name
    let $charmap := file:read($charmap-path)

    let $synonyms-name := $prefix || "-synonyms.txt"
    let $synonyms-path := $file-system-path || $synonyms-name
    let $synonyms := file:read($synonyms-path)

    return
        <div class="row">
            <div class="col-md-6">
                <em>Contents of {$prefix}-charmap.txt:</em>
                <pre><code>
                    {$charmap}
                </code></pre>
            </div>
            <div class="col-md-6">
                <em>Contents of {$prefix}-synonyms.txt:</em>
                <pre><code>
                    {$synonyms}
                </code></pre>
            </div>
        </div>
};

mrodzis's avatar
mrodzis committed
354

Mathias Goebel's avatar
Mathias Goebel committed
355
356
357
358
359
360
361
362
(:~
 : serves the error messages powered by programmingexcuses.com
 : a random message is created on each error report and send out to the user.
 :   :)
declare function app:error($node as node(), $model as map(*)) {
    httpclient:get(xs:anyURI('http://programmingexcuses.com/'), false(), ())//*:body/*:div[@class="wrapper"]/*:center/*:a/text()
};

Mathias Goebel's avatar
Mathias Goebel committed
363
364
365
declare function app:currentyear($node as node(), $model as map(*)) {
    year-from-date( current-date() )
};
Mathias Goebel's avatar
Mathias Goebel committed
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445

(:~
 : A templating function to lookup and represent the XQDocs from fundocs app.
 : Depends on the optional fundocs app.
 :   :)
declare
    %templates:wrap
function app:xqdocs($node as node(), $model as map(*)) {
    let $xqdata := "/db/apps/fundocs/data/"
    return
if( not(xmldb:collection-available($xqdata)) )
then error( QName("https://sade.textgrid.de/ns/app", "XQDOCS01"), "Docs not available.")
else
    let $modules :=
        for $xqdoc in collection($xqdata)//xqdoc:location[starts-with(., $config:app-root||"/")]/ancestor::xqdoc:xqdoc
        order by $xqdoc/xqdoc:module/xqdoc:name/text()
        return
            $xqdoc
    return
(:        error( QName("https://sade.textgrid.de/ns/app", "XQDOCS01"), $modules[1]):)
    map {"modules": $modules}
};

declare function app:xqdocs-name($node as node(), $model as map(*)) {
    element { node-name($node) } {
        attribute data-doc {($model("module")/base-uri() => tokenize("/"))[last()]},
        $model("module")//xqdoc:module/xqdoc:name/text()
    }
};
declare function app:xqdocs-version($node as node(), $model as map(*)) {
    element { node-name($node) } {
        $node/@*[not(starts-with(local-name(), "data-"))],
        $model("module")//xqdoc:module//xqdoc:version/text()
    }
};
declare function app:xqdocs-uri($node as node(), $model as map(*)) {
    element { node-name($node) } {$model("module")//xqdoc:module/xqdoc:uri/text()}
};
declare function app:xqdocs-desc($node as node(), $model as map(*)) {
    let $desc := $model("module")//xqdoc:module//xqdoc:description/text()
    return
        element { node-name($node) } {
            $node/@*[not(starts-with(local-name(), "data-"))],
            $desc
        }
};
declare function app:xqdocs-author($node as node(), $model as map(*)) {
    let $author := string-join($model("module")//xqdoc:module//xqdoc:author/text(), ", ")
    return
        element { node-name($node) } {
            $node/@*[not(starts-with(local-name(), "data-"))],
            $author
        }
};
declare
    %templates:wrap
function app:xqdocs-module($node as node(), $model as map(*)) {
    map {"functions": $model("module")//xqdoc:functions/xqdoc:function}
};
declare function app:xqdocs-functionname($node as node(), $model as map(*)) {
    element { node-name($node) } {$model("function")/xqdoc:name/text()}
};
declare function app:xqdocs-signature($node as node(), $model as map(*)) {
    $model("function")/xqdoc:signature/text()
};
declare function app:xqdocs-functiondesc($node as node(), $model as map(*)) {
    $model("function")/xqdoc:comment/xqdoc:description/text()
};
declare function app:xqdocs-functionparam($node as node(), $model as map(*)) {
    for $param in $model("function")/xqdoc:comment/xqdoc:param
    let $pattern := "\s(\-|–)\s"
    let $name := tokenize($param/text(), $pattern)[1]
    let $desc := string-join(tokenize($param/text(), $pattern)[position() gt 1], " ")
    return
        element { node-name($node) } {
            $node/@*[not(starts-with(local-name(), "data-"))],
            <li class="param">
                {$name}
                {if($desc = "") then () else
                <ul>
Mathias Goebel's avatar
Mathias Goebel committed
446
                    <li>{ $desc }</li>
Mathias Goebel's avatar
Mathias Goebel committed
447
448
449
450
451
452
453
454
455
456
457
                </ul>
                }
            </li>
        }
};
declare function app:xqdocs-functionreturn($node as node(), $model as map(*)) {
element { node-name($node) } {
            $node/@*[not(starts-with(local-name(), "data-"))],
            $model("function")/xqdoc:comment/xqdoc:return/text()
    }
};
Mathias Goebel's avatar
Mathias Goebel committed
458
459
460
461
462
463
464
465
466
467
468
469

declare function app:fork-successfull($node as node(), $model as map(*), $newpw as xs:string?) {
if(not($newpw)) then () else
    <div class="alert alert-success fade in" id="fork-successfull">
    Sucessfully created a new package. You may now set the following parameters in the TextGrid Laboratory:
    <ul>
        <li>URL: <span>{request:get-url()}</span></li>
        <li>Username: <span>{app:target($node, $model)}</span></li>
        <li>Password: <span>{request:get-parameter("newpw", "")}</span></li>
    </ul>
    </div>
};
470
471
472
473
474

declare function local:check-dba()
as xs:boolean {
  sm:id()//*:username => string() => sm:is-dba()
}