3

I want to use the XSD1.1 assert feature to validate Elements on content level. (To be more precise, I want to check the existence of content-combinations in EDIFACT represented in XML, but that's not the main point...)

To test my XPaths I have constructed the following mini-test-scenario:

XML

<root>
    <group>
        <elem1>test1</elem1>
        <elem2>test2</elem2>
    </group>
    <group>
        <elem1>something1</elem1>
        <elem2>something2</elem2>
    </group>
    <group>
        <elem1>other1</elem1>
        <elem2>other2</elem2>
    </group>
</root>

The requirement is: I want to check, that I have the combination of test1 + test2 string, and the combination of something1 and something2 string. There may be groups like the other1 + other2 group, which can be there, but I don't care about. The order of the three groups here also should have no influence.

The XSD I have to test is:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:element name="root">
    <xsd:complexType>
      <xsd:sequence>

        <xsd:element name="group" minOccurs="1" maxOccurs="unbounded">
          <xsd:complexType>
            <xsd:sequence>
              <xsd:element name="elem1" minOccurs="1">
              </xsd:element>
              <xsd:element name="elem2" minOccurs="1">
              </xsd:element>
            </xsd:sequence>
          </xsd:complexType>
        </xsd:element>

      </xsd:sequence>
      <xsd:assert test="(count(./group/elem1/text() = 'test1') > 0 
                         and count(./group/elem2/text() = 'test2') > 0) 
                         and (count(./group/elem1/text() = 'something1') > 0 
                         and count(./group/elem2/text() = 'something2') > 0)"/>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

The interesting bit is:

(count(./group/elem1/text() = 'test1') > 0 
and count(./group/elem2/text() = 'test2') > 0) 
and (count(./group/elem1/text() = 'something1') > 0 
and count(./group/elem2/text() = 'something2') > 0)

or to break it down:

count(./group/elem1/text() = 'test1') > 0

My problem is: The expression (count to be more specific) returns true, even if the strings don't match. Let's say, I test against "test1", but my string is "test":

./group/elem1/text() = 'test1'

in it self works. It returns true or false correctly. But using count on it does not work. (Seems to always return true)

I assume, count is not the right solution here, the thing is, I don't want to test each group on "it is exactly" but after all groups "does this and this specific combination occur at least once" within all the repetitions of the groups.

I am testing this on Saxon 9 EE, but the XPath has the same behavior on other XPath implementations as well.

Any help would be greatly appreciated.

Thank you, e


edit:

After getting this to work with the help of Mads Hansen and Michael Kay (Thank you!) I had one last hurdle to jump:

Consider this case:

<root>
    <group>
        <elem1>test1</elem1>
        <elem2>WRONG</elem2>
    </group>
    <group>
        <elem1>WRONG</elem1>
        <elem2>test2</elem2>
    </group>
</root>

with this XPath

count(group[elem1/text() = 'test1' and elem2/text() = 'test2']) > 0)

This now leads to the above example being NOT Valid (as I would like to), whilst the original XPath I had validated the above, since it didn't check within .

eee
  • 45
  • 6

2 Answers2

3

You need to adjust the XPath to filter the items you are looking for, and then count what is left. Your current expression is evaluating whether any of the group/elem1/text() nodes are equal to test1, which will be either true() or false(), and then you are counting the boolean value.

Use a predicate to test the text() values and count how many satisfy the condition:

count(./group/elem1/text()[.='test1'])
Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
  • 1
    Is there difference with `count(./group/elem1[text() = 'test1'])` ? – splash58 May 10 '16 at 15:09
  • 4
    Note, you can simplify `test="count(./x/y[text() = 'TTT']) > 0"` to `test="x/y = 'TTT'"`. – Michael Kay May 10 '16 at 22:19
  • MadsHansen, splash58 @MichaelKay thank you for your answer. I now receive the message "To use XPath 3.1 syntax, you must configure the XPath parser to handle it" from Saxon, but I have not found a option to enable it in the documentation. I currently call the validation this way: `/opt/..../_jvm/bin/java -cp "/home/..../test_saxon_ee/saxon9ee.jar" com.saxonica.Validate -t -xsdversion:1.1 -xsd:assert.xsd assert.xml` . – eee May 13 '16 at 13:50
  • @eee, exactly what XPath expression triggers this message? – Michael Kay May 13 '16 at 20:17
  • @MichaelKay I call the example from my question with this XPath: `` – eee May 17 '16 at 11:59
  • 1
    The "/" before the predicate "[text() = ...]" is wrong and should be removed. The error message is because in XPath 3.1 a "[" after the "/" becomes legal, though it doesn't mean what you wanted it to mean. – Michael Kay May 17 '16 at 22:24
  • Thank you @MichaelKay , now the validation is working. Found one last hurdle to jump, will put it in my initial question for better readability. – eee May 19 '16 at 12:09
0

The Answer:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:element name="root">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="group" minOccurs="1" maxOccurs="unbounded">
                    <xsd:complexType>
                        <xsd:sequence>
                            <xsd:element name="elem1" minOccurs="1">
                            </xsd:element>
                            <xsd:element name="elem2" minOccurs="1">
                            </xsd:element>
                        </xsd:sequence>
                    </xsd:complexType>
                </xsd:element>

                </xsd:sequence>
                <xsd:assert test="(count(group[elem1/text() = 'test1' 
                                and elem2/text() = 'test2']) > 0) 
                                and (count(group[elem1/text() = 'something1' 
                                and elem2/text() = 'something2']) > 0 )"/>
            </xsd:complexType>
        </xsd:element>
    </xsd:schema>

Will validate:

<root>
    <group>
        <elem1>test1</elem1>
        <elem2>test2</elem2>
    </group>
    <group>
        <elem1>test1</elem1>
        <elem2>test4</elem2>
    </group>
    <group>
        <elem1>something1</elem1>
        <elem2>something2</elem2>
    </group>
    <group>
        <elem1>other1</elem1>
        <elem2>other2</elem2>
    </group>
</root>
eee
  • 45
  • 6