Home > other >  XPath test if any ancestor has a specific child element
XPath test if any ancestor has a specific child element

Time:08-25

I am still struggling with my XML Schema and I just do not seem to be able to get it right. I need to write an XSLT assertion to ensure that every nested <instruction> element has either a child element <stroke> or any ancestor has a <stroke> element (XOR).

This is an example of a valid XML:

<program>
    <instruction>
        <lengthAsDistance>100</lengthAsDistance>
        <stroke>
            <standardStroke>backstroke</standardStroke>
        </stroke>
    </instruction>
   <instruction>
        <repetition>
            <repetitionCount>3</repetitionCount>
            <stroke>
                <standardStroke>freestyle</standardStroke>
            </stroke>
            <instruction>
                <lengthAsDistance>100</lengthAsDistance>
            </instruction>
            <instruction>
                <lengthAsDistance>200</lengthAsDistance>
            </instruction>
            <instruction>
                <repetition>
                    <repetitionCount>4</repetitionCount>
                    <instruction>
                        <lengthAsDistance>100</lengthAsDistance>
                    </instruction>
                </repetition>
            </instruction>
        </repetition>
    </instruction>
</program>

I then tried:

<xs:assert test="(stroke and not(ancestor::stroke) or not(stroke) and ancestor::stroke) or (.. is root() and repetition)"/>

The test for the child element <stroke> works fine, but the test for any ancestors having a <stroke> child does not work as expected. The test for all <instruction> directly under the root <program> also does not seem to work.

Thank you up front for any help that you can offer.

CodePudding user response:

I think in Schematron, where XPath can be used for assertions, you would want something like

<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt3">
    <pattern>
        <rule context="instruction[not(descendant::instruction | ancestor::instruction)]">
            <assert test="stroke">There is no associated stroke element.</assert>
        </rule>
        <rule context="instruction//instruction">
            <assert test="stroke or ancestor::repetition/stroke">There is no associated stroke element.</assert>
        </rule>
    </pattern>
</schema>

For XSD 1.1 assertions, I am not sure how freely you can navigate around ancestors, with Saxon EE the following works for me:

<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="program">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="instruction"/>
      </xs:sequence>
      <xs:assert id="stroke"
        test="every $instruction in instruction
        satisfies (
        (
        not($instruction//instruction) and $instruction/stroke
        )
        or
        (every $nested-instruction in $instruction//instruction
        satisfies $nested-instruction/ancestor::repetition/stroke)
        )"/>
    </xs:complexType>
  </xs:element>

CodePudding user response:

It strikes me as possible that you might actually want to ensure that an instruction has exactly one stroke element among its children and the children of any of its ancestors. If that is the assertion you want, then the following XPath should do it:

count(ancestor-or-self::*/stroke)=1
  • Related