Use of Maps for Returned Values

A classic problem with functional programming is that a function can only return one result. If you want to compute two values (say a maximum and minimum) from the same input then you have two choices. You can either process the input more than once (which may involve some redundant computation), or you can return a composite result.

In this application there are many cases where we need to return a composite result.

To take an example, suppose we are validating an attribute, and we find that the declared type for that attribute is a user-defined list type, where the item type of the list is part-number, and part-number is derived from xs:ID. The calling code wants to know (a) whether the attribute is valid against its declared type; (b) what error messages to report if not; and (c) whether the value contains any xs:ID or xs:IDREF values that need to be added to global tables of xs:ID and xs:IDREF values for document-level validity checking at the end.

Our solution to this is that we recurse through the instance document in a tree-walk driven by xsl:apply-templates in the normal way, but the return value from xsl:apply-templates is a map containing all the information gleaned from the processing of this subtree.

Very often the information returned from several calls on xsl:apply-templates (for example, one call for child elements and another for attributes) will need to be combined into a single map. At the top level, when we return from the initial call on xsl:apply-templates on the root node, all the information that is needed to produce the validation report is present in one large map, and the final stage of processing takes this map and generates the XML report.

The maps that are produced by the different processing stages thus typically include some subset of a common set of fields. These include:

Table 1. The structure of maps used to return partial results of processing

NameValue
validAn xs:boolean indicating whether the subtree is valid
errorsA set of error objects indicating error information to be included in the validation report
valueThe typed value of an element or attribute
typeThe type against which a subtree was validated
lexicalThe lexical form of an attribute or text node after whitespace normalization
idA set of xs:ID values found in the subtree
id-mapA mapping from xs:ID values found in the subtree, to the nodes on which they appeared
idrefsA set of xs:IDREF values found in the subtree

When two of these maps representing properties of different subtrees are combined, different rules apply to each field. For example, for the id and idrefs and errors fields we can take the union of the two values. For the valid property, we can apply a logical AND; a tree is valid only if all its subtrees are valid. For value and type we can drop the value; these fields are used only at the next level up, and do not propagate all the way to the root.