Transformers - xokomola/origami GitHub Wiki

Node Transformers

Use Mu node transformers to modify Mu data structures. Origami provides many node transformer functions for doing things like: append or insert new elements, modify attributes, add handlers to nodes etc. Together they provide a toolbox for building Mu document transformations. These can be used as handler functions or as part of regular XQuery code.

These functions can be subdivided into the following categories:

  • Node information functions get information about a Mu document node.
  • Element transformers modify Mu element nodes or take them apart.
  • Attribute transformers make setting, removing or changing node attributes easy.
  • Flow control functions provide means to make conditionally evaluate transforms or to compose transformers into a single function.
  • Handler related functions for manipulating attribute and element handlers.
  • Tree manipulation provides functions that help walk Mu document trees and transform them.
  • Rule-based transformations for more complex transformations using XSLT or other rule-based transformations.
  • Utility functions a grab bag of functions that don't fit in the other categories but help with things like

The sections below will show examples for some of each of these categories. For more information look up the function in the function reference page.

Node information

o:attributes, o:attrs, o:children, o:has-attr, o:has-attrs, o:has-children, o:is-element, o:is-text-node, o:size, o:tag.

The o:has- and o:is- functions are used as predicates and evaluate to true() or false() depending on the item passed in. The other functions return different parts of the passed in item.

o:tag(['x', 10, 20])

Returns "x".

o:has-attrs(['x', 10, 20])

Returns false() because the element does not have attributes.

o:attributes(['x', 10, 20])

Returns the empty sequence () because the element does not have attributes.

o:attrs(['x', 10, 20])

Returns the empty map map {} because the element does not have attributes. If the argument is not an element it will raise an error. In many situations it's easier to always get a map so we can ask for an attribute value like this o:attrs($node)?y and even if $node doesn't have any attributes this expression will still succeed and return an empty sequence.

o:has-attr(['x', map { 'y': 0 }, 10, 20], 'y')

Returns true() because the element has an attribute named y. Note that to get the actual attribute value you have to use o:attrs and query the resulting attribute map.

o:children(['x', map { 'y': 0 }, 10, 20])

Returns (10,20) which are all the children of the x element. Note that attributes are not considered child nodes of an element.

o:size(['x', map { 'y': 0 }, 10, 20])

Returns 2 because the element has exactly two child nodes.

Element transformers

o:after, o:before, o:head, o:insert, o:insert-after, o:insert-before, o:ntext, o:rename, o:replace, o:tail, o:text, o:unwrap, o:wrap.

Most of these functions can help you do small transformations on a Mu node. The are often useful as handlers, part of handlers or normal functions.

In the Mu section I already showed an example of a function that generates a Mu data structure.

declare function my:list($items)
{
	o:for-each($items, o:wrap(['li']))
	=> o:wrap(['ul'])
}

This function combines several simple node transformers to produce a list element for a sequence of items.

You can visualize this function as a small pipeline. First o:for-each, a flow control function, is used to wrap every item from the $items sequence separately in an li element. The resulting nodes are then wrapped in an ul element. The steps in the pipeline are connected using the array operator (=>).

o:xml(my:list(('A','B','C')))
<ul>
	<li>A</li>
	<li>B</li>
	<li>C</li>
</ul>

Most functions presented here can be used with the arrow operator and that is because they accept Mu nodes as their first argument. To write the same function without the arrow operator shows where this first argument to these functions goes.

declare function my:list($items)
{
	o:wrap(
		o:for-each($items, o:wrap(['li'])),
		['ul']
	)
}

The following examples will keep on using the arrow operator.

Attribute transformers

o:add-attr-token, o:add-class, o:advise-attr, o:advise-attrs, o:remove-attr-token, o:remove-attrs, o:remove-class, o:set-attr, o:set-attrs,

['x', map { 'y': 0 }]
=> o:set-attr('y',10)
=> o:set-attr('z',20)

Using o:set-attr you can add attributes and also modify existing attributes.

['x', map { 'y': 10, 'z': 20 }]

To set multiple attributes you can also use o:set-attrs.

['x', map { 'y': 0 }]
=> o:set-attrs(map { 'y': 10, 'z': 20 })

Remove attributes using o:remove-attr or o:remove-attrs.

['x', map { 'y': 0 }]
=> o:remove-attrs(('y', 'z'))
['x']

Note that when no attribute remains the attribute map is removed as well.

To add to token valued attributes use o:add-attr-token or o:add-class.

['p']
=> o:add-class(('note','danger'))
['p', map { 'class': 'note danger' }]

The o:add-attr-token function is a more generic version of the above. Use it to modify other token value attributes.

['task', map { 'otherprops': 'important todo' }]
=> o:set-attr-token('otherprops', 'done')
=> o:remove-attr-token('otherprops', 'todo')
['task', map { 'otherprops': 'important done' }]

Finally there's also a way to set an attribute value only if it's not already set: o:advise-attr and o:advise-attrs.

['x', map { 'y': 0 }]
=> o:advise-attrs(map { 'y': 10, 'z': 20 })
['x', map { 'y': 0, 'z': 20 }]

Flow control

o:choose, o:comp, o:filter, o:for-each, o:map, o:map1, o:reduce, o:repeat, o:sort.

These functions provide various ways to make the node transformations smarter.

Composing functions

Suppose I have a pipeline like the following.

['p', 'Hello Origami!']
=> o:add-class('shout')
=> o:insert(function($n) { upper-case(o:text($n)) }

First it adds a class and then it replaces the element content with an upper-cased version of the text content.

['p', map { 'class': 'shout' }, "HELLO ORIGAMI!"]

Rewriting this into a re-usable function may look like this.

let $shout :=
	function($node) {
		o:insert(
			o:add-class($node, 'shout'),
			upper-case(o:text($node))
		)
	}
return
	$shout(['p', 'Hello Origami!'])

Using function composition with o:comp I would write it like this.

let $shout :=
	o:comp((
		o:add-class('shout'),
		o:insert(function($n) { upper-case(o:text($n)) })
	))
return
	$shout(['p', 'Hello Origami!'])

With o:comp you provide a sequence of functions and it will return a new function that takes one argument and calls the nested functions passing the input argument to the first function, the result to the second etc.

Tree manipulation

TODO: review these functions. Pick out values with tree-seq then map over it. Using depth of nodes (tree-seq-depth). Also see o:select as this can be used as well.

o:collect, o:leafs, o:postwalk, o:prewalk, o:tree-seq.

A Mu data structure is a tree. With XQuery it is quite easy to query XML using XPath. But even in XQuery it is not always very easy to handle deep tree structures. In many cases you will have to resort to recursion.

Some tree manipulation patterns can be made easier to use when the core of such a mechanism is provided and the developer only has to provide the varying bits.

For a deep dive into algorithmic tree manipulation via the Clojure language see the IBM developerWorks article on Tree visitors in Clojure.

Flattening a tree

The o:leafs function picks out all the non-element leafs from a document effectively flattening it and removing all elements.

Collecting values from a tree

Processing a tree collecting values along the way using o:tree-seq. Which essentially does a depth-first walk over a tree structure but it can be used to walk other tree representations as well.

It only requires two bits of information in order to drive the visitor. A visitor needs to know where a branch occurs and what to do with each element node.

This is the identity function using o:tree-seq.

o:tree-seq($nodes, o:is-element(?), o:identity(?))

The first argument has the nodes to visit. The second argument tells the visitor on which element to branch, and finally the function to call on each element node.

Tree walkers

Rule-based transformations

o:select, o:transform, o:transformer, o:xslt.

Handler related functions

o:has-handler, o:is-handler, o:remove-attr-handlers, o:remove-handler, o:set-attr-handlers, o:set-handler.

Utility functions

o:conj, o:constantly o:copy, o:identity, o:seq.

TODO o:cycle?

TODO o:xf-builder? with o:xslt-builder

⚠️ **GitHub.com Fallback** ⚠️