Create a numbering for nodes

This guide describes how to create numbering for nodes. It also has a few example cases in which using node numbering can be useful. This guide focuses on using the addReducer function and XQuery modules.

Basic steps

These are the basic steps for adding numbering for nodes.

Step 1: Create an XQuery module to count the nodes

Create an XQuery module in the src directory of a package. The module will be registered automatically when placed in an src directory.

The XQuery module is used to declare a function that counts the nodes that we want to number. This function contains the logic used for counting the nodes. The arguments and the return value of this function are explained in the addReducer documentation.

myNodeNumberingCallback.xqm

XQuery

xquery version "3.0";
 
(: The namespace URI that is used in other places of the editor :)
module namespace app="http://www.fontoxml.com/app";

declare function app:myNodeNumberingCallback(
	$previousAccumulator as item()*,
	$relType as xs:string,
	$node as node(),
	$isUnloaded as xs:boolean
) as item()* {
	(: Compute a new accumulator value for $node :)
};

Step 2: Add the reducer

Add the reducer in a configureSxModule.js file. More details about this function are available in the addReducer documentation.

configureSxModule.js

JavaScript

import addReducer from 'fontoxml-indices/src/addReducer.js';

export default function configureSxModule(sxModule) {
	addReducer(
		'http://www.fontoxml.com/app',
		'myReducerForNodeNumbering',
		// In this case we decided to count the "mynodename" elements. We could also have counted
		// multiple elements like this: 'self::mynodename or self::othernodename'. This will result
		// in the same numbering but pass both the mynodename and othernodename elements as $node.
		'self::mynodename',
		'http://www.fontoxml.com/app',
		'myNodeNumberingCallback'
	);

	// my other codes...
}

Step 3: Use the reducer in a widget

The added reducer function can be used in the same way registered custom XPath functions are used. Pass the current hierarchy node id and the node to the reducer function. It will return a number or a sequence of numbers. The result can be used in a title or label.

configureSxModule.js

JavaScript

import addReducer from 'fontoxml-indices/src/addReducer.js';
import configureAsFrame from 'fontoxml-families/src/configureAsFrame.js';
import createLabelQueryWidget from 'fontoxml-families/src/createLabelQueryWidget.js';

export default function configureSxModule(sxModule) {
	addReducer(
		'http://www.fontoxml.com/app',
		'myReducerForNodeNumbering',
		// In this case we decided to count the "mynodename" elements. We could also have counted
		// multiple elements like this: 'self::mynodename or self::othernodename'. This will result
		// in the same numbering but pass both the mynodename and othernodename elements as $node.
		'self::mynodename',
		'http://www.fontoxml.com/app',
		'myNodeNumberingCallback'
	);

	configureAsFrame(sxModule, 'self::mynodename', t('My Title'), {
		// Here we can use the result of the reducer function.
		// If the result is "1" and the title is "My Title" for a node, the titleQuery will returns "1 - My Title".
		blockHeaderLeft: [
			createLabelQueryWidget(
				`import module namespace app = "http://www.fontoxml.com/app";
				app:myReducerForNodeNumbering(fonto:current-hierarchy-node-id(), .)`
			)
		]
	});

	// my other codes...
}

Example: Flat numbering

One common use for reducers is the numbering of images, tables, and other objects. This example will show how to show a figure element's number in a widget. The number shown will be the number of the previous figure element + 1. This even works across different documents.

A screenshot showing automatic numbering widgets in the top right of several figures.

XQuery module

In this example we use $relType to determine whether the current item is the first item. If it is the first item, $previousAccumulator contains an empty sequence. If it is not the first item, we can use the previous value stored in $previousAccumulator, add 1 to it, and return the result.

figNumberingCallback.xqm

XQuery

xquery version "3.0";

module namespace app = "http://www.fontoxml.com/app";

declare %public function app:figNumberingCallback(
	$previousAccumulator as item()*,
	$relType as xs:string,
	$node as node(),
	$isUnloaded as xs:boolean
) as item()* {
	(: Compute a new accumulator value for $node :)
	if ($relType eq "first") then
		(: First figure :)
		1
	else
		(: Increment the counter :)
		$previousAccumulator + 1
};

addReducer

The reducer is added for fig nodes and uses the callback defined in the XQuery module.

configureSxModule.js

JavaScript

import addReducer from 'fontoxml-indices/src/addReducer.js';
import configureAsFrame from 'fontoxml-families/src/configureAsFrame.js';
import createLabelQueryWidget from 'fontoxml-families/src/createLabelQueryWidget.js';

export default function configureSxModule(sxModule) {
	addReducer(
		'http://www.fontoxml.com/app',
		'numberFigs',
		'self::fig',
		'http://www.fontoxml.com/app',
		'figNumberingCallback'
	);

	// figure
	configureAsFrame(sxModule, 'self::fig', 'figure', {
		blockHeaderLeft: [
			createLabelQueryWidget(
				`import module namespace app = "http://www.fontoxml.com/app";
				app:numberFigs(fonto:current-hierarchy-node-id(), .)`
			)
		]
		// other options...
	});
}

Example: Hierarchical numbering

Another common use for reducers is numbering documents in the outline view. This numbering needs to be updated automatically when the order of documents is changed.

A screenshot showing automatic numbering title queries in the structure view.
A screenshot showing the automatic numbered title queries in the structure view updated.

This example will run the callback function defined in the XQuery module for each topicref node. It will produce a hierarchical numbering for the documents, depending on the nesting of the topicref node. The first document will get number 1, its first child will get number 1.1, its sibling will get number 1.2, and so on.

XQuery module

This example uses the $relType to determine whether the current topicref node is a child of the previous topicref node. If it is, it will add a new "start" value to the return sequence. We can do this since nested sequences will always be flattened. Refer to our XPath and XQuery documentation for more information on how sequences work and behave.

topicNumberingCallback.xqm

XQuery

xquery version "3.0";

module namespace app = "http://www.fontoxml.com/app";

declare %public function app:topicNumberingCallback(
	$previousAccumulator as item()*,
	$relType as xs:string,
	$node as node(),
	$isUnloaded as xs:boolean
) as item()* {
	(: Compute a new accumulator value for $node :)
	if ($relType eq "first") then
		(: First item in the hierarchy :)
		1
	else
		if ($relType eq "parent") then
			(: No preceding siblings under this parent, append our numbering sub-level :)
			($previousAccumulator, 1)
		else
			(: Increment the counter for our preceding sibling :)
			$previousAccumulator!(if (position() = last()) then . + 1 else .)
};

addReducer

If you want to count documents (e.g. "topic" in DITA), we strongly recommend you to use document references (e.g. "topicref" in DITA) as selector. Allowing you to count documents without having to loading them.

configureSxModule.js

JavaScript

import addReducer from 'fontoxml-indices/src/addReducer.js';
import configureAsStructureViewItem from 'fontoxml-families/src/configureAsStructureViewItem.js';

export default function configureSxModule(sxModule) {
	addReducer(
		'http://www.fontoxml.com/app',
		'numberTopics',
		// In this case we decided to count the topicref elements. We could also have used
		// self::topic. This will result in the same numbering but pass the topic element as $node.
		'self::topicref',
		'http://www.fontoxml.com/app',
		'topicNumberingCallback'
	);

	// topic svk
	configureAsStructureViewItem(sxModule, 'self::topic', {
		icon: 'file-text-o',
		titleQuery:
			`import module namespace app = "http://www.fontoxml.com/app";
			 import module namespace fonto = "http://www.fontoxml.com/functions";
			 string-join(app:numberTopics(fonto:current-hierarchy-node-id(), .), ".") || " - " || string-join(./title ! fonto:curated-text-in-node(.))`
	});
}

Example: Numbering Elements In Case Of Unloaded Documents

Some documents may not yet be loaded when JIT loading is enabled in an editor. Documents in an error state are also considered not loaded. This can influence the numbering of elements when an unloaded document contains those elements.

To make the numbering of these elements possible, the CMS can fill in the gaps by keeping track of the number of those elements in a document. The CMS can then save these numbers in an attribute on, for example, DITA topicref element.

XQuery module

The callback function uses the $isUnloaded argument to determine which source to use for the number of elements. This source can be either an attribute set on the topicref element when the document is not loaded or the actual number of fig elements in a document.

figNumberingCallback.xqm

XQuery

xquery version "3.0";

module namespace app = "http://www.fontoxml.com/app";

declare %public function app:figNumberingCallback(
	$previousAccumulator as item()*,
	$relType as xs:string,
	$node as node(),
	$isUnloaded as xs:boolean
) as item()* {
	let $previous := if ($relType eq "first") then 0 else $previousAccumulator
    return
    (: if $isUnloaded is true, the document is unloaded and we just have the topicref as $node :)
    if ($isUnloaded) then
        if ($node[@unloaded-count]) then
            $previous + number($node/@unloaded-count)
        else
            $previous
    else
        $previous + 1
};

addReducer

configureSxModule.js

JavaScript

import addReducer from 'fontoxml-indices/src/addReducer.js';
import configureAsFrame from 'fontoxml-families/src/configureAsFrame.js';
import createLabelQueryWidget from 'fontoxml-families/src/createLabelQueryWidget.js';

export default function configureSxModule(sxModule) {
	addReducer(
		'http://www.fontoxml.com/app',
		'numberFigs',
		'self::fig',
		'http://www.fontoxml.com/app',
		'figNumberingCallback'
	);

	// figure
	configureAsFrame(sxModule, 'self::fig', 'figure', {
		blockHeaderLeft: [
			createLabelQueryWidget(
				`import module namespace app = "http://www.fontoxml.com/app";
				app:numberFigs(fonto:current-hierarchy-node-id(), .)`
			)
		],
		// other options...
	});
}