Search code examples
xmlxsdxsd-validationxml-validation

XSD to constrain XML element values to existing XML attribute values?


The goal is to constrain BEST_FRIEND_ID to have a value of an existing FRIEND_ID attribute in this XML:

friends.xml

<?xml version="1.0"?>
<FRIENDS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="friends.xsd">
    <FRIEND FRIEND_ID="1">
        <NAME>John</NAME>
        <BEST_FRIEND_ID>2</BEST_FRIEND_ID>
    </FRIEND>
    <FRIEND FRIEND_ID="2">
        <NAME>Kate</NAME>
        <BEST_FRIEND_ID>1</BEST_FRIEND_ID>
    </FRIEND>
</FRIENDS>

E.g. for given XML, validation should fail if I change a value of any BEST_FRIEND_IDelement to 3.

friends.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
    xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning">
    <xs:complexType name="FriendsType">
        <xs:sequence>
            <xs:element ref="FRIEND" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="FriendType">
        <xs:sequence>
            <xs:element ref="NAME"/>
            <xs:element ref="BEST_FRIEND_ID"/>
        </xs:sequence>
        <xs:attribute name="FRIEND_ID" type="xs:integer"/>
    </xs:complexType>

    <xs:element name="FRIENDS" type="FriendsType"/>
    <xs:element name="FRIEND" type="FriendType"/>
    <xs:element name="NAME" type="xs:string"/>
    <xs:element name="BEST_FRIEND_ID" type="xs:integer"/>
</xs:schema>

My idea was to use power of asserts (XSD 1.1). This is what I've figured out so far

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
    xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" vc:minVersion="1.1">
    <xs:complexType name="FriendsType">
        <xs:sequence>
            <xs:element ref="FRIEND" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="FriendType">
        <xs:sequence>
            <xs:element ref="NAME"/>
            <xs:element ref="BEST_FRIEND_ID"/>
        </xs:sequence>
        <xs:attribute name="FRIEND_ID" type="xs:integer"/>
    </xs:complexType>
    <xs:complexType name="IdConstrain">
        <xs:simpleContent>
            <xs:extension base="xs:integer">
                <xs:assert test="$value = @FRIEND_ID"/>
            </xs:extension>
        </xs:simpleContent>
    </xs:complexType>

    <xs:element name="FRIENDS" type="FriendsType"/>
    <xs:element name="FRIEND" type="FriendType"/>
    <xs:element name="NAME" type="xs:string"/>
    <xs:element name="BEST_FRIEND_ID" type="IdConstrain"/>
</xs:schema>

and the validation fails:

Severity: error Description: cvc-assertion: Assertion evaluation ('$value = @FRIEND_ID') for element 'BEST_FRIEND_ID' on schema type 'IdConstrain' did not succeed. Start location: 6:10 End location: 6:25 URL: http://www.w3.org/TR/xmlschema11-1/#cvc-assertion

I have also tried with something like BEST_FRIEND_ID[contains(., @FRIEND_ID)] but it does not work as well.


Solution

  • You should use the following to check that every best friend id is an existing friend id. You should also check that the bestfriend id is not equal to itself:

    <?xml version="1.0" encoding="UTF-8"?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
        elementFormDefault="qualified"
        xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"
        vc:minVersion="1.1">
    
        <xs:complexType name="FriendsType">
            <xs:sequence>
                <xs:element ref="FRIEND" maxOccurs="unbounded"/>
            </xs:sequence>
            <xs:assert test="every $bfid in FRIEND/BEST_FRIEND_ID
                satisfies $bfid = (FRIEND/@FRIEND_ID)"/>
            <!-- Optional check best friend is not equals to itself -->
            <xs:assert test="every $fr in FRIEND
                satisfies $fr/@FRIEND_ID != $fr/BEST_FRIEND_ID"/>
        </xs:complexType>
    
        <xs:complexType name="FriendType">
            <xs:sequence>
                <xs:element ref="NAME"/>
                <xs:element ref="BEST_FRIEND_ID"/>
            </xs:sequence>
            <xs:attribute name="FRIEND_ID" type="xs:integer"/>
        </xs:complexType>
    
        <xs:element name="FRIENDS" type="FriendsType"/>
        <xs:element name="FRIEND" type="FriendType"/>
        <xs:element name="NAME" type="xs:string"/>
        <xs:element name="BEST_FRIEND_ID" type="xs:integer"/>
    </xs:schema>
    

    You could also check is bestfriend id is diferent to the friend id inside FRIEND tag instead of the FRIENDS tag.

    <xs:complexType name="FriendType">
        <xs:sequence>
            <xs:element ref="NAME"/>
            <xs:element ref="BEST_FRIEND_ID"/>
        </xs:sequence>
        <xs:attribute name="FRIEND_ID" type="xs:integer"/>
        <xs:assert test="@FRIEND_ID != BEST_FRIEND_ID"/>
    </xs:complexType>