RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Taking XML Validation to the Next Level: Explore CAM's Expressive Power

The generic-sounding Content Assembly Mechanism, or CAM, is an exciting step beyond XML Schema, but it's new and not well documented. This article series represents CAM: The Missing Manual. This last installment is a deep exploration of CAM's ability to express exactly what you need for data-centric documents.


ow that you've seen a more detailed comparison of CAM to XML Schema in Part 2, this section builds upon that foundation to explore several of those strengths in more detail.

Part 1 of this article showed a table that you'll need for this section, so it's reproduced here for your convenience as Table 1, which summarizes the key strengths of CAM compared to XML Schema and DTDs. The line items in the table are covered in detail in the sections below the table.

Validation Examples

The following examples draw on Table 1 to illustrate the points more fully.

Current-Node Fixed Validation (see Table 1, item 2)

As you have seen, applying business rules to restrict the domain of an element to a very specific range of values is straightforward—and something that XML Schema can do as well. Here are some examples:

   <as:constraint action="setNumberMask(//item/quantity,######)" />
   <as:constraint action="setNumberRange(//item/quantity,1-999)" />

Current-Node Conditional Validation (see Table 1, item 3)

You've also seen how to make acceptable values contingent upon some aspect of the value itself (such as deciding that a value is either a five- or nine-digit zip code) with constraints such as these:

       condition="string-length(.) >= 9"
       action="setStringMask(//zip,00000'-'0000)" />
       condition="string-length(.) < 9"
       action="setStringMask(//zip,00000)" />

Cross-Node Conditional Validation (see Table 1, item 4)

XML Schema's conditional expressions are available only through regular expressions. CAM extends this with cross-node conditional validation, where you specify an action on one node based on the value of a different node. For example, suppose that items with a part number of 123-678 are customized for customers in the state of Washington. If that part number shows up on a purchase order for a customer from a different state you would want it to be flagged as an error. Recall that CAM conditions and actions select nodes by standard XPath. Thus, you essentially already know how to do this; in prior examples the condition always referred to an XPath of "." (the current node). But if you change this to some other node, with a constraint such as that shown below, you now have a cross-node validation rule.

       condition="//Item/@pno = '123-678'"
        action="restrictValues(//shipTo/state,'WA')" />

The term cross-node fits better than cross-element because "node" is the more general XML term. An element is a node and an attribute is a node. The XPath conditional expressions aren't limited to XML element nodes; you may also (as in the example above) reference attribute nodes.

Structure Variability (see Table 1, item 6)

Cross-node validation is impressive. But discovering that you can dynamically modify what constitutes a valid structure is dazzling. Such modification provides tremendous flexibility—but requires learning a few intricacies. Here are a few examples.

  • Example 1: Assume a purchase order includes an optional color attribute. Further assume that the purchase order includes a tray number where the item is found in the warehouse. Items with color attributes don't need a tray number—the color is sufficient for picking the item. However, it is harmless if the tray number is provided. This constraint expresses these business rules:
        action="makeOptional(//Item/TrayNumber)" />
  • Example 2: Assume you have a purchase order that includes the total order weight, and that the purchase order also includes an optional freight carrier element. However, if the order weight exceeds 25 kg, then the purchase order must specify a freight carrier to transport the goods. That is, if the condition shown below is satisfied then the FreightHandler node must be present. This constraint expresses these business rules:
       condition="//Item/@weight > 25"

If you are following along with the working examples, you may find that constraints referring to other nodes (such as the preceding examples) may perform as advertised—or not. The results depend on your specific XML instance. Notice that each of the last three examples used the XPath // designator to start each selector. That selector locates any and all nodes that match the rest of the expression. In the previous section the example's goal was to restrict part number 123-678 to ship only to customers in Washington State. Here's the rule, repeated:

       condition="//Item/@pno = '123-678'"
        action="restrictValues(//shipTo/state,'WA')" />

Previously, I glossed over the precise meaning of that constraint, which is:

  • If any <Item> has a @pno of 123-678 then all <state> elements within all <shipTo> elements must be WA.

Typical purchase orders have only one shipping address, so the net result is the same as specifying this:

  • If any <Item> has a @pno of 123-678 then the single <state> element within the single <shipTo> element must be WA.

The two previous examples in this section that deal with structure variability are not quite as forgiving. The intents of these two constraints are:

  • Make the <TrayNumber> of an <Item> optional if this <Item> has a color.
  • Require a <FreightHandler> for an <Item> if the weight of this <Item> exceeds 25 kg.

But as written, what they really indicate is this:

  • Make the <TrayNumber> of any <Item> optional if any <Item> has a color.
  • Require a <FreightHandler> for an <Item> if the weight of any <Item> exceeds 25 kg.

Because a purchase order typically lists more than one item, these rules may produce incorrect results. To fix this, you must introduce the appropriate XPath axis to select only nodes related to the current node for the condition predicate; in other words, you must rewrite the two constraints as:

        action="makeOptional(//Item/TrayNumber)" />
       condition="ancestor::Item/@weight > 25"
       action="makeMandatory(//item/FreightHandler)" />

Note that both actions still use the "every match" notation //. Remember that the constraint is attached to the node specified in the action attribute. You want to consider every <TrayNumber> within an <Item>. Whether to trigger the action for any particular one then depends upon the condition, which needs to refer to its locality.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date