m modelNode {Element|Text} The node to be processed.
	 */
	updateDefinition( definition, modelNode ) {
		if ( modelNode.data ) {
			definition[ $text ] = modelNode.data;
		} else {
			definition[ $element ] = modelNode.name;
		}

		// Attributes.
		{
			const nodeAttribs = Array.from( modelNode.getAttributes() );

			if ( nodeAttribs.length ) {
				definition[ $attribs ] = nodeAttribs.reduce( ( obj, [ name, value ] ) => {
					obj[ name ] = value;
					return obj;
				}, {} );
			}
		}
	}

	/**
	 * Gets the complete node definition, including its children tree.
	 *
	 * @param modelNode {Element|Text} The node to be processed.
	 * @returns {Object} The node definition.
	 */
	getDefinition( modelNode ) {
		const definition = {};

		this.updateDefinition( definition, modelNode );

		if ( $element in definition ) {
			if ( modelNode.childCount ) {
				definition[ $children ] = Array.from( modelNode.getChildren() )
					.map( child => this.getDefinition( child ) );
			}
		}

		return definition;
	}

	/**
	 * Calculates the tree path for a given model node.
	 *
	 * The path reflects that node position in the model document.
	 *
	 * @param modelNode {Element|Text} The model node.
	 * @returns {Number[]} A sequence of indexes to reach this node in the tree.
	 */
	getPath( modelNode ) {
		const path = [];

		while ( modelNode.parent ) {
			path.unshift( modelNode.index );
			modelNode = modelNode.parent;
		}

		return path;
	}

	/**
	 * Gets the tree entry representing a model node.
	 *
	 * @param modelNode {Element|Text} The model node.
	 * @returns {String[]} The tree representation of the node.
	 */
	getNode( modelNode ) {
		const path = this.getPath( modelNode );

		let node = this.root;
		while ( path.length ) {
			node = node[ $children ][ path.shift() ];
		}

		return node;
	}
}


TEST?=./...

default: test

fmt: generate
	go fmt ./...

test: generate
	go get -t ./...
	go test $(TEST) $(TESTARGS)

generate:
	go generate ./...

updatedeps:
	go get -u golang.org/x/tools/cmd/stringer

.PHONY: default 