views:

1021

answers:

3

I'm trying to define some foreign key constraints on an XML schema using xs:key and xs:keyref definitions. I want the structure of the document to be hierarchical in the following way:

<?xml version="1.0" encoding="UTF-8"?>
<tns:root xmlns:tns="http://www.example.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.example.org/ SampleSchema.xsd ">
  <parent parentKey="parent1">
    <child childKey="child1"/>
    <child childKey="child2"/>
  </parent>
  <parent parentKey="parent2">
    <child childKey="child1"/>
    <child childKey="child2"/>
  </parent>
  <referrer parentRef="parent1" childRef="child2"/>
</tns:root>

A each parent has a (globally) unique key, defined by parentKey. Each child has key defined by childKey, but childKey is only unique within the scope of its containing parent.

There is then a list of referrers with foreign key references to a particular parent and child.

I'm able to define the keys as I want, simply by putting them on the correct element: the parentKey constraint on the root element, and the childKey constraint on the parent element. I can also define the keyref to parentKey without difficulty.

The problems arise when trying to define a keyref to childKey. I tried defining a simple keyref on the root element to childKey, but that doesn't work since I see no way to select only the child elements under the proper parent subtree. (The Eclipse validator, at least, always simply validates against the content of the last parent subtree in the document...).

I then tried defining a composite key (on root), with:

  • selector = parent
  • field = @parentKey
  • field = child/@childKey

This fails if there is more than one child defined under the parent. That is the correct behavior based on the XSD 1.1 spec, section 3.11.4, item 3, which states that the key has to match at most one node per field definition.

Just to reiterate: if I force childKeys to be globally unique, this is easy to implement; the difficulty is around referencing locally unique childKeys.

Any XSD masters out there have an idea?

For reference, here is a sample XSD, with a failed childKey keyref commented out:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/" xmlns:tns="http://www.example.org/" elementFormDefault="unqualified">

    <element name="root">
        <complexType>
            <sequence>
                <element name="parent" maxOccurs="unbounded" minOccurs="1">
                    <complexType>
                        <sequence>
                            <element name="child" maxOccurs="unbounded" minOccurs="1">
                                <complexType>
                                    <attribute name="childKey" type="string" use="required"/>
                                </complexType>
                            </element>
                        </sequence>
                        <attribute name="parentKey" type="string" use="required"/>
                    </complexType>
                    <key name="childKeyDef">
                        <selector xpath="child"/>
                        <field xpath="@childKey"/>
                    </key>
                </element>
                <element name="referrer" maxOccurs="unbounded" minOccurs="1">
                    <complexType>
                        <attribute name="parentRef" type="string"/>
                        <attribute name="childRef" type="string"/>
                    </complexType>
                </element>
            </sequence>
        </complexType>
        <key name="parentKeyDef">
            <selector xpath="parent"/>
            <field xpath="@parentKey"/>
        </key>
        <keyref name="parentKeyRef" refer="tns:parentKeyDef">
            <selector xpath="referrers"/>
            <field xpath="@parentRef"/>
        </keyref>
<!--        <keyref name="childKeyRef" refer="tns:childKeyDef">-->
<!--            <selector xpath="referrers"/>-->
<!--            <field xpath="@childRef"/>-->
<!--        </keyref>-->
    </element>
</schema>
+1  A: 

How about referring to the parent from the child? Even if many children, there will only be one parent, and combining the (parent,child) creates a globally unique key, even though the child key is only unique within its parent:

  <key name="childKeyDef">
    <selector xpath="child"/>
    <field xpath="@childKey"/>
    <field xpath="../@parentKey"/>
  </key>

This doesn't work in xmllint, even though the spec doesn't seem to explicitly disallow this for fields - only for selectors: 3.11.4, (2) says the selector can't be an ancestor (it can only be the context node or descendants.)

Ah, here's the nail in the coffin (looking at specific syntax): the XPath expressions allowed are very limited, and simply don't include ".." http://www.w3.org/TR/xmlschema-1/#c-fields-xpaths

So, sorry, this doesn't answer your question, but maybe it will give you some ideas.

13ren
Yeah; I had tried that myself and made the same discovery about the strict limitations on selector and field xpath expressions. Nice idea in theory, though.Thanks for the efforts!
Aron
I conjecture that in combining this constraint, together with the other constraint you found (for an xpath to refer to only one node), there is a proof that what you want to do is impossible in XSD - I bet the proof (if there is one) is very simple, but I can't quite see it.
13ren
+1  A: 

An ugly solution is to change your XML format, so that the parentKey is included in each child, like this:

<parent>
  <child parentKey="parent1" childKey="child1"/>
  <child parentKey="parent1" childKey="child2"/>
</parent>

I think your situation is very legitimate, and I'd expect there to be a way to do this - why not try the xml-dev mailing list? It's become noisy last I checked, but some of the creators of xml were still hanging out there.

13ren
Yeah, that would be a possibility, but I think it causes more problems than it solves...as you say, ugly.My design has moved away from this structure for external reasons, so it's more of an academic exercise at this point, but maybe I'll give the list a try. Thanks for the idea!
Aron
A: 

I had a similar question: http://stackoverflow.com/questions/1348753/xml-schema-key-with-multiple-fields

I decided that the best approach for me was to reorder the XML to allow for the scope to be determined by locality instead of enforcing a key with two fields.

In your scenario, if you move referrer inside the parent, this will allow the scope to be set to reference the appropriate child. You would then have the referrer element refer to the outer scope to the element that it needs to reference.

It is a little hard to determine if this is an acceptable solution because your problem appears to be a little abstracted. In my problem, described in my question, I was dealing with questions, answers, and user responses. I originally was trying to validate if a user's response was actually a valid answer; my first approach involved a same technique you are using. My final solution involved moving the response to inside the question, and then referring to the user.

My XML BEFORE:

<?xml version="1.0" encoding="utf-8"?>
<survey>
  <user id="bob">
    <response questionIdRef="q101">yes</response>
    <response questionIdRef="q102">white</response>
  </user>
  <user id="jane">
    <response questionIdRef="q101">no</response>
    <response questionIdRef="q102">blue</response>
  </user>
  <question id="q101">
    <text>Do you like the color red?</text>
    <answer>yes</answer>
    <answer>no</answer>
  </question>
  <question id="q102">
    <text>What is your favorite color?</text>
    <answer>red</answer>
    <answer>blue</answer>
    <answer>white</answer>
    <answer>yellow</answer>
  </question>
</survey>

My XML AFTER:

<?xml version="1.0" encoding="utf-8"?>
<survey>
  <user id="bob" />
  <user id="jane" />
  <question id="q101">
    <text>Do you like the color red?</text>
    <answer>yes</answer>
    <answer>no</answer>
    <response userIdRef="bob">yes</response>
    <response userIdRef="jane">no</response>
  </question>
  <question id="q102">
    <text>What is your favorite color?</text>
    <answer>red</answer>
    <answer>blue</answer>
    <answer>white</answer>
    <answer>yellow</answer>
    <response userIdRef="bob">white</response>
    <response userIdRef="jane">blue</response>
  </question>
</survey>
Chris