Converting XML Schemas to Schematron: (#13) Identity constraints

The article originally appeared in a blog on O'Reilly on December 11, 2008, after the project had finished.

A feature of XML Schemas (XSD) we have not looked at in this series so far has been integrity constraints. XML Schemas does provide an ID datatype which provides the same function as DTD's ID attribute type. But it also goes beyond that with special integrity constraints.

Interestingly enough, for the would-be Schematron implementer, is that XSD abandons the roccoccoccooned intricacies of its complex type derivation system entirely for integrity constraints: instead the elements of interested are selected by a simple kind of XPath.

This article sketches out how to implement the same functionality as XSD's integrity constraints in Schematron.  The code below has not been tested, but should be indicative.

These XPaths are in selector attributes, and pretty much exactly match Schematron's rule/@context attributes: all that is needed is to remove any leading .//.

Unique

Uniqueness is the simplest constraint. It says that, from all the fields selected, there should be no duplicate values. Here is an example in XSD.

<xs:unique name="nearlyID">
 <xs:selector xpath=".//*"/>
 <xs:field xpath="@id"/>
</xs:unique>

So unique differs from XML's ID in that 1) there is no restriction on the characters or tokens that can be used, and 2) there can be multiple declarations of unique fields.

Let's use Schematron abstract patterns for this, and deal with the implementation further down the page. Here is the pattern:

<pattern id="nearlyID" is-a="one-field-unique-constraint" >
  <param name="nearlyID" value="'nearlyID'" />
  <param name="selector" value="*" />
  <param name="field" value="@id" />
</pattern>

The XSLT code for converting from the XSD to the Schematron is trivial.

Key

There is a further difference with IDs: the XSD integrity constraints can be multi-field constraints: this is where, rather than the document-processing idiom of giving things unique labels, it is desired to use data values to uniquely identify elements, the relational idiom. So each field may be non-unique, but the combination should be unique.

XSD's key element is very much like the unique element, however it carries the additional constraint that the key fields should be present (and populated?).

<xs:key name="fullName">
 <xs:selector xpath=".//person"/>
 <xs:field xpath="forename"/>
 <xs:field xpath="surname"/>
</xs:key>

Here is the Schematron equivalent declaration we can use.

<pattern id="fullName" is-a="two-field-key-constraint" >
  <param name="name" value="'fullName'" />
  <param name="selector" value="person" />
  <param name="field1" value="forename" />
  <param name="field2" value="surname" />
</pattern>

One mismatch between XSD and Schematron is that XSD's integrity constraints take an arbitrary number of fields, while Schematron's abstract pattern mechanism does not have a varargs capability. (Maybe it is a good feature for the next version of Schematron?) Instead I have chosen to provide five versions of the abstract pattern, which can cover common cases. More fields could be implemented by conversion direct to patterns without using abstract patterns, if they were needed.

Keyref

A keyref element is the equivalent of an IDREF. It says that for all the fields in the reference, there should be matching fields in a key elsewhere in the same document.

Here is what they look like in XSD

<xs:keyref name="personRef" refer="fullName">
 <xs:selector xpath=".//personPointer"/>
 <xs:field xpath="@first"/>
 <xs:field xpath="@last"/>
</xs:keyref>

And here is what I came up with using Schematron abstract patterns: I think it is not any more difficult to read.

<pattern id="personRef" is-a="two-field-keyref-constraint" >
  <param name="name" value="'personRef'" />
  <parame name="keyName" value="'fullName'" />
  <param name="keySelector" value="person" />
  <param name="refSelector" value="personPointer" />
  <param name="keyField1" value="forename" />
  <param name="refField1" value="@first" />
  <param name="keyField2" value="surname" />
  <param name="refField2" value="@last" />
</pattern>

Abstract pattern: unique

Here is (a sketch of) the implementation for the abstract pattern for unique. We have another traversal of the document and store it in a variable belonging to the pattern, so that it only gets created once for all firings of the rule.

<pattern id="one-field-key-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <key> has a rule, to select each selected content -->
   <rule context="$selector[$field]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$field]"/>

    <assert test="count( $targetNodeSet[$field =  current()/$field] ) = 1 " >
    The <value-of select="$name" /> key value of <value-of select="$field" /> should be used once only.
    </assert>

 </rule>
  
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> key
      on a <name/>  must be present</assert>
   </rule>
  
</pattern>

Next is the code for handling multiple arguments, using pairwise matching.

<pattern id="two-field-unique-constraint" abstract="true"  >
   <title>Unique</title>
   <!-- Each <unique> has a rule, to select each selected content -->
   <rule context="$selector[$field1 or $field2]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector "/>

    <assert test="count( $targetNodeSet
                         [$field1 = current()/$field1]
                     [$field2 = current()/$field2]
                      ) = 1 " >
    The <value-of select="$name" /> id value of [<value-of select="$field1" />, <value-of select="$field2" />] should be used once only.
    </assert>
   </rule>
   
  
</pattern>
  
  
<pattern id="three-field-unique-constraint" abstract="true"  >
   <title>Unique</title>

   <!-- Each <unique> has a rule, to select each selected content -->
   <rule context="$selector[$field1 or $field2 or $field3]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector"/>
   
    <assert test="count( $targetNodeSet
                           [$field1 = current()/$field1]
                     [$field2 = current()/$field2]
                     [$field3 = current()/$field3]
                      ) = 1 " >
    The <value-of select="$name" /> id value of [<value-of select="$field1" />, <value-of select="$field2" />, <value-of select="$field" />
] should be used once only.
    </assert>
   </rule>
  
</pattern>
  

<pattern id="four-field-unique-constraint" abstract="true"  >
   <title>Unique</title>
   <!-- Each <unique> has a rule, to select each selected content -->
   <rule context="$selector[$field1 or $field2 or $field3 or $field4]">
    <!-- Make a variable containing all the selected content -->

    <let name="targetNodeSet" select="//$selector"/>
   
    <assert test="count( $targetNodeSet
                     [$field1 = current()/$field1]
                     [$field2 = current()/$field2]
                     [$field3 = current()/$field3]
                     [$field4 = current()/$field4]
                      ) = 1 " >
    The <value-of select="$name" /> id value of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />, <value-of select="$field4" />] should be used once only.
    </assert>
   </rule>
  
  
</pattern>  
  

<pattern id="five-field-unique-constraint" abstract="true"  >
   <title>Unique</title>
   <!-- Each <unique> has a rule, to select each selected content -->
   <rule context="$selector[$field1 or $field2 or $field3 or $field4 or $field5]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector"/>
   
    <assert test="count( $targetNodeSet
                           [$field1 = current()/$field1]
                     [$field2 = current()/$field2]
                     [$field3 = current()/$field3]
                     [$field4 = current()/$field4]
                     [$field5 = current()/$field5]
                      ) = 1 " >
    The <value-of select="$name" /> id value of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />,<value-of select="$field4" />, <value-of select="$field5" />] should be used once only.
    </assert>
   </rule>
  
</pattern>

Abstract pattern: key

Here is (a sketch of) the basic implementation for one-field keys.

<pattern id="one-field-key-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <key> has a rule, to select each selected content -->
   <rule context="$selector[$field]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$field]"/>
   
    <assert test="count( $targetNodeSet[$field =  current()/$field] ) = 1 " >
    The <value-of select="$name" /> key value of <value-of select="$field" /> should be used once only.
    </assert>

  </rule>
  
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> key
      on a <name/>  must be present</assert>
   </rule>
  
</pattern>

Next is the code for handling multiple arguments, using pairwise matching.

<pattern id="two-field-key-constraint" abstract="true"  >
   <title>Key</title>
  
    <let name="nodeSet" select="//$selector"/>
   <!-- Each <key> has a rule, to select each selected content -->
   <rule context="$selector[$field1][$field2]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="$nodeSet[$field1][$field2]"/>
   
    <assert test="count( $targetNodeSet
                        [$field1 = current()/$field1]
                      [$field2 = current()/$field2]
                       ) = 1 " >
    The <value-of select="$name" /> key value of [<value-of select="$field1" />, <value-of select="$field2" />] should be used once only.
    </assert>
 
 
   </rule>
  
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> key
      on a <name/>  must be present</assert>
    <report test="not($nodeSet[$field1])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'" />/<value-of select="'$field1'" />)</report>

  <report test="not($nodeSet[$field2])">There are no  <value-of select="$name" /> keys defined with all the fields.
   (<value-of select="'$selector'" />/<value-of select="'$field2'" />)</report>
 </rule>
  
</pattern>
  
  
<pattern id="three-field-key-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <key> has a rule, to select each selected content -->

    <let name="nodeSet" select="//$selector"/>
   <rule context="$selector[$field1][$field2][field3]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="$nodeSet[$field][$field2][field3]"/>
   
    <assert test="count( $targetNodeSet
                      [$field1 = current()/$field1]
                     [$field2 = current()/$field2]
                     [$field3 = current()/$field3]
                      ) = 1 " >
    The <value-of select="$name" /> key value of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />] should be used once only.
    </assert>
   
   </rule>
 
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> key
      on a <name/>  must be present</assert>
    <report test="not($nodeSet[$field1])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'" />/<value-of select="'$field1'" />)</report>

 <report test="not($nodeSet[$field2])">There are no  <value-of select="$name" /> keys defined with all the fields.
   (<value-of select="'$selector'" />/<value-of select="'$field2'" />)</report>
    <report test="not($nodeSet[$field3])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'" />/<value-of select="'$field3'" />)</report>
   </rule>
  

</pattern>
  

<pattern id="four-field-key-constraint" abstract="true"  >
   <title>Key</title>
  
    <let name="nodeSet" select="//$selector"/>
   <!-- Each <key> has a rule, to select each selected content -->
   <rule context="$selector[$field1][$field2][field3][field4]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="$nodeSet[$field][$field2][field3][field4]"/>
   
    <assert test="count( $targetNodeSet
                           [$field1 = current()/$field1]
                     [$field2 = current()/$field2]
                     [$field3 = current()/$field3]
                     [$field4 = current()/$field4]
                      ) = 1 " >

  The <value-of select="$name" /> key value of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />,  <value-of select="$field4" />] should be used once only.
    </assert>
   
   </rule>
 
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> key
      on a <name/>  must be present</assert>
    <report test="not($nodeSet[$field1])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field1'"/>)</report>
   <report test="not($nodeSet[$field2])">There are no  <value-of select="$name" /> keys defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$field2'"/>)</report>
    <report test="not($nodeSet[$field3])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field3'"/>)</report>
    <report test="not($nodeSet[$field4])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field4'"/>)</report>
   </rule>
  
</pattern>  
  

<pattern id="five-field-key-constraint" abstract="true"  >
   <title>Key</title>
    <let name="nodeSet" select="//$selector"/>
   <!-- Each <key> has a rule, to select each selected content -->
   <rule context="$selector[$field1][$field2][field3][field4][field5]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="$nodeSet[$field][$field2][field3][field4][field5]"/>
   
    <assert test="count( $targetNodeSet
                     [$field1 = current()/$field1]
                     [$field2 = current()/$field2]
                     [$field3 = current()/$field3]
                     [$field4 = current()/$field4]
                     [$field5 = current()/$field5]
                      ) = 1 " >
    The <value-of select="$name" /> key values of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />, <value-of select="$field4" />] should be used once only.
    </assert>
   
   </rule>
 
   <rule context="$selector">     
       <assert test="false()">All fields required for <value-of select="$name" /> key
      on a <name/>  must be present</assert>
 
     <report test="not($nodeSet[$field1])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field1'"/>)</report>
   <report test="not($nodeSet[$field2])">There are no  <value-of select="$name" /> keys defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$field2'"/>)</report>
    <report test="not($nodeSet[$field3])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field3'"/>)</report>
   <report test="not($nodeSet[$field4])">There are no  <value-of select="$name" /> keys defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$field4'"/>)</report>
    <report test="not($nodeSet[$field5])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field5'"/>)</report>
 
     </rule>
  
</pattern>     


<!-- Weaker test: five fields of key tested, plus that the others are there -->
<pattern id="weak-n-field-key-constraint" abstract="true"  >
   <title>Key</title>
    <let name="nodeSet" select="//$selector"/>
   <!-- Each <key> has a rule, to select each selected content -->
   <rule context="$selector[$field1][$field2][$field3][$field4][$field5][$andFields]">
     <assert test="true()">The values should be unique</assert>
   </rule>
  
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> key
      on a <name/>  must be present</assert>
       
    <report test="not($nodeSet[$field1])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field1'"/>)</report>
   <report test="not($nodeSet[$field2])">There are no  <value-of select="$name" /> keys defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$field2'"/>)</report>
    <report test="not($nodeSet[$field3])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field3'"/>)</report>
   <report test="not($nodeSet[$field4])">There are no  <value-of select="$name" /> keys defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$field4'"/>)</report>
    <report test="not($nodeSet[$field5])">There are no  <value-of select="$name" /> keys defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$field5'"/>)</report>
   </rule>
  
  
</pattern>

The last abstract pattern is a weaker test for more than five field keys: it merely tests that at least the first five fields have been declared.

Abstract pattern: keyref

Here is (a sketch of) the basic one-field abstract pattern implementation.

<pattern id="one-field-keyref-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <keyref> has a rule, to select each selected content -->
   <rule context="$selector[$refField]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$keyField]"/>
   
    <assert test="count( $targetNodeSet[$keyField =  current()/$refField] ) = 1 " >
    The <value-of select="$name" /> keyref value of <value-of select="$field" />
    should correspond to something.
    </assert>
   
    <report test="not($targetNodeSet)">There are no  <value-of select="$keyName" /> keys defined.</report>
   
   </rule>
  
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> keyref
      on a <name/>  must be present</assert>
      
   </rule>
  
</pattern>

Next is the code for handling multiple arguments, using pairwise matching. In this case, however, the match is between the key values and the keyref values. There is also some extra assertions in the fallthrough rule to allow more specific reporting of expected but missing fields: I haven;t tried to give particularly intricate diagnostics.

<pattern id="two-field-keyref-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <keyref> has a rule, to select each selected content -->
   <rule context="$selector[$refField1][$refField2]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$keyField][$keyField2]"/>
   
    <assert test="count( $targetNodeSet
                      [$keyField1 = current()/$refField1]
                       [$keyField2 = current()/$refField2]
                        ) = 1 " >
    The <value-of select="$name" /> keyref value of [<value-of select="$field1" />, <value-of select="$field2" />]
    should correspond to something.
    </assert>
   
    <report test="not($targetNodeSet)">There are no  <value-of select="$keyName" /> keys defined.</report>
   
   </rule>
  
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> keyref
      on a <name/>  must be present</assert>
     <report test="not($refField1)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField1'"/>)</report>
   <report test="not($refField2)">There are no  <value-of select="$name" />  references defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$refField2'"/>)</report>
   </rule>
  
</pattern>
  
  
<pattern id="three-field-keyref-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <keyref> has a rule, to select each selected content -->
   <rule context="$selector[$refField1][$refField2][refField3]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$keyField][$keyField2][keyField3]"/>
   
    <assert test="count( $targetNodeSet
                            [$keyField1 = current()/$refField1]
                       [$keyField2 = current()/$refField2]
                       [$keyField3 = current()/$refField3]
                        ) = 1 " >
    The <value-of select="$name" /> keyref value of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />] should correspond to something.
    </assert>
    <report test="not($targetNodeSet)">There are no  <value-of select="$keyName" /> keys defined.</report>
   
   </rule>
 
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> keyref
      on a <name/>  must be present</assert>
     <report test="not($refField1)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField1'"/>)</report>
   <report test="not($refField2)">There are no  <value-of select="$name" />  references defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$refField2'"/>)</report>
    <report test="not($refField3)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField3'"/>)</report>
   </rule>
  
</pattern>
  

<pattern id="four-field-keyref-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <keyref> has a rule, to select each selected content -->
   <rule context="$selector[$refField1][$refField2][refField3][refField4]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$keyField][$keyField2][keyField3][keyField4]"/>
   
    <assert test="count( $targetNodeSet
                          [$keyField1 = current()/$refField1]
                       [$keyField2 = current()/$refField2]
                       [$keyField3 = current()/$refField3]
                       [$keyField4 = current()/$refField4]
                        ) = 1 " >
    The <value-of select="$name" /> keyref value of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />,  <value-of select="$field4" />] should correspond to something.
    </assert>
    <report test="not($targetNodeSet)">There are no  <value-of select="$keyName" /> keys defined.</report>
   
   </rule>
 
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> keyref
      on a <name/>  must be present</assert>
     <report test="not($refField1)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField1'"/>)</report>
   <report test="not($refField2)">There are no  <value-of select="$name" />  references defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$refField2'"/>)</report>
    <report test="not($refField3)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField3'"/>)</report>
   <report test="not($refField4)">There are no  <value-of select="$name" />  references defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$refField4'"/>)</report>
   </rule>
  
</pattern>  
  

<pattern id="five-field-keyref-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <unique> has a rule, to select each selected content -->
   <rule context="$selector[$refField1][$refField2][$refField3][$refField4][$refField5]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$keyField][$keyField2][$keyField3][$keyField4][$keyField5]"/>
   
    <assert test="count( $targetNodeSet
                            [$keyField1 = current()/$refField1]
                       [$keyField2 = current()/$refField2]
                       [$keyField3 = current()/$refField3]
                       [$keyField4 = current()/$refField4]
                       [$keyField5 = current()/$refField5]
                        ) = 1 " >
   The <value-of select="$name" /> keyref values of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />, <value-of select="$field4" />] should correspond to something.
    </assert>
    <report test="not($targetNodeSet)">There are no  <value-of select="$keyName" /> keys defined.</report>
   
   </rule>
 
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> keyref
      on a <name/>  must be present</assert>
     <report test="not($refField1)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField1'"/>)</report>
   <report test="not($refField2)">There are no  <value-of select="$name" />  references defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$refField2'"/>)</report>
    <report test="not($refField3)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField3'"/>)</report>
   <report test="not($refField4)">There are no  <value-of select="$name" />  references defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$refField4'"/>)</report>
    <report test="not($refField5)">There are no  <value-of select="$name" />  references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField5'"/>)</report>
   </rule>
  
</pattern>     


<!-- Weaker test: five fields of keyref tested, plus that the others are there -->
<pattern id="weak-n-field-keyref-constraint" abstract="true"  >
   <title>Key</title>
   <!-- Each <keyref> has a rule, to select each selected content -->
   <rule context="$selector[$refField1][$refField2][$refField3][$refField4][$refField5][$andRefFields]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$keyField][$keyField2][$keyField3][$keyField4][$keyField5][$andKeyFields]"/>
   
    <assert test="count( $targetNodeSet
                      [$keyField1 = current()/$refField1]
                       [$keyField2 = current()/$refField2]
                       [$keyField3 = current()/$refField3]
                       [$keyField4 = current()/$refField4]
                       [$keyField5 = current()/$refField5]
                        )  &gt; 0 " >
    The <value-of select="$name" /> keyref value of [<value-of select="$field1" />, <value-of select="$field2" />,
    <value-of select="$field3" />, <value-of select="$field4" />, <value-of select="$field5" />, ...] should correspond to something.
    </assert>
    <report test="not($targetNodeSet)">There are no  <value-of select="$keyName" /> keys defined.</report>
   
   </rule>
 
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> keyref
      on a <name/>  must be present</assert>
     <report test="not($refField1)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField1'"/>)</report>
   <report test="not($refField2)">There are no  <value-of select="$name" />  references defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$refField2'"/>)</report>
    <report test="not($refField3)">There are no  <value-of select="$name" /> references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField3'"/>)</report>
   <report test="not($refField4)">There are no  <value-of select="$name" />  references defined with all the fields.
   (<value-of select="'$selector'"/>/<value-of select="'$refField4'"/>)</report>
    <report test="not($refField5)">There are no  <value-of select="$name" />  references defined with all the fields.
    (<value-of select="'$selector'"/>/<value-of select="'$refField5'"/>)</report>
   </rule>
  
</pattern>

Of course, being XSD there is always one more thing to cope with. I have avoided any treatment of nillibility and dynamic typing (xsi:type) in any of the material.

Multi-way key-refs

The XSD key refs only link to single keyed values. However, it is trivially possible to change the pattern to validate that the reference matches at least one key: non-unique keys.

<pattern id="one-field-multiway-keyref-constraint" abstract="true"  >
   <title>Key</title>
   <rule context="$selector[$refField]">
    <!-- Make a variable containing all the selected content -->
    <let name="targetNodeSet" select="//$selector[$keyField]"/>
   
    <assert test="count( $targetNodeSet[$keyField =  current()/$refField] ) &gt; 0 " >
    The <value-of select="$name" /> keyref value of <value-of select="$field" />
    should correspond to something.
    </assert>
    <report test="not($targetNodeSet)">There are no  <value-of select="$keyName" /> keys defined.</report>
   
   </rule>
  
   <rule context="$selector">
      <assert test="false()">All fields required for <value-of select="$name" /> keyref
      on a <name/>  must be present</assert>
   </rule>
  
</pattern>

Note: This is sketch code, not code I have debugged.