Create a numbering for nodes

This guide describes the steps to create a numbering for nodes and then several cases where this can be useful. In these cases we want to number the nodes across documents which means we need dependencies on the structure. For this we can use the addReducer function.

We can create a numbering for nodes in three steps:

Step 1: Create an XQuery module to count the nodes

Create an XQuery module in the "src" folder of a package for our numbering callback. The editor will register it automatically.

In this function, define the rules on how to count the nodes according to the relationship between the nodes and the previously counted node. The arguments and the result of the callback is explained in the addReducer documentation.

myNodeNumberingCallback.xqm
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(),
	$isSourceNode 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 is available in the addReducer documentation.

configureSxModule.js
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 your title query

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
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...
}


Examples

Flat Numbering

One case is the numbering of images and or tables. In this case we want to show the number of the current image in the top right of the figure. For this example we will count figures so they will continue numbering 1, 2, 3, etc across documents.

In this example, the $relType in the callback function does not matter. Because we do not count hierarchically, we only need to continue counting.
You can follow this implementation to create flat numbering:

  • Numbering callback:
figNumberingCallback.xqm
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(),
	$isSourceNode as xs:boolean
) as item()* {
	(: Compute a new accumulator value for $node :)
	if ($relType eq "first") then
		(: First table :)
		1
	else
		(: Increment the counter :)
		$previousAccumulator + 1
};
  • configureSxModule.js:
configureSxModule.js
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'
	);

	// table
	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...
	});
}


Hierarchical Numbering

Another case of numbering we have seen is numbering in the structure view which needs to update when the structure is changed.

This example will run the callback function for each topicref and create a hierarchical numbering. I.e. 1 for the first, 1.1 for its first child 1.2. for its second child, 2 for its following sibling and so on.

  • Numbering callback:
topicNumberingCallback.xqm
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(),
	$isSourceNode 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 .)
};


  • configureSxModule.js:

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 depending on loading them.


configureSxModule.js
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";
			string-join(app:numberTopics(fonto:current-hierarchy-node-id(), .), ".") || " - " || ./title`
	});
}


Numbering Elements In Case Of Unloaded Documents

If the CMS saves the number of the elements that we want to count in the map. Then we can use those numbers if the documents are not loaded as previous values for the following loaded documents.
In the example below, the topicref elements have an unloaded-count attribute. This attribute should be added by the CMS.

When  JIT loading is enabled in an editor, some documents can be unloaded. Errored documents are also unloaded. If we need to take these cases into consideration, we need to have additional logic in our figNumberingCallback function.

  • Numbering callback:
figNumberingCallback.xqm
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(),
	$isSourceNode as xs:boolean
) as item()* {
	let $previous := if ($relType eq "first") then 0 else $previousAccumulator
    return
    (: if $isSourceNode is true, the document is unloaded and we just have the topicref as $node :)
    if ($isSourceNode) then
        if ($node[@unloaded-count]) then
            $previous + number($node/@unloaded-count)
        else
            $previous
    else
        $previous + 1
};
  • configureSxModule.js:
configureSxModule.js
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::table',
		'http://www.fontoxml.com/app',
		'figNumberingCallback'
	);

	// table
	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...
	});
}