XQuery

Fonto is moving to XQuery as its main configuration language. Fonto 7.4 and up contain a preview for XQuery integration, so that we can get feedback early.

Background

In FontoXML, DOM manipulation happens in JavaScript, either in transforms or custom mutations. They are linked together by the operations framework. Over time, we implemented an XPath 3.1 engine which allows more and more transforms to be rewritten using XPath.  FontoXPath is now slowly growing into an XQuery implementation while more XQuery features are being added.

So far, named element constructors and modules have been implemented. These two features can replace even more application-level transforms that only generate a JsonML fragment or custom XPath functions which only get their data from the XML Document.

We are implementing these features because Fonto will, on the long road, move to supporting XQuery as the main XML manipulation API. This will eventually replace the current operations API. We are doing this because a standard like XQuery has a bigger community around it, with resources like Stack overflow. We are releasing a preview version of the XQuery integration in the 7.4 release, so that we can collect feedback as soon as possible. This means that some APIs will change in the future, but they will offer the same or better functionality than they do now and with less effort.

XQuery in operations

XQuery can be executed in Operations using the x__ prefix, just like how XPath could be used in earlier releases. The main new feature is that the keys ending with 'nodeStructure' now assume the XPath/XQuery program to return an array, which is usually JsonML. The platform offers an XQuery function called serialize-as-jsonml, which can be used to serialize a node to jsonML.

Example usage

{
	"my-special-operation": {
		"steps": {
			"type": "operation/append-structure",
			"data": {
				"contextNodeId": "{{contextNodeId}}",
				"childNodeStructure": "x__import module namespace fonto = 'http://www.fontoxml.com/functions'; <someNewElement with='attributes'>And some text</someElement> => fonto:serialize-as-jsonml()"
			}
		}
	}
}

Modules can also be imported for a whole operations.json file by adding a __moduleImports__ key on the root level of the file:

{
	"__moduleImports__": {
		"fonto": "http://www.fontoxml.com/functions"
	},
	"my-special-operation": {
		"steps": {
			"type": "operation/append-structure",
			"data": {
				"contextNodeId": "{{contextNodeId}}",
				"childNodeStructure": "x__<someNewElement with='attributes'>And some text</someElement> => fonto:serialize-as-jsonml()"
			}
		}
	}
}

Defining modules

To make it easier to write large XPath or XQuery programs, they can be written in XQuery module files and be imported wherever XQuery programs or XPaths are evaluated. XQuery modules should have .xqm as file extension. Any file with the .xqm extension in the src/ directory of your package will be automatically registered and loaded.

jsonml.xqm
module namespace fonto = "http://www.fontoxml.com/functions";

(: This is the implementation of fonto:serialize-as-jsonml in the platform. It recursively transforms DOM nodes to their JSONML notation. :)

declare %public function fonto:serialize-as-jsonml ($node as node()) as item() {
	if ($node/self::element()) then
		array {
			name($node),
			map:merge($node/attribute::node()/map:entry(name(.), string(.))),
			$node/child::node()/fonto:serialize-as-jsonml(.)
		}
	else if ($node/self::text()) then string($node)
	else if ($node/self::processing-instruction()) then ["?" || name($node), string($node)]
	else if ($node/self::comment()) then ["!", string($node)]
	else ()
};

Creating dynamic structures

One major use-case of using XQuery from operations is to generate a structure based on parameters from stepdata. Some of these uses could be addressed using the gap feature of Stencils, but some could not. A good example is creating a table of set dimensions:

module namespace docbook-editor = "http://www.fontoxml.com/editors/docbook";

(: Generate an XHTML table of the given dimensions :)

declare %public function docbook-editor:make-a-table ($height, $width) as element() {
	<table>{
		for $i in (1 to $height)
		return <tr>{
			(: This outputs a number of cells each containing the coordinates for the cell: "Cell at (1, 2)" :)
   			(1 to $width)!(<td><p>Cell at ({$i},{.})</p></td>)
    	}</tr>
	}</table>
};

And in an operation:

{
	"__moduleImports__": {
		"docbook-editor": "http://www.fontoxml.com/editors/docbook",
		"fonto": "http://www.fontoxml.com/functions"
	},
	"my-special-operation": {
		"steps": {
			"type": "operation/append-structure",
			"data": {
				"contextNodeId": "{{contextNodeId}}",
				"childNodeStructure": "x__docbook-editor:make-a-table($data('height'), $data('width')) => fonto:serialize-as-jsonml()"
			}
		}
	}
}

Code reuse

Because it is now possible to define XQuery modules on the application-level, a number of custom XPath functions can be rewritten in XPath. Things like numbering algorithms that are defined in JavaScript can usually be written in XQuery in a more concise way:

module namespace docbook-editor = "http://www.fontoxml.com/editors/docbook";

(: Get the formatted number for the current section :)
declare %public function local:section-number ($node as element()) as xs:string {
	(: Recurse to the parent and add a "." separator, then generate the number for the current level and append that :)
	$node/parent::*/(local:section-number(.) || ".") || $node/preceding-sibling::section => count() + 1
};

This function can be used from a custom widget as described in Create a custom widget:

import readOnlyBlueprint from 'fontoxml-blueprints/src/readOnlyBlueprint.js';
import evaluateXPathToString from 'fontoxml-selectors/src/evaluateXPathToString.js';

export default function createCustomWidget() {
    return function(sourceNode) {
        return [
            'cv-attribute-label-widget',
            evaluateXPathToString('d:section-number(.)', sourceNode, readOnlyBlueprint, null, {
                moduleImports: {
                    d: 'http://www.fontoxml.com/editors/docbook'
                }
            })
        ];
    };
}

On Namespaces

XQuery modules register functions into a namespace. All functions that are part of the Fonto SDK reside in the http://www.fontoxml.com/functions namespace uri, Fonto also binds the prefix fonto to this namespace uri. Functions that are defined on the application level should choose a different namespace uri. Refer to the documentation on Handling namespaces for more information on how to deal with namespaces.

XQuery Update Facility

Starting from Fonto 7.5, we include a preview of how XQuery Update Facility will be integrated. This can be used by calling the execute-update-script operation.