Mu - xokomola/origami GitHub Wiki
Origami manipulates documents that are based on arrays and maps. Mu documents are a flavor of Micro-XML. Mu is the name of a letter in the Greek alphabet: μ.
Summary of this section:
- What is a Mu data structure?
-
o:doc
creates a Mu data structure from XML -
o:xml
creates XML from a Mu data structure - How XML namespaces are handled in Mu
- Embedding behaviour in a Mu data structure and using
o:apply
to invoke it
The Mu data structure can best be explained by a few simple examples.
The following XML fragment
<p>Hello, <i class="name">World</i>!</p>
Can be written as a Mu data structure using XQuery's literal syntax for data and maps.
['p', 'Hello, ', ['i', map { 'class': 'name' }, 'World'], '!']
A Mu element consists of an array. The first item of this array is called the tag. The attributes of an element are written as a map. All other items are the child nodes. The attribute map is not required.
An empty element:
<p/>
['p']
An empty element with attributes:
<img src="origami.jpg" alt="Origami"/>
['img', map { 'src': 'origami.jpg', 'alt': 'Origami'}]
Mu does not support comments, processing instructions or even have a real concept of namespaces. For Origami names are just plain strings. However it does provide some tools to deal with namespaced XML.
You can use the full power of XQuery as a language to generate and build these data structures from smaller parts and functions. Origami is a library for helping with that.
You may ask what the benefits are. Mu documents are not XML and thus some of the power of XQuery and XPath is lost by using them.
True, you cannot query Mu data structures with the same XPath expressions that are used on XML nodes. You also cannot store them, as-is, on the filesystem or in an XML database.
But let's see what is gained by using a plain data structure.
Mu data structures and Origami templating code uses regular XQuery code and works just fine with XML. Also, Origami is a templating library and, as any other templating library, it is applied only in some parts of your code.
This data structure is build up of the simple generic data types arrays and maps. The result is a lightweight data structure capable of capturing, similar as XML, tree structured data.
But more importantly, being a native data structure, it can contain other items such as anonymous functions.
['p', 'Hello, ',
['i', function($data) { upper-case($data) }],
'!'
]
The above Mu data structure contains an anonymous function, a
handler. In rest this function is just a piece of the data
structure. To make it do something we need to apply some data to
it. Note that a handler always takes two arguments, the first is the
Mu node in which the handler appears and the second is the data passed
into the mu structure using o:apply
.
o:apply($mu, 'Origami')
['p', 'Hello, ', ['i', 'ORIGAMI'], '!']
The Mu fragment with the handler can be composed with other Mu fragments. Origami provides the tools to solve templating problems using Mu data structures.
You create Mu documents in various ways.
Use literal syntax for embedding Mu structures in code.
['ul',
for $item in $items
return
['li', $item]
]
Write functions that produce Mu structures.
declare function my:list($items)
{
['ul',
for $item in $items
return
['li', $item]
]
};
Convert an XML structure into a Mu document.
let $xml :=
<ul>
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
</ul>
return
o:doc($xml)
Using the o:doc
function has the advantage that it will always
produce a wellformed Mu document.
Origami offers a host of helper functions to build up Mu documents. Using some of these functions you could rewrite the function example above as:
declare function my:list($items)
{
o:for-each($items, o:wrap(['li']))
=> o:wrap(['ul'])
}
More about these helper functions can be found in the Node Transformers section.
The o:doc
function can also make use of Builders to
control how names are translated from XML to Mu or to associate
handlers to specific nodes using XPath selectors. But this is more of
an advanced topic so it's not discussed here.
One of the advantages of generic data structures is that they
can contain behaviour in the form of function items. These functions
can take external data and generate new content in response. This is
the prime feature of any templating engine and it is often called
"rendering". In Origami the function that does this is called
o:apply
.
In a Mu data structure these functions are called handlers and they can appear in different positions.
-
Content handlers are located in the position of the tag of an element node or as a content node.
-
Node handlers are located on the attribute map. An element handler is a function item as the value of the
@
key.
Each of these will be called with either the node contents or the current node and the external data.
TODO: two things could change o:apply
and which I experiment with in
o:render
. a) automatically apply the result of a handler or let the
handler do this and b) use a single argument function as the data
arguments for every handler (using a map with keys like content
,
attrs
and data
).
Every handler should be a function item that takes either none, one or
two arguments. What is passed into the handler depends on this arity
(0, 1 or 2) and the position of the handler in the Mu data
structure. The arguments that may be passed are the current Mu node
itself, the Mu node contents or the data passed in via o:apply
.
Content handlers
-
#0
no arguments -
#1
the external data -
#2
the content of the handler node (may be empty), and the external data
Node handlers
-
#0
no arguments -
#1
the current node -
#2
the current node, and the external data
Other arities of handlers will result in an error when used with
o:apply
.
let $span := function($name) {
['span', map { 'class': 'name' }, $name]
}
let $template :=
['div', 'Hello ', [$span#1, 'Name'], '!']
return
o:apply($template, 'Origami')
let $name := function($name) { $name }
let $template :=
['div', 'Hello ', ['span', map { 'class': 'name' }, $name#1], '!']
return
o:apply($template, 'Origami')
This is an example of an element node handler.
let $span := function($node, $name) {
$node => o:insert($name)
}
let $template :=
['div', 'Hello ', ['span', map { '@': $span#2 }, 'Name'], '!']
return
o:apply($template, 'Origami')
This can be written a little bit more succinctly as:
let $span := o:insert(function($name) { $name })
let $template :=
['div', 'Hello ', ['span', map { '@': $span#1 }, 'Name'], '!']
return
o:apply($template, 'Origami')
Attributes can have handlers too.
let $span := function($node, $name) {
$node
=> o:insert($name)
}
let $width := function($node, $name) {
string-length($name)
}
let $template :=
['div', 'Hello ', ['span', map { 'width': $width#2, '@': $span#2 }, 'Name'], '!']
return
o:apply($template, 'Origami')
Attribute node handlers can often be replaced by element node
handlers that set attribute values (using o:set-attr
or
o:set-attrs
).
let $span := function($node, $name) {
$node
=> o:set-attr('width', string-length($name))
=> o:insert($name)
}
let $template :=
['div', 'Hello ', ['span', map { '@': $span#2 }, 'Name'], '!']
return
o:apply($template, 'Origami')
These are all the ways that dynamic content or behaviour can be embedded inside a Mu data structure.
At some point you want to create XML from a Mu data structure. To write it to disk, store it in a database or to return it to a web service client or browser.
Use the o:xml
function to construct XML nodes from Mu.
o:xml(['p', 'Hello, World!'])
This returns
<p>Hello World!</p>
Some notes about the conversion.
Handlers will not be evaluated - named functions will be rendered using their QName.
o:xml([o:doc#1, 'a document'])
<o:doc xmlns="http://xokomola.com/xquery/origami">a document</o:doc>
Anonymous functions will be removed and only their child nodes will be rendered. The attributes will be removed too as this would leave an unwellformed document.
let $anon-fn := function($n) { $n }
return
o:xml([$anon-fn, map { 'x': 10 }, 'a document'])
'a document'
QNames will be used as-is. No translation necessary.
let $div := QName('http://www.w3.org/1999/xhtml','div')
return
o:xml([$div, 'an HTML div element'])
<div xmlns="http://www.w3.org/1999/xhtml">an HTML div element</div>
- Strings will pass through a standard QName resolution function (unless it is overridden using a custom resolver in a builder).
When a XML document is converted to a Mu document with the o:doc
function the namespace URI bindings will be dropped and only the node
name is used to create a simple string such as "p"
or "svg:rect"
.
Origami provides some help to preserve namespace URIs but they are dealt with separately from the Mu documents.
Namespace URIs are needed when a Mu document containing namespace
prefixed tag names is converted to XML. This happens when the o:xml
function is used but also when, for example inside a handler, the
o:xslt
node transformer is used.
The o:xml
function takes a second argument which can be a
Builder or a namespace resolver. A namespace resolver
consists of a map or function item which can take a string (possibly
prefixed) and which translates it into a QName.
The namespace map can be created explicitly in code or it can be created from the XML fragment itself.
let $xml :=
<o:doc>
<p>Hello Origami</p>
</o:doc>
return
o:doc($xml)
['o:doc', ['p', 'Hello Origami']]
To get the namespace map to convert this Mu document back to XML use
the above XML itself and provide it to the o:ns
function.
let $xml :=
<o:doc>
<p>Hello Origami</p>
</o:doc>
return
o:ns($xml)
map {
'': xs:anyURI(''),
xs:NCName('o'): xs:anyURI('http://xokomola.com/xquery/origami')
}
This map can be used to resolve strings to namespace URIs. The o:xml
function can use such a map to translate a string like "o:doc"
to
the QName needed to reconstruct the XML element <o:doc xmlns:o="http://xokomola.com/xquery/origami"/>
.
Without such a map it will not know how to translate prefixed names and it will generate it's own namespace URIs.
o:xml(['o:doc', ['p', 'Hello Origami']])
<o:doc xmlns:o="urn:x-prefix:o">
<p>Hello Origami</p>
</o:doc>
Provide a map with the namespace URIs and using the prefixes as keys to help the conversion.
o:xml(
['o:doc', ['p', 'Hello Origami']],
map { 'o': 'http://xokomola.com/xquery/origami' }
)
<o:doc xmlns:o="http://xokomola.com/xquery/origami">
<p>Hello Origami</p>
</o:doc>
The namespace resolution process can be customized using Builders, see XML Namespaces for more information.
Being generic data structures there are many ways to transform
them. For complex transformations it may be best to use XSLT (using
o:xslt
for example) but for simpler transformations you may want to
use algorithmic transformation. Some interesting techniques are:
Origami provides some functions for tree walking (see Advanced Subjects) alongside a host of other basic Node Transformers.