Javier
Javier

Reputation: 363

Ordered/unordered definition of an XML element's children using RELAX NG compact syntax

I want to use RELAX NG compact syntax to validate an XML element whose children are one, two, three or n of a set of n specific elements. For instance, if the element is 'Layout' and there is a set of three specific elements: 'top', 'center' and 'bottom', the following XML element definitions would be valid:

<Layout>
    <top/>
    <center/>
    <bottom/>
</Layout>

<Layout>
    <top/>
</Layout>

<Layout>
    <center/>
</Layout>

<Layout>
    <bottom/>
</Layout>

<Layout>
    <top/>
    <center/>
</Layout>

I want to know how to write two patterns: 1) Allow the children to be in any order. 2) Restrict the children to be in a specific order (e.g.: top, center, bottom).

The solution I have so far for the XML example and the ordered pattern is:

element Layout {
   (
      element top { text },
      element center { text }?
   ) |(
      element top { text }?,
      element center { text }?,
      element bottom { text }
   ) |(
      element center { text }
   )
}

I don't have a good solution for more than 3 elements and/or for an unordered pattern.

Upvotes: 0

Views: 818

Answers (3)

Miro
Miro

Reputation: 11

I am not sure if it will help you but we used this. Our problem: Two elements, any order, both can appear 1 to n amount of times Solution: <interleave> <oneOrMore> <ref name="a.class"/> </oneOrMore> <oneOrMore> <ref name="b.class"/> </oneOrMore> </interleave> For ordered just remove <interleave>. For 0 to n occurences use <zeroOrMore>.

Upvotes: 1

John Cowan
John Cowan

Reputation: 1567

You can use "element top {text}?, element center {text}?, element bottom {text}?" to get zero or one of each in the stated order

Or use "element top{text}? & element center {text}? & element bottom {text}?" to get zero or one of each in any order.

Both these patterns are extensible to arbitrarily many elements.

Upvotes: 0

Nic Gibson
Nic Gibson

Reputation: 7143

These was discussed recently on the RelaxNG list (I asked the question as it happens). The restriction you are trying to codify in the first case is something on the lines of "allow zero or one each of a set of elements" I think. There is no neat way to do this but you can do something. If you have a lot of possible child elements, this is fiddly to say the least (I have eight in my schema and that's enough pain honestly). Something like this should work for case 1.

I've used named patterns to make this more legible.

start = e.layout
e.top = element top { text }
e.center = element center { text } 
e.bottom = element bottom { text }
e.layout = element Layout {
    (e.top & e.center? & e.bottom?) |    
    (e.top? & e.center & e.bottom?) |
    (e.top? & e.center? & e.bottom) 
}

That requires any one of the elements plus zero or one of each of the other two.

In order to enforce a sequence, you can do a similar thing but use the ',' operator instead as you have done :

start = e.layout
e.top = element top { text }
e.center = element center { text } 
e.bottom = element bottom { text }
e.layout = element Layout {
    (e.top, e.center?) |
    (e.top, e.center, e.bottom?) |        
    (e.top, e.center, e.bottom) |    
    (e.top, e.bottom?)
}

If you were going to get any more complex than this I would seriously consider writing a much simpler that simply matched the appropriate elements and then use a Schematron rule to enforce the counts. So, for your second requirement:

<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0" 
         xmlns:sch="http://purl.oclc.org/dsdl/schematron">
  <start>
    <ref name="e.Layout"/>
  </start>
  <define name="e.top">
    <element name="top">
      <text/>
    </element>
  </define>
  <define name="e.center">
    <element name="center">
      <text/>
    </element>
  </define>
  <define name="e.bottom">
    <element name="bottom">
      <text/>
    </element>
  </define>
  <define name="e.Layout">
    <sch:pattern name="check no more than one of each">
      <sch:rule context="Layout/*">
        <sch:assert test="count(../*[local-name(.) eq local-name(current())]) = 1">You may only have one <name/> element as a child of Layout.</sch:assert>
      </sch:rule>
    </sch:pattern>
    <element name="Layout">
      <oneOrMore>
        <group>
          <ref name="e.top"/>
          <ref name="e.center"/>
          <ref name="e.bottom"/>
        </group>
      </oneOrMore>
    </element>
  </define>
</grammar>

or, in compact syntax (annotations are not pretty in rnc):

namespace sch = "http://purl.oclc.org/dsdl/schematron"

start = e.Layout
e.top = element top { text }
e.center = element center { text }
e.bottom = element bottom { text }
[
  sch:pattern [
    name = "check no more than one of each"
    "\x{a}" ~
    "      "
    sch:rule [
      context = "Layout/*"
      "\x{a}" ~
      "        "
      sch:assert [
        test = "count(../*[local-name(.) eq local-name(current())]) = 1"
        "You may only have one " rng:name [ ] " element as a child of Layout"
      ]
      "\x{a}" ~
      "      "
    ]
    "\x{a}" ~
    "    "
  ]
]
e.Layout = element Layout { (e.top, e.center, e.bottom)+ }

Upvotes: 1

Related Questions