Configure a custom table definition

This guide describes how to configure a custom table definition. A custom table definition should be used when the table to be configured does not conform to any of the four table standards supported by Fontoxml out-of-the-box.

When you are not sure if you need a custom table definition, please see the Configure tables guide. For more information about the supported table standards and their posibilities, see the CALS, XHTML, TEI and Simpletable add-on pages.

When you are sure you need a custom table definition, continue on reading this guide. The creation of a custom table definition will be explained step-by-step. Therefore it is recommended to read each step carefully and to not skip over steps.

Create a package

The first step for implementing a custom table definition is to create a package. This package will contain at least the table definition itself, a configureSxModule file, a configureAsMyTableElements file and a operations.json file.

Create the package folder

Create a folder in the packages folder of your application. Create a src folder inside this new package folder.

For clarity, keep the naming of the package folder in line with the naming of other packages.

Create configureSxModule file

The next step is to create the configureSxModule file in the src folder. This file will mark this package as add-on.

configureSxModule.js

JavaScript

define([

], function (

) {
	'use strict';

	return function configureSxModule (sxModule) {
		sxModule.markAsAddon();
	};
});

Create MyTableDefinition

Create a folder named table-definition. Then create a file called MyTableDefinition in this new folder. Replace the "My" part of the table definition name with the name of the table specification you are implementing.

MyTableDefinition.js

JavaScript

define([
	'fontoxml-table-flow/TableDefinition'
], function (
	TableDefinition
) {
	'use strict';

	function MyTableDefinition (options) {
		var properties = {
			...
		};

		TableDefinition.call(this, properties);
	}

	MyTableDefinition.prototype = Object.create(TableDefinition.prototype);
	MyTableDefinition.prototype.constructor = MyTableDefinition;

	return MyTableDefinition;
});

Create configureAsMyTableElements file

Create a configureAsMyTableElements file in the src folder of the newly created package. This file should contain and return a function named the same as the file. This function can be called in any configureSxModule file in the application.

Call this function in the same configureSxModule as where the table elements and their related nodes are configured.

configureAsMyTableElements.js

JavaScript

define([
	'fontoxml-table-flow/configureAsTableElements',
	'./TableDefinition/MyTableDefinition'
], function (
	configureAsTableElements,
	MyTableDefinition
) {
	'use strict';

	return function configureAsMyTableElements (sxModule, options) {
		options = options || {};
		var tableDefinition = new MyTableDefinition(options);
		configureAsTableElements(sxModule, options, tableDefinition);
	};
});

In addition to the bare minimum, you may also want to configure elements that can be found within a table but do not define any structure of the table here. An example of such element is the XHTML caption element. Note that this is not required and that these elements can be configured outside of this function just like any other element.

Create the insert operation

The only operation that has to be created is the insert operation. This needs to be done in order to determine which kind of table to insert. For the insert operation to determine which kind of table to insert, it requires the childNodeStructure to be set to determine which table definition to use. The platform already offers a generic table-insert operation for inserting a table. This operation can be wrapped in a specific insert operation as shown in the example.

operations.json

JavaScript

{
	"my-table-insert": {
		"label": "Table",
		"description": "Insert a table.",
		"icon": "table",
		"initialData": {
			"childNodeStructure": [
			"table"
			]
		},	
		"steps": [
			{
				"type": "operation/table-insert"
			}
		]
	}
}

This childNodeStructure should at least contain the table node of the table to be inserted. When the childNodeStructure does not contain a table node, the following error will be thrown:

Found no table elements in the childNodeStructure, please provide exactly one table element. at operation 'my-table-insert', step 1 of 2, type 'command/table-insert'.

When this error occurs and the childNodeStructure does contain a table node, you may want to check the namespaces used for constructing the selectors, more on the usage of namespaces and the construction of selectors can be found at the selectorParts property section of this guide.

When you want to insert a table wrapped in, for example, some sort of figure element, you can add this element to the childNodeStructure's jsonML.

Add dependency on the created package

Add the created package to the dependencies list of your application. This list can be found in the manifest.json file in the config folder of your application.

manifest.json

JavaScript

...
"my-table-flow": "packages/my-table-flow"
...

Configuring the table definition

The table definition is used to express how a table should be read from XML and how it should be written to XML. A table definition comes down to a collection of XPath queries and strategies which are used in the serialization and deserialization processes.

These XPath queries and strategies will be set as properties on a property variable which can be seen in the example for the MyTableDefinition file. The properties are grouped per type and explained per property. The best way to set up a custom table definition is by reading through all properties and to decide per property if it needs to be set for the table you are implementing.

Configure general settings

shouldRemoveEmptyRows

This property is optional.

The shouldRemoveEmptyRows property determines whether rows which do not contain any cell nodes should be removed. The situation can exists when a row contains cells which all span over the row below, effectively making the row below empty.

This property is set to true by default, when empty rows need to be preserved, set this property to false.

MyTableDefinition.js

JavaScript

...
shouldRemoveEmptyRows: false
...

shouldCreateColumnSpecificationNodes

This property is optional.

The shouldCreateColumnSpecificationNodes property determines whether nodes containing information regarding the columns should be created. This property should be set to true if these nodes should be created.

This property is set to false by default.

MyTableDefinition.js

JavaScript

...
shouldCreateColumnSpecificationNodes: true
...

supportsBorders

This property is optional.

The supportsBorders property determines whether a tables supports borders to be set. If this property is set to false, the table will have all its borders rendered in the content view and the

This property is set to false by default.

Other

...
supportsBorders: true
...

selectorParts

This property is required.

The selectorParts property must contain a map of abstract table element names to XPath selectors. These selectors should include a namespace selector if applicable. The selectorParts are used for configuring the elements and can be used to construct XPath queries.

Some examples of XPath selectors are:

Other

table (: A table element without namespace :)
Q{}table (: A table element without namespace :)
Q{http://some-namespace.com/}table (: A table element in the http://some-namespace.com/ namespace :)
table[@class] (: A table element having a class attribute :)

These abstract table names can be configured:

  • tableFigure - A figure-like element containing the table element. Only use this element when it is a required element. Other possible figure-like elements should be configured separately.

  • table (required) - The table element itself.

  • columnSpecification - An element specifying the properties of a column. Found most commonly as child node of the table element.

  • columnSpecificationGroup - An element used for grouping column specification elements.

  • row (required) - An element defining a table row.

  • headerRow - An element defining a table row which is a header.

  • cell (required) - An element defining a table cell.

  • headerCell - An element defining a table cell which is a header.

  • headerContainer - An element defining a header. These elements contain row elements.

  • bodyContainer - An element defining a body. These elements contain row elements.

  • footerContainer - An element defining a footer. These elements contain row elements.

Below is an example on how to create the mapping for the selectorParts property:

Other

function MyTableDefinition (options) {
	var selectorParts = {
		table: 'table',
		row: 'row',
		cell: 'cell'
	};

	var properties = {
		...
		selectorParts = selectorParts;
		...
	};
}

When a table should be in a different namespace, the URI of that namespace must be passed to the table definition by setting the namespaceURI on the table property of the options object passed to the table definition.

Below is an example on how to create the mapping for the selectorParts property with namespace URIs:

Other

function MyTableDefinition (options) {
	var namespaceURI = options.table.namespaceURI;

	var namespaceSelector = 'Q{' + namespaceURI + '}';
	var selectorParts = {
		table: namespaceSelector + 'table',
		row: namespaceSelector + 'row',
		cell: namespaceSelector + 'cell'
	};

	var properties = {
		...
		selectorParts = selectorParts;
		...
	};
}

widthToHtmlWidthStrategy

This property is optional.

The widthToHtmlWidthStrategy property should contain a function accepting a width (string). The function should then convert the value passed to it to a valid HTML width value (string).

addWidthsStrategy

This property is optional.

The addWidthsStrategy property should contain a function accepting two widths (strings). The function should then add up both values and return the result (string).

divideWidthByTwoStrategy

This property is optional.

The divideWidthByTwoStrategy property should contain a function accepting one width (string). The function should then divide the value given by two and return the result (string).

widthsToFractionsStrategy

This property is optional. It should be set when the column sizing popover is to be used. See the open-table-column-sizing-popover operation and CALS implementation for more information.

The widthsToFractionsStrategy property should contain a function accepting an array of widths (strings). It should return an array of the same length as the input array which represents the given widths as fractions (number).

fractionsToWidthsStrategy

This property is optional. It should be set when the column sizing popover is to be used. See the open-table-column-sizing-popover operation and the CALS implementation for more information.

The fractionsToWidthsStrategy property should contain a function accepting an array of fractions (numbers). It should return an array of the same length as the input array which represents the given fractions as this tables specific widths (string).

tableDefiningNodeSelector

This property is optional.

The tableDefiningNodeSelector property must contain an XPath selector which returns true when given a table node.

Other

...
tableDefiningNodeSelector: 'self::table'
...

When this property is not set, it will be auto-generated by appending the table property of the selectorParts object to self:: which results in something like self::table.

cellDefiningNodeSelecton

This property is optional.

The cellDefiningNodeSelector property should contain an XPath selector which returns true when given a cell node.

Other

...
cellDefiningNodeSelector: 'self::cell'
...

When this property is not set, it will be auto-generated by appending the table property of the selectorParts object to self:: which results in something like self::cell.

tablePartsNodeSelector

This property is optional.

The tablePartsNodeSelector property should contain an XPath selector which returns true when given any node that defines the structure of a table.

Other

...
tablePartsNodeSelector: 'self::table or self::row or self::cell'
...

When this property is not set, it will be auto-generated by appending every property of the selectorParts object to self:: and then joining the results together with or , resulting in something like self::table or self::row or self::cell.

Configure finds

The "find" properties are XPath queries used to find one or more specific nodes under a given node. Depending on the structure of the table, not all of these finds have to be configured. For example, when a table does not support container nodes, the finds for these nodes do not have to be set.

When your schema supports nested tables, keep in mind that some XPath queries need to be specific enough to not select nodes from the parent/child table.

Create aliases for all elements to be used in XPath queries to increase readability of those XPath queries. This is especially useful when your table definition should support tables with a namespace different from the namespace of the document.

Other

var table = selectorParts.table;
var row = selectorParts.row;
var cell = selectorParts.cell;

findHeaderRowNodesXPathQuery

This property is optional.

The findHeaderRowNodesXPathQuery should return any row nodes found in the header of the table. This query is executed with the table node as its context node.

Other

...
findHeaderRowNodesXPathQuery: './thead/row'
...

findBodyRowNodesXPathQuery

This property is required.

The findBodyRowNodesXPathQuery should return any row nodes found in the body of the table. These are "regular" rows. This query is executed with the table node as its context node.

Other

...
findBodyRowNodesXPathQuery: './tbody/row'
...

findFooterRowNodesXPathQuery

This property is optional.

The findFooterRowNodesXPathQuery should return any row nodes found in the footer of the table. This query is executed with the table node as its context node.

Other

...
findFooterRowNodesXPathQuery: './tfoot/row'
...

findHeaderContainerNodesXPathQuery

This property is optional.

The findHeaderContainerNodesXPathQuery should return container nodes defining the header of a table. This query is executed with the table node as its context node.

Examples are the thead element found in both CALS and XHTML.

Other

...
findHeaderContainerNodesXPathQuery: './thead'
...

findBodyContainerNodesXPathQuery

This property is optional.

The findBodyContainerNodesXPathQuery should return container nodes defining the body of a table. This query is executed with the table node as its context node.

Examples are the tbody element found in both CALS and XHTML.

Other

...
findBodyContainerNodesXPathQuery: './tbody'
...

findFooterContainerNodesXPathQuery

This property is optional.

The findFooterContainerNodesXPathQuery should return container nodes defining the footer of a table. This query is executed with the table node as its context node.

Examples are the tfoot found in both CALS and XHTML.

Other

...
findFooterContainerNodesXPathQuery: './tfoot'
...

findColumnSpecificationNodesXPathQuery

This property is optional.

The findColumnSpecificationNodesXPathQuery should return column specifying nodes. This query is executed with the table node as its context node.

Examples are the colspec found in CALS and col found in XHTML.

Other

...
findColumnSpecificationNodesXPathQuery: './colspec'
...

findCellNodesXPathQuery

This property is required.

The findCellNodesXPathQuery must return cell nodes found in a given row. This property is required. This query is executed with the current row node as its context node.

Other

...
findCellNodesXPathQuery: './cell'
...

findNonTableNodesPrecedingRowsXPathQuery

This property is optional.

The findNonTableNodesPrecedingRowsXPathQuery should return all nodes that do not define the structure of a table but are located inside the table node, before any structure defining nodes. This query is executed with the table node as its context node.

Examples of these nodes are the caption in XHTML and the head in TEI.

Other

...
var tableNodesSelector = 'self::' + col +
		' or self::' + colGroup +
		' or self::' + tr +
		' or self::' + thead +
		' or self::' + tbody +
		' or self::' + tfoot;
...
var properties = {
	findNonTableNodesPrecedingRowsXPathQuery: './*[(' + tableNodesSelector + ') => not() and following-sibling::*[' + tableNodesSelector + ']] '
...

findNonTableNodesFollowingRowsXPathQuery

This property is optional.

The findNonTableNodesFollowingRowsXPathQuery should return all nodes that do not define the structure of a table but are located inside the table node, after any structure defining nodes. This query is executed with the table node as its context node.

Other

...
findNonTableNodesFollowingRowsXPathQuery: './*[(' + tableNodesSelector + ') => not() and preceding-sibling::*[' + tableNodesSelector + ']] '
...

Configure data XPath queries

The data XPath queries provide the application with information about the table being parsed, such as the number of columns.

getNumberOfColumnsXPathQuery

This property is required.

The getNumberOfColumnsXPathQuery should return the number of columns present in the table. The query is executed with the table node as its context node.

Other

...
getNumberOfColumnsXPathQuery: './row/cell => count()'
...

The given example is a very naive implementation which does not take into account any column spanning cells. For a solution, see the next example.

Other

...
getNumberOfColumnsXPathQuery: '(let $cells := (.//' + row + ')[1]/' + cell + ' return for $cell in $cells return let $colspan := $cell/@colspan => number() return if ($colspan) then $colspan else 1) => sum()'
...

getRowSpanForCellNodeXPathQuery

This property is required.

The getRowSpanForCellNodeXPathQuery should return the row span of a given cell node. This query is executed with the cell node as its context node.

Other

...
getRowSpanForCellNodeXPathQuery: 'let $rowspan := ./@rowspan => number() return if ($rowspan) then $rowspan else 1'
...

This query should always return a number. This means it should always return 1 as a default when a rowspan-like attribute is not present.

getColumnSpanForCellNodeXPathQuery

This property is required.

The getColumnSpanForCellNodeXPathQuery should return the column span of a given cell node. This query is executed with the cell node as its context node.

Other

...
getColumnSpanForCellNodeXPathQuery: 'let $colspan := ./@colspan => number() return if ($colspan) then $colspan else 1'
...

This query should always return a number. This means it should always return 1 as a default when a colspan-like attribute is not present.

Configure normalizations

Normalizations are used for creating, converting and removing elements.

normalizeContainerNodeStrategies

This property is optional.

The normalizeContainerNodeStrategies will normalize the container nodes present in a table.

The following normalization strategies are available for containers:

  • createAddHeaderContainerNodeStrategy - Adds a header container node, only when needed. Moves row nodes to the newly created header container node. Use this strategy when tables without header containers are to be expected and the tables to be created should have header containers.

  • createAddBodyContainerNodeStrategy - Adds a body container node, only when needed. Moves row nodes to the newly created header container node. Use this strategy when tables without body containers are to be expected and the tables to be created should have body containers.

  • createRemoveBodyContainerNodeStrategy - Removes a body container node, when present. Use this strategy when tables with body containers are to be expected and the tables to be created should not have body containers.

  • createRemoveFooterContainerNodeStrategy - Removes a footer container node, when present. Moves rows to the body container node or else to the table node.

  • createRemoveHeaderContainerNodeStrategy - Removes a header container node, when present. Moves rows to the body container node or else to the table node. Use this strategy when tables with header containers are to be expected and the tables to be created should not have header containers.

These strategies are available after including fontoxml-table-flow/normalizeContainerNodeStrategies.

In addition to the normalization that can be set here, during the transformation from XML to the grid model, the header container node will be updated when the number of header rows gets updated. Rows will be moved into the header container when the number of header rows increases and rows will be moved out of the header container when the number of header rows decreases. If the header container remains empty after decreasing the number of header rows, it will remove the header container.

For an example of the usage of these strategies, see the XHTML table definition on Github or the CALS table definition on Github.

normalizeRowNodeStrategies

This property is optional.

The normalizeRowNodeStrategies are used to normalize rows nodes. This, for example, is useful when a table uses row nodes to define its header, like simpletable does.

The following normalization strategies are available for rows:

  • createConvertNormalRowNodeStrategy - This strategy converts a normal row (or body row) to a header row by converting the row element itself. The element to convert to can be configured.

  • createConvertFormerHeaderRowNodeStrategy - This strategy converts a former header row to a normal row by converting the row element itself. The element to convert to can be configured.

These strategies are available after including fontoxml-table-flow/normalizeRowNodeStrategies.

You may almost always want to use the createConvertNormalRowNodeToHeaderRowNodeStrategy and the createConvertFormerHeaderRowNodeToNormalRowNodeStrategy together.

normalizeCellNodeStrategies

This property is optional.

The normalizeCellNodeStrategies are used to normalize cell nodes. This, for example, is useful when a table uses cell nodes to define its header, like XHTML can do.

The following normalization strategies are available for cells:

These strategies are available after including fontoxml-table-flow/normalizeCellNodeStrategies.

Other

...
normalizeCellNodeStrategies: [
	normalizeCellNodeStrategies.createConvertHeaderCellNodeStrategy(namespaceURI, 'th'),
	normalizeCellNodeStrategies.createConvertFormerHeaderCellNodeStrategy(namespaceURI, 'td')
]
...

For a more extensive example of the usage of these strategies, see the XHTML table definition on Github or the CALS table definition on Github.

normalizeColumnSpecificationStrategies

This property is optional.

The normalizeColumnSpecificationStrategies are used to normalize column specifications, not the column specification nodes. This, for example, can be used to re-apply column names.

The following normalization strategies are available for column specifications:

  • createRecreateColumnName - This strategy recreates the column name of the columns. The name is configurable and gets appended with "-0" for the first column.

These strategies are available after including fontoxml-table-flow/normalizeColumnSpecificationStrategies.

For an example of the usage of this strategy, see the CALS table definition on Github.

findNextColumnSpecification

This property is optional.

The findNextColumnSpecification is used to find the column specification for the next column. When no column specification can be found for the next column, false must be returned.

The function has 4 arguments set when it is executed:

  • columnIndex - The index of the column to find the specification for

  • columnSpecification - The current column specification

  • columnSpecificationIndex - The index of the current column specification

  • columnSpecifications - The array containing all column specifications

For an example of the usage of this strategy, see the CALS table definition on Github.

Default specifications

The default specifications can be used to provide some default values to be present on a specification when it is created. Specifications are just plain javascript objects used as a mapping from value name to value.

getDefaultColumnSpecificationStrategy

This property is optional.

The getDefaultColumnSpecificationStrategy should return a default specification for columns. This function will have the context object as its only argument.

When column widths should be updated, the property containing the width must be named "columnWidth".

Other

...
getDefaultColumnSpecificationStrategy: function (context) {
	return {
		columnName: 'column-' + context.columnIndex,
		columnNumber: context.columnIndex + 1,
		columnWidth: '1*',
		rowSeparator: true,
		columnSeparator: true
	};
}
...

getDefaultCellSpecificationStrategy

This property is optional.

The getDefaultCellSpecificationStrategy should return a default specification for cells.

For the rendering of cell borders, the rowSeparator and columnSeparator values will be read from the cell specification.

Other

...
getDefaultCellSpecificationStrategy: function (context) {
	return {
		rowSeparator: true,
		columnSeparator: true
	};
}
...

getDefaultRowSpecificationStrategy

This property is optional.

The getDefaultRowSpecificationStrategy should return a default specification for rows.

Other

...
getDefaultRowSpecificationStrategy: function (context) {
	return {
		someValue: 'value'
	};
}
...

Create strategies

createCellStrategy

This property is required.

This strategy should create a new cell node and return it. This property only accepts one strategy. The platform offers one standard strategt which can be used by including fontoxml-table-flow/createCreateCellStrategy.

Other

...
createCellStrategy: createCreateCellStrategy(namespaceURI, localName)
...

createRowStrategy

This property is required.

This strategy should create a new row node and append it to the appropriate container. This property accepts only one strategy. The platform offers one standard strategy which can be used by including fontoxml-table-flow/createCreateRowStrategy. This strategy creates a new row and appends it to the appropriate node.

Other

...
createRowStrategy: createCreateRowStrategy(namespaceURI, localName)
...

createColumnSpecificationStrategy

This property is optional.

This strategy should create a new column specification node and append it to the table node. This property accepts only one strategy. The platform offers one standard strategy which can be used by including fontoxml-table-flow/createCreateColumnSpecificationNodeStrategy. This strategy creates a new column specification node and inserts it before the row nodes or container nodes of the table.

Other

...
createColumnSpecificationStrategy: createCreateColumnSpecificationNodeStrategy(namespaceURI, localName, insertBeforeXPathQuery)
...

Get specification strategies

Getting values for specifications is done by reading the value of an attribute from a node.

The following strategies are available for getting attribute values from a node:

  • createGetValueAsBooleanStrategy - Reads the value of an attribute and converts it to a boolean using the given XPath selector. The value gets saved under the given name in the current strategy.

  • createGetValueAsStringStrategy - Reads the value of an attribute and converts it to a string using the given XPath selector. The value gets saved under the given name in the current strategy.

  • createGetValueAsNumberStrategy - Reads the value of an attribute and converts it to a number using the given XPath selector. The value gets saved under the given name in the current strategy.

These strategies are available after including fontoxml-table-flow/getSpecificationValueStrategies.

getTableSpecificationStrategies

This property is optional.

The getTableSpecificationStrategies are used to get attributes from the table node.

Other

...
getTableSpecificationStrategies: [
	getSpecificationValueStrategies.createGetValueAsBooleanStrategy('borders', './borders = "all"')
]
...

getColumnSpecificationStrategies

This property is optional.

The getColumnSpecificationStrategies are used to get attributes from a given column specification node.

Other

...
getColumnSpecificationStrategies: [
	getSpecificationValueStrategies.createGetValueAsStringStrategy('columnName', './colname')
]
...

getRowSpecificationStrategies

This property is optional.

The getRowSpecificationStrategies are used to get attributes from a given row node.

getCellSpecificationStrategies

This property is optional.

The getCellSpecificationStrategies are used to get attributes from a given cell node.

Set attribute strategies

The set attribute strategies set, as the name implies, attributes on a specific node. These strategies can also remove attributes from nodes. The set attribute strategies can be found in the setAttributeStrategies file.

The following strategies are available for setting attributes to a node:

These strategies are available after including fontoxml-table-flow/setAttributeStrategies.

setTableNodeAttributeStrategies

This property is optional.

The setTableNodeAttributeStrategies are used to set attributes on the table node.

setColumnSpecificationNodeAttributeStrategies

This property is optional.

The setColumnSpecificationNodeAttributeStrategies are used to set attributes on a given column specification node.

setRowNodeAttributeStrategies

This property is optional.

The setRowNodeAttributeStrategies are used to set attributes on a given row node.

setCellNodeAttributeStrategies

This property is optional.

The setCellNodeAttributeStrategies are used to set attributes on a given cell node.

Examples

If you need examples of already configured tables, please see the Github pages for the CALS, XHTML and TEI table implementations.