X Query
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.
We're slowly moving all XPath and XQuery documentation to a single concept page. Make sure to check it out!
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 Json
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.
X Query 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 Jsonserialize-as-jsonml
, which can be used to serialize a node to jsonML.
Example usage
JSON
{
"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</someNewElement> => fonto:serialize-as-jsonml()"
}
}
}
}
Modules can also be imported for a whole operations.json
file by adding a __module
key on the root level of the file. This is useful when an operation uses a lot of different functions in different namespaces:
JSON
{
"__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
XQuery
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:
XQuery
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:
JSON
{
"__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
XQuery
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:
createCustomWidget.js
JavaScript
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('import module namespace d= 'http://www.fontoxml.com/editors/docbook'; d:section-number(.)', sourceNode, readOnlyBlueprint)
];
};
}
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.
X Query 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. The 7.9 release brings a new improvement to XQuery Update facility: updating functions. Besides that you can set selection in these updating functions by using selection PIs. It is now possible to write the actual implementation of an XQuery update script in a separate module:
updating-functions.xqm
XQuery
module namespace app = "http://www.fontoxml.com/editor";
declare %public %updating function app:append-note ($container as element(), $title as xs:string) {
insert node <note><title>{$title}</title></note> as last into $container
};
(: update or insert an element :)
declare %private %updating function app:upsert ($container as element(), $newElement as element()) {
let $existingContainer := $container/child::*[name() = name($newElement)])
return
if ($existingContainer) then
replace $existingContainer with $newElement
else
insert node $newElement as last into $container
};
declare %public %updating function app:upsert-metadata ($metadata-container as element(), $title as xs:string, $publication-date as xs:string) {
app:upsert($metadata-container, <publication-date>{$publication-date}</publication-date>),
app:upsert($metadata-container, <title>{$title}</title>),
};
These functions can be imported and evaluated in Operations like this:
operations.json
JSON
{
"my-special-operation": {
"steps": {
"type": "operation/execute-update-script",
"data": {
"expression":
"import module namespace app='http://www.fontoxml.com/editor'; app:upsert-metadata($data('contextNode'), $data('title'), $data('publicationDate'))"
}
}
}
}