-->

How do I extend a base schema with custom elements

2019-03-16 03:24发布

问题:

Given an XSD as follows:

<xs:schema elementFormDefault="qualified" attributeFormDefault="unqualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"  xmlns:std="http://..." targetNamespace="...">
  <xs:element name="SomeRootNode" type="std:SomeRootNodeType" />
  ...
</xs:schema>

that defines some elements which allow any child from a different namespace.

I want to extend this schema with my own and insert child elements and attributes of specific elements in the base document. For example, myElementX or myAttributeY must have parent node std:SomeRootNode. The combined document should then be capable of allowing any third parties to continue to extend the document in any way already allowed by the base schema, but for elements and attributes from my namespace I want to validate that all elements and attributes have the correct parent nodes and appear only in the allowed places in the base document.

How can this be achieved?

I hope there is a clean solution that does not resort to redefining the base schema I am extending. I want to be able to easily adapt if new versions of the base schema are released. I do not want to have to change mine with new redefinitions each time a new version of the base document is released (unless it has breaking changes for my design).

回答1:

When it comes to extending existing XML Schema, there are a few options.

Using the base schema

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Root" type="RootType" />
    <xs:complexType name="RootType">
        <xs:sequence>
            <xs:element name="OriginalContent" />
        </xs:sequence>
    </xs:complexType>
</xs:schema>

Extension/Restriction

You can extend/restrict a type, which effectively creates a new type with additional/less elements/attributes, however there is no way to force the use of this new type. The creator of the XML can tell you they are using their new type using the xsi:type="MyCustomType", but you can not insist its used.

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:include schemaLocation=".\Root.xsd" />
    <xs:complexType name="MyNewRoot">
        <xs:complexContent>
            <xs:extension base="RootType">
                <xs:sequence>
                    <xs:element name="AdditionalElement" type="xs:string" />
                </xs:sequence>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>
</xs:schema>

Sample XML File

<?xml version="1.0" encoding="utf-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="Extension.xsd" 
      xsi:type="MyNewRoot">
    <OriginalContent />
    <AdditionalElement/>
</Root>

Redefine

The other alternative is to use <redefine>. Basically replaces the definition of RootType, so wherever RootType appears our new version must now be used.

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:redefine schemaLocation="root.xsd">
        <xs:complexType name="RootType">
            <xs:complexContent>
                <xs:extension base="RootType">
                    <xs:sequence>
                        <xs:element name="MyContent" type="xs:string" />
                    </xs:sequence>
                </xs:extension>
            </xs:complexContent>
        </xs:complexType>
    </xs:redefine>
</xs:schema>

Sample XML File

<?xml version="1.0" encoding="utf-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Redefine.xsd">
    <OriginalContent></OriginalContent>
    <MyContent>stuff</MyContent>
</Root>

Any

Another solution is to include a xs:any in the base elements definition. However, this is not possible if you don't control the base schema.

Open Content

** Only available in XSD 1.1 **

Open Content was added specifically to allow you to create open schemas, it has 2 'mode's interleave and suffix. When set to interleave additional elements (matching the contained xs:any clause) can be interleaved through out the element (before, between and after any of the existing elements). When in suffix mode then additional elements (matching the contained xs:any clause) can be added after the existing elements.

OpenContent can be applied to specific complexTypes or it can be applied at schema level, in which it applies to all the elements declared within it.

If you are using XSD 1.1, this is definitely the way to go in order to make your schemas extensible, however XSD 1.1 is still not well supported.

<xs:complexType name="BookType">
    <xs:openContent mode="interleave">
        <xs:any />
    </xs:openContent>
    <xs:sequence>
        <xs:element name="Title"  type="xs:string" />
        <xs:element name="Author" type="xs:string" maxOccurs="unbounded" />
        <xs:element name="ISBN"   type="xs:string" />
    </xs:sequence>
</xs:complexType>

It should also be noted that if you interleave elements between runs of existing elements (i.e. Title, Author, NewElement, Author, ISBN), then most parsers will treat the second Author as a 'new' element and validate it using the openContent rules not the xs:element name="Author" type="xs:string", furthermore if the Author had a minOccurs of 2, this clause may fail as its seeing 1xAuthor, 1xNewElement, 1xAuthor and the first 1xAuthor does not for full the minoccurs clause.

Summary

All these methods have there ups and downs, but having a good XML Editor, makes it much easier to figure out what's going on.

I recommend Liquid XML Studio which is made by my company, as it has a good XSD Editor, can create sample XML from your schemas, making it easy to see the result of your work, also the XML Intellisense makes it easy to see the valid options.