3 |
eXist i18n XQuery Module Documentation
4 |
5 |
Introduction
6 |
The eXist i18n module provides an easy to use mechanism to internationalize (i18n) any kind of XML document.
7 |
8 |
9 |
Brief description
10 |
Following features are supported by the eXist i18n module
11 |
12 | - Text translation
13 | - Parameter substitution within text translations
14 | - Usage of internationalized parameters
15 | - Attribute translation
16 |
17 |
See the reference and configuration sections to utilize the i18n extension within your application.
18 |
Reference
19 |
All i18n tags or attributes use a key to retrieve localized text from the i18n catalogue. If no catalogue is available for a chosen language and there is no default catalogue configured then the 'defaultValue' is used. See the description
20 | of the i18n tags / attributes to see where to place the 'defaultValue'.
21 |
22 |
23 |
Language catalogues
24 |
Catalogues are holding the language data and have the following (flat) structure:
25 |
<catalogue xml:lang="de">
26 | <msg key="Welcome">Willkommen</msg>
27 | <msg key="Cancel">Abbrechen</msg>
28 | </catalogue>
29 |
The xml:lang attribute on the catalogue tag defines the language for the catalogue. The attribute value should correspond to ISO 639. The catalogue itself is made up of an flat list of msg tags. Each has an attribute 'key' with a unique value
30 | to identify the translated text. The translated text itself is placed as node value of the msg node.
31 |
32 |
33 |
Text translation
34 |
<i18n:text key="uniqueID">Text in default language</i18n:text>
35 |
The <i18n:text> tag is used to mark text to be translated. The attribute @key is used to find the respective translation
36 | from a specific catalogue. The text
37 | value of the <i18n:text> node can be used as default translation and is further displayed if no catalogue for a selected language exists (and no default language is configured).
38 |
39 |
40 |
Parameter substitution for text
41 |
<i18n:translate> provides parameter substitution for internationalized text. The substitution can either be archived by the numerical order of the i18n:param tags or by character keys. Be aware that numerical and character parameter
42 | substitution must not be mixed within one i18n:translate tag.
43 |
Numerical i18n:param substitution
44 |
Parameters are referenced with numbers in angle brackets. Be aware that the i18n:param count starts with 1.
45 |
<i18n:translate>
46 | <i18n:text key="textWithParam">{2} is prerequisite for {1}.(E.W. Dijkstra)</i18n:text>
47 | <i18n:param>reliability</i18n:param>
48 | <i18n:param>Simplicity</i18n:param>
49 | </i18n:translate>
50 |
The above example resolves to: 'Simplicity is prerequisite for reliability.(E.W. Dijkstra)
51 |
Character i18n:param substitution
52 |
Using character parameter substitution uses keys instead of the numerical order to substitute parameters. The order of the parameters in the i18n:text tag does not matter.
53 |
<i18n:translate>
54 | <i18n:text key="textWithParam">{key1} is prerequisite for {key2}.(E.W. Dijkstra)</i18n:text>
55 | <i18n:param key="key1">Simplicity</i18n:param>
56 | <i18n:param key="key2">reliability</i18n:param>
57 | </i18n:translate>
58 |
The above example resolves to: 'Simplicity is prerequisite for reliability.(E.W. Dijkstra)
59 |
Translation of parameters
60 |
i18n:params can be translated just like i18n:text tags.
61 |
<i18n:translate>
62 | <i18n:text key="textWithParam">{key1} is prerequisite for reliability.(E.W. Dijkstra)</i18n:text>
63 | <i18n:param key="key1">
64 | <i18n:text key="simplicity">Simplicity</i18n:text>
65 | </i18n:param>
66 | </i18n:translate>
67 |
Attribute translation
68 |
Use i18n(key,defaultValue) to translate attributes where the parameter 'defaultValue' is optional.
69 |
<div attr1="i18n(key1)" attr2="i18n(key2,defaultValue1)"/>
70 |
71 |
72 |
Configuration
73 |
74 |
Automatically process XML
75 |
The i18n xquery module is configured via the controller.xql in your application. A typical configuration to process any .html file
76 | in an application would look like this:
77 |
xquery version "1.0";
78 |
79 | import module namespace request="http://exist-db.org/xquery/request";
80 | import module namespace xdb = "http://exist-db.org/xquery/xmldb";
81 |
82 | declare variable $exist:path external;
83 | declare variable $exist:resource external;
84 |
85 | if (ends-with($exist:resource, ".html")) then
86 | <dispatch xmlns="http://exist.sourceforge.net/NS/exist">
87 | <view>
88 | <forward url="/db/i18n/modules/view.xql"/>
89 | </view>
90 | </dispatch>
91 | else
92 | <ignore xmlns="http://exist.sourceforge.net/NS/exist">
93 | <cache-control cache="yes"/>
94 | </ignore>
95 |
Usage of i18n via XQuery calls
96 |
Another variant is to directly call the i18n:process function within the i18n module:
97 |
(:~ i18n.xql:
98 | : i18n processing on the given set of nodes. Call this function from
99 | : within other functions to enable recursive processing of i18n tags.
100 | :
101 | : @param $nodes - the nodes to process
102 | : @param $selectedLang - chosen language, controls which i18n catalogue to load
103 | : @param $pathToCatalogues - path that points to the available i18n catalogues within the eXist database
104 | : @param $defaultLang - language that is used if no i18n catalogue can be found for the chosen language
105 | :)
106 | declare function i18n:process($nodes as node()*,
107 | $selectedLang as xs:string, $pathToCatalogues as xs:string,
108 | $defaultLang as xs:string)
109 |
The following sample shows how this could be done:
110 |
xquery version "1.0";xquery version "1.0";
111 |
112 | import module namespace i18n = "http://exist-db.org/xquery/i18n" at "/db/i18n/modules/i18n.xql";
113 |
114 | declare option exist:serialize "method=xhtml media-type=text/html";
115 | (: get some html containing i18n tags :)
116 | let $content := doc('index.html')/*
117 |
118 | return
119 | i18n:process($content,'de','/db/i18n/data','es')
120 |
Usage
121 |
Select language
122 |
The language to use for internationalization can be chosen in various ways
123 |
124 | - via http parameter 'lang', e.q http://exist/index.html?lang=de
125 | - as xquery parameter $selectedLang if the i18n module is called directly
126 | - xml:lang attribute on the root node of the markup to process
127 |
128 |
Path to i18n catalogues
129 |
The path to the catalogue files can be given as follows:
130 |
131 | - via http parameter 'cataloguesPath'
132 | - as xquery parameter $cataloguesPath if the i18n module is called directly
133 | - using the attribute i18n:catalogues on the root node of the processed markup
134 |
135 |
Default Language
136 |
Chosing a default language (without placing it as inline markup within i18n tags) can be done with:
137 |
138 | - via http parameter 'defaultLang'
139 | - as parameter $defaultLang if the i18n module is called directly
140 | - using the attribute i18n:default-lang on the root node of the processed markup
141 |
142 |
143 |
--------------------------------------------------------------------------------
/examples/special/i18n.html:
--------------------------------------------------------------------------------
1 |
2 |
30 |
JSON Serialization Demo
31 |
eXist supports various serializers for writing out the results of an XQuery script. One of the
32 | most useful is the JSON serializer, which transforms the XML output of an XQuery into JSON.
33 |
This example embeds the jQuery dynatree widget to display the current database collection hierarchy
34 | as a tree. The tree is loaded via an AJAX call to the XQuery json.xql, which returns
35 | data in the JSON format as expected by the dynatree widget.
36 |
37 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/examples/special/json.xql:
--------------------------------------------------------------------------------
1 | xquery version "1.0";
2 |
3 | declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
4 | declare namespace json="http://www.json.org";
5 |
6 | (: Switch to JSON serialization :)
7 | declare option output:method "json";
8 | declare option output:media-type "text/javascript";
9 |
10 | (:~
11 | : Travers the sub collections of the specified root collection.
12 | :
13 | : @param $root the path of the root collection to process
14 | :)
15 | declare function local:sub-collections($root as xs:string) {
16 | let $children := xmldb:get-child-collections($root)
17 | for $child in $children
18 | return
19 |
3 | Templating
4 | The templating module is used throughout this and most of the other applications which ship with
5 | eXist. Its design has one goal: a clean separation of concerns. All views are plain, valid HTML5. They do
6 | not include any XQuery or other executable code. Application code should go into separate XQuery modules
7 | and will be called automagically by the templating framework.
8 | This document provides a number of simple, working examples. For a detailed description of the features of the
9 | templating framework, refer to the documentation.
10 |
11 | The templating module scans the HTML for elements with class attributes following a simple convention
12 | and tries to translate them into XQuery function calls. In the simplest case,
13 | a class attribute which triggers a function call just contains the name of a function in an XQuery library
14 | known to the system. For example:
15 | <div class="ex:hello"></div>
16 | The expanded output of this template call is shown below:
17 |
20 | Here's the code for the templating function. The two parameters are required for any function
21 | to be used by the templating framework:
22 |
23 | - $node
24 | - The HTML node being processed. This is the node with the class attribute which triggered the
25 | templating call.
26 | - $model
27 | - Application data which may have been provided by template functions further up in the document (see below)
28 |
29 |
30 | declare function ex:hello($node as node()*, $model as map(*)) as element(span) {
31 | <span>Hello World!</span>
32 | };
33 | Note: instead of putting template calls into class attributes, you can also use the HTML5-compliant method: specify the template call as well
34 | as any optional parameters (see the section below) in data attributes:
35 |
36 |
37 | <div data-template="ex:hello-world" data-template-language="de"></div>
38 |
39 | Parameter Injection
40 | Very often, you will also need to pass static parameters to the template. This is done by appending
41 | a query string to the template call:
42 | <div class="ex:multiply?n1=5&n2=8"></div>
43 | This calls the following function:
44 | declare function ex:multiply($node as node()*, $model as map(*), $n1 as xs:int, $n2 as xs:int) {
45 | $n1 * $n2
46 | };
47 | Parameters can be static or dynamic. Static parameters are specified in the HTML as in the example above.
48 | Dynamic parameters are read from the HTTP request or HTTP session. The templating framework automatically
49 | tries to determine a value for a given function parameter by looking at those alternatives in turn. If you add
50 | "?n1=2&n2=4" to the location URL of this page in your browser, you'll see how the output below will change:
51 | Again, the expanded output is shown below:
52 |
55 |
56 |
57 | Annotations
58 | By default, the return value of a templating function will replace the HTML node it was called for. This means the
59 | element will be lost unless you copy it. To avoid manually copying the wrapper element, the %templates:wrap
60 | annotation does just that.
61 | There's also an annotation %templates:default
to define a fallback value for a parameter.
62 | declare
63 | %templates:wrap %templates:default("language", "en")
64 | function ex:hello-world($node as node(), $model as map(*), $language as xs:string, $user as xs:string) as xs:string {
65 | switch($language)
66 | case "de" return
67 | "Hallo " || $user
68 | case "it" return
69 | "Ciao " || $user
70 | default return
71 | "Hello " || $user
72 | };
73 | This function could be called with:
74 | <div class="hi ex:hello-world?user=Mary></div>
75 | Please note the extra class "hi", which should color the div. Without %templates:wrap,
76 | it would have been lost. Output below:
77 |
78 |
79 |
80 | Nested Template Calls
81 | Templating calls can be nested, which enables us to build more complex HTML structures. For example:
82 |
83 | <table class="ex:addresses">
84 | <tr class="templates:each?from=addresses&to=address">
85 | <td class="ex:print-name"></td>
86 | <td class="ex:print-street"></td>
87 | <td class="ex:print-city"></td>
88 | </tr>
89 | </table>
90 |
91 | ex:addresses
retrieves a set of addresses from the database and puts them into the $model
.
92 | templates:each
iterates through the model items and processes its inner HTML once for each item.
93 | Finally, the ex:print-name
and friends print out a specific field of the address.
94 | The corresponding XQuery functions are:
95 |
96 | declare
97 | %templates:wrap
98 | function ex:addresses($node as node(), $model as map(*)) as map(*) {
99 | map { "addresses" := collection($config:app-root || "/data/addresses")/address }
100 | };
101 |
102 | declare
103 | %templates:wrap
104 | function ex:print-name($node as node(), $model as map(*)) {
105 | $model("address")/name/string()
106 | };
107 |
108 | declare
109 | %templates:wrap
110 | function ex:print-city($node as node(), $model as map(*)) {
111 | $model("address")/city/string()
112 | };
113 |
114 | declare
115 | %templates:wrap
116 | function ex:print-street($node as node(), $model as map(*)) {
117 | $model("address")/street/string()
118 | };
119 |
120 | Again, the HTML output is displayed below:
121 |
122 |
123 |
124 | |
125 | |
126 | |
127 |
128 |
129 |
130 |
131 |
132 | Use the Templating from other Contexts
133 | The HTML templating module can also be used outside eXist's default app setup. For example, one may want
134 | to call the templating from within a RestXQ module. Because RestXQ uses a separate module for access to
135 | HTTP request parameters, you have to supply one additional lookup function for resolving parameters when
136 | initializing the templating. The code below demonstrates this:
137 |
138 | Demo
139 | (Source code)
140 |
141 |
142 |
143 | Source Links
144 |
145 | - The template functions
146 | used for the examples in this page.
147 | - The main XQuery which triggers the
148 | templating in this app.
149 | - Shakespeare app: the HTML
150 | and the XQuery module implementing
151 | the templating functions.
152 |
153 |
154 |
--------------------------------------------------------------------------------
/examples/tests/shakespeare-tests.xql:
--------------------------------------------------------------------------------
1 | (:~
2 | : Test cases for the Shakespeare search app.
3 | :)
4 | xquery version "1.0";
5 |
6 | module namespace t="http://exist-db.org/apps/demo/shakespeare/tests";
7 |
8 | import module namespace shakes="http://exist-db.org/apps/demo/shakespeare" at
9 | "../web/shakespeare.xql";
10 |
11 | declare namespace test="http://exist-db.org/xquery/xqsuite";
12 |
13 | (:~
14 | : Test translation of query parameters into a full text query.
15 | :)
16 | declare
17 | %test:args("love", "all")
18 | %test:assertEquals("
3 |
Unit Tests
4 |
This page runs a set of unit tests on the Shakespeare example. The tests are contained in a
5 | normal XQuery module, using XQuery annotations for assertions, arguments and more. Click on the source link
6 | to see how tests are defined.
7 |
8 |
11 |
--------------------------------------------------------------------------------
/examples/urlrewriting/bad-page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Error Handler Test
4 |
Submitting the form below should trigger a cast error in the XQuery script
5 | on the server side (it expects a number). You should be redirected to the error handler
6 | page.
7 |
20 |
21 |
25 |
--------------------------------------------------------------------------------
/examples/urlrewriting/index.html:
--------------------------------------------------------------------------------
1 |
2 | Congratulations! You guessed the right number with
40 | {$count} tries. Try again!
,
41 | session:set-attribute("random", ())
42 | )
43 | )
44 | };
45 |
46 | (:~
47 | : Helper function: generate a random integer.
48 | :
49 | : @param $max the generated random will be between 1 and $max
50 | :)
51 | declare function guess:random($max as xs:integer) as xs:integer
52 | {
53 | let $r := ceiling(util:random() * $max) cast as xs:integer
54 | return (
55 | session:set-attribute("random", $r),
56 | session:set-attribute("guesses", 0),
57 | $r
58 | )
59 | };
--------------------------------------------------------------------------------
/examples/web/guess.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Using XQuery for Web Applications
4 |
Guessing Game using HTML templating
5 |
This version of the number guessing game uses HTML templating to build the page.
6 | Compare it to the simple version.
7 |
8 |
Guess a Number
9 |
20 |
21 |
Evaluation result will be shown here
22 |
23 |
24 | Back to examples
25 |
26 |
30 |
--------------------------------------------------------------------------------
/examples/web/guess.xql:
--------------------------------------------------------------------------------
1 | xquery version "1.0";
2 | (: $Id$ :)
3 |
4 | (:~
5 | : Simple XQuery example without HTML templating. The entire app is contained in one file.
6 | :)
7 | import module namespace request="http://exist-db.org/xquery/request";
8 | import module namespace session="http://exist-db.org/xquery/session";
9 | import module namespace util="http://exist-db.org/xquery/util";
10 | import module namespace config="http://exist-db.org/xquery/apps/config" at "../../modules/config.xqm";
11 |
12 | declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
13 |
14 | declare option output:method "html5";
15 | declare option output:media-type "text/html";
16 |
17 | declare function local:random($max as xs:integer)
18 | as empty-sequence()
19 | {
20 | let $r := ceiling(util:random() * $max) cast as xs:integer
21 | return (
22 | session:set-attribute("random", $r),
23 | session:set-attribute("guesses", 0)
24 | )
25 | };
26 |
27 | declare function local:guess($guess as xs:integer,
28 | $rand as xs:integer) as element()
29 | {
30 | let $count := session:get-attribute("guesses") + 1
31 | return (
32 | session:set-attribute("guesses", $count),
33 | if ($guess lt $rand) then
34 | Congratulations! You guessed the right number with
41 | {$count} tries. Try again!
42 | )
43 | };
44 |
45 | declare function local:main() as node()?
46 | {
47 | session:create(),
48 | let $rand := session:get-attribute("random"),
49 | $guess := xs:integer(request:get-parameter("guess", ()))
50 | return
51 | if ($rand) then
52 | if ($guess) then
53 | local:guess($guess, $rand)
54 | else
55 |
3 |
Searching Shakespeare
4 |
Demonstrates basic full text indexing features, search results processing, keywords in context display.
5 |
9 |
35 |
36 |
Found: matches.
37 |
38 |
39 |
64 |
--------------------------------------------------------------------------------
/examples/web/shakespeare.xql:
--------------------------------------------------------------------------------
1 | module namespace shakes="http://exist-db.org/apps/demo/shakespeare";
2 |
3 | import module namespace config="http://exist-db.org/xquery/apps/config" at "../../modules/config.xqm";
4 | import module namespace templates="http://exist-db.org/xquery/templates" at "../../modules/templates.xql";
5 | import module namespace kwic="http://exist-db.org/xquery/kwic"
6 | at "resource:org/exist/xquery/lib/kwic.xql";
7 |
8 | declare variable $shakes:SESSION := "shakespeare:results";
9 |
10 | (:~
11 | : Execute a query and pass the result to nested template functions. This function returns
12 | : a map, not a node. The templating module recognizes this and will merge the map into
13 | : the current model, then continue processing any children of $node.
14 | :
15 | : The annotation %templates:wrap indicates that the current element (in $node) should be preserved.
16 | : The templating module copies the current element and its attributes, before processing
17 | : its children.
18 | :)
19 | declare
20 | %templates:wrap
21 | function shakes:query($node as node()*, $model as map(*), $query as xs:string?, $mode as xs:string?) {
22 | session:create(),
23 | let $hits := shakes:do-query($query, $mode)
24 | let $store := session:set-attribute($shakes:SESSION, $hits)
25 | return
26 | map:entry("hits", $hits)
27 | };
28 |
29 | declare function shakes:do-query($queryStr as xs:string?, $mode as xs:string?) {
30 | let $query := shakes:create-query($queryStr, $mode)
31 | for $hit in collection($config:app-root)//SCENE[ft:query(., $query)]
32 | order by ft:score($hit) descending
33 | return $hit
34 | };
35 |
36 | (:~
37 | Read the last query result from the HTTP session and pass it to nested templates
38 | in the $model parameter.
39 | :)
40 | declare
41 | %templates:wrap
42 | function shakes:from-session($node as node()*, $model as map(*)) {
43 | map:entry("hits", session:get-attribute($shakes:SESSION))
44 | };
45 |
46 | (:~
47 | : Create a span with the number of items in the current search result.
48 | : The annotation %templates:output("wrap") tells the templating module
49 | : to create a new element with the same name and attributes as $node,
50 | : using the return value of the function as its content.
51 | :)
52 | declare
53 | %templates:wrap
54 | function shakes:hit-count($node as node()*, $model as map(*)) {
55 | count($model("hits"))
56 | };
57 |
58 | (:~
59 | : Output the actual search result as a div, using the kwic module to summarize full text matches.
60 | :)
61 | declare
62 | %templates:default("start", 1)
63 | function shakes:show-hits($node as node()*, $model as map(*), $start as xs:int) {
64 | for $hit at $p in subsequence($model("hits"), $start, 10)
65 | let $kwic := kwic:summarize($hit,
3 |
eXist XQuery Features Demo
4 |
This application contains various small demos for particular eXist-db features:
5 |
37 |
External Demos
38 |
Our showcases server hosts or links to a number of applications
39 | whose code is available on github or elsewhere and can be studied. All applications share a similar technology stack and setup, so they
40 | are a good source for inspiration and show what can be done with eXistdb.
41 |
44 |
--------------------------------------------------------------------------------
/modules/cex-trigger.xql:
--------------------------------------------------------------------------------
1 | xquery version "3.0";
2 |
3 | module namespace trigger="http://exist-db.org/xquery/trigger";
4 |
5 | declare namespace xhtml="http://www.w3.org/1999/xhtml";
6 |
7 | import module namespace content="http://exist-db.org/xquery/contentextraction"
8 | at "java:org.exist.contentextraction.xquery.ContentExtractionModule";
9 |
10 | declare function trigger:do-index($fieldName as xs:string, $value as xs:string?, $path as xs:anyURI) {
11 | let $index :=
12 |