Search code examples
xmlxsd

Restriction on attributes depending on other attributes in XSD 1.1


in my XML file a "MultipleChoice" node defined in the following way:

<multipleChoice  numberOfChoices="" percentage="0"> text

corresponding to the needs of my XSD schema, my XSD definition of the mentioned is the following one:

<xs:element name="multipleChoice" type="MultipleChoiceType"/>
                    
<xs:complexType name="MultipleChoiceType" mixed="true">
  <xs:sequence>
    <xs:element  minOccurs="0" maxOccurs="unbounded" ref="choice"/>
  </xs:sequence>
  <xs:attribute name="numberOfChoices" type="xs:integer" use="required"/>
  <xs:attribute name="percentage" type="xs:integer" use="required"/>
  <xs:assert test="count(./choice) = @numberOfChoices" />
</xs:complexType>

What I need is to add another restriction to my "percentage" attribute:

  1. if in the "actor" attribute we have the string "Me", the "percentage" attribute has to be specified following the syntax of the point 2)
  2. there have to be as many integers as specified by the "numberOfChoices" attribute, all separated by only one white space.

For example: if "numberOfChoices"="3" then in "percentage" we need 3 integers, separated by only one white space, for example "percentage"= "30 40 30".

In case in the "actor" attribute there is something else than the string "Me", we don't care what's happening in the "numberOfChoices" and "percentage" attributes.

I need the "percentage" attribute to be required and I need that the following situation is accepted as well:

<multipleChoice actor="" bar="" points="0" numberOfChoices="3" percentage="">

Since in the "actor" attribute there is not the string "Me" I don't have to check what's in the "percentage" attribute. But it has to be there anyway.


Solution

  • First of all, in your example percentage is a xs:int attribute, you need to change it to a xs:int list (and/or add a regex if you really need only one withespace between values).

    Then you can use xpath tokenize function to divide and count percentage value (example: tokenize('1 2 3 4 5', '\s') returns ('1', '2', '3', '4', '5').

    Example schema:

    <?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:element name="multipleChoice" type="MultipleChoiceType"/>
    
        <xs:complexType name="MultipleChoiceType" mixed="true">
            <xs:sequence>
                <xs:element  minOccurs="0" maxOccurs="unbounded" name="choice" type="xs:string"/>
            </xs:sequence>
            <xs:attribute name="numberOfChoices" type="xs:integer" use="required"/>
            <!-- Percentage is now a list of xs:int -->
            <xs:attribute name="percentage" use="required">
                <xs:simpleType>
                    <xs:list itemType="xs:integer"/>
                </xs:simpleType>
            </xs:attribute>
            <!-- New actor attribute -->
            <xs:attribute name="actor" type="xs:string" use="required"/>
            <xs:assert test="count(./choice) = @numberOfChoices" />
            <!-- The count only needs to be satisfied if actor=Me -->
            <xs:assert test="@actor != 'Me' or count(tokenize(normalize-space(string(@percentage)),'\s')) =  @numberOfChoices"/>
        </xs:complexType>
    
    </xs:schema>
    

    Note that I've used normalize-space xpath function function because ' 1 2 3' is a valid xs:int list (if you want you could use a regex instead).