Validating Linked Documents

By Michael Kay on August 13, 2026 at 09:00p.m.

A chance remark at Balisage 2026 (RIP) led me to consider the question: can you write an XSD schema to validate a linked set of XML documents, rather than just a single document?

Here's a use case: the test suite for XQuery/XPath 4.0 consists of a catalog file, which lists 800 or so linked test sets. To make it more specific, the catalog contains many lines rather like this:

<test-set name="bin-shift" file="bin/shift.xml"/>

Both the top-level catalog and the individual test-set files are governed by XSD schemas. Currently we validate the collection of documents using an XQuery query along the lines of:

//fots:test-set/@file ! (validate{doc(resolve-uri(., base-uri(..)))})

And of course other solutions are possible, for example using XProc. But it occurred to me, can we write a schema for the catalog file that includes an XSD 1.1 assertion that invokes validation on the referenced test-set files? And can we use a different schema to validate the test-set files, different from the one we use for the catalog?

I'll give you the answer first, before describing the journey to get there. I've made it work like this


            
<xs:annotation>
   <xs:appinfo>
       <saxon:param name="test-set-validator" 
                    select="xsd-validator(map{'schema-location': 'test-set-schema.xsd',
                                              'use-imported-schema': false(),
                                              'return-error-details': true()})"/>
   </xs:appinfo>
</xs:annotation>
    
<xs:element name="test-set">
  <xs:complexType>
    <xs:complexContent>
      <xs:extension base="baseType">
        <xs:attributeGroup ref="nameAttr"/>
        <xs:attributeGroup ref="fileAttr"/>
        <xs:assert test="
           let $doc := doc(resolve-uri(@file, base-uri(.)), 
                           {'xsd-validation':'skip'})
           let $validity := $test-set-validator($doc)
           return if ($validity?is-valid)
                  then true()
                  else (false(), `In {@file}: {$validity?error-details?message => string-join('. ')}`)"/>
      </xs:extension>
    </xs:complexContent>                        
  </xs:complexType>
</xs:element>

This won't run out-of-the-box in Saxon 13.0: though most of the ingredients are present I had to make some tweaks to make it all hang together, which I'll describe as I go along. It should work as written in 13.1.

Firstly, the saxon:param element has been available for quite a while as a Saxon extension. I'm using it here not to parameterize the validation, but rather to set up a global variable. The variable is the result of the 4.0 xsd-validator function: a new higher-order function that, given a schema, returns a function that can be used to validate instance documents against this schema. Putting this validator in a global variable means we only need to compile the schema for the test-set documents once.

The expression in the xs:assert/@test attribute contains several features of note:

So with Saxon 13.1, you will be able to invoke validation of a linked set of documents, using the same schema or different schemas, in a single call of the validation API.