Search code examples
c#xmlxsd

How to extend complex Type in namespace without changing the target namespace


Referencing this: Other post I am trying to do basically the same thing as the OP from this other post; to extend a complexType with my own added attributes, without changing the primary namespace. I tried following the answer reply as closely as possible but am not having success.

Following the last post I created 4 files: 3 xsd's and 1 xml.

original.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns="http://bookshop.com" 
targetNamespace="http://bookshop.com"
elementFormDefault="qualified">

<xs:complexType name="Book">
    <xs:sequence>
        <xs:element name="Author" type="String32" 
            minOccurs="1" maxOccurs="1" />
        <xs:element name="Title" type="String32" 
            minOccurs="1" maxOccurs="1" />
    </xs:sequence>
</xs:complexType>

<xs:element name="Book" type="Book"/>

<xs:simpleType name="String32">
    <xs:restriction base="xs:string"></xs:restriction>
</xs:simpleType>
</xs:schema>

added.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
elementFormDefault="qualified"
xmlns="http://custombookshop.com" 
xmlns:bs="http://bookshop.com" 
targetNamespace="http://custombookshop.com"> 

<xs:import namespace="http://bookshop.com" schemaLocation="original.xsd"/>
<xs:element name="Publisher" type="bs:String32" />
</xs:schema>

newMainfile.xsd

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
xmlns="http://bookshop.com"
targetNamespace="http://bookshop.com"
xmlns:cs="http://custombookshop.com">

<xs:import schemaLocation="added.xsd" namespace="http://custombookshop.com"/>
<xs:redefine schemaLocation="original.xsd">
    <xs:complexType name="Book">
        <xs:complexContent>
            <xs:extension base="Book">
                <xs:sequence>
                    <xs:element ref="cs:Publisher" minOccurs="1" maxOccurs="1" />
                </xs:sequence>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>
</xs:redefine>
</xs:schema>

test.xml

<?xml version="1.0"?>
<Book xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
xmlns="http://bookshop.com"
targetNamespace="http://bookshop.com"
xs:schemaLocation="newMainfile.xsd"
xmlns:cs="http://custombookshop.com">
<Author>Frank Herbert</Author>
<Title>Dune</Title>
<cs:Publisher>Ace</cs:Publisher>
</Book>

However, this does not produce something I can validate. After adding the xml and xsd's to the working directory of my c# project running the following code does not work.

var path = @"test.xml";
        XmlSchemaSet schema = new XmlSchemaSet();
        schema.Add("", "newMainfile.xsd");
        XmlReader rd = XmlReader.Create(path);
        XDocument doc = XDocument.Load(rd);
        doc.Validate(schema, ValidationEventHandler);

An exception occurs with

"System.Xml.Schema.XmlSchemaException: 'The targetNamespace parameter '' should be the same value as the targetNamespace 'http://bookshop.com' of the schema.'"

Substituting the namespace "http://bookshop.com" for the "" in schema.Add("", "newMainfile.xsd"); also does not validate and instead produces error.

System.Xml.Schema.XmlSchemaException: ''SchemaLocation' must successfully resolve if <'redefine'> contains any child other than <'annotation'>.'

Am I missing some sort of vital step? Appreciate your time and responses.


Solution

  • So the issue is that the default resolver of the XmlSchemaSet class doesn't actually support relative file paths. To get this to work you can either:

    • Change the schemaLocation attribute to point to a full file path like <xs:redefine schemaLocation="file://C:/temp/whatever/original.xsd"> or
    • Pre-load all required XmlSchemas through the use of an instance of XmlPreloadedResolver.
    using System;
    using System.IO;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.Schema;
    // you will need this namespace for the preloaded resolver class
    using System.Xml.Resolvers;
    
    void Main()
    {
        // put all your XSD files to a single folder path
        var basePath = @"C:\temp\so76168480";
        var path = Path.Combine(basePath, @"test.xml");
        // see method definition below 
        var preloader = PreLoadXmlSchemas(basePath);
        XmlSchemaSet schemaSet = new XmlSchemaSet() {
            XmlResolver = preloader
        };
        
        schemaSet.Add("http://bookshop.com", Path.Combine(basePath, "newMainfile.xsd"));
        
        XmlReader rd = XmlReader.Create(path);
        XDocument doc = XDocument.Load(rd);
        
        doc.Validate(schemaSet, (i, e) => {
            // should be ok
        });
    }
    
    public static XmlPreloadedResolver PreLoadXmlSchemas(string directoryPath)
    {
        if (directoryPath == null) throw new ArgumentNullException(nameof(directoryPath));
    
        var directoryInfo = new DirectoryInfo(directoryPath);
        FileInfo[] additionalXsds = directoryInfo.GetFiles("*.xsd");
    
        var xmlPreloadedResolver = new XmlPreloadedResolver();
    
        foreach (FileInfo xsd in additionalXsds)
        {
            xmlPreloadedResolver.Add(new Uri($"file://{xsd.FullName}"), File.OpenRead(xsd.FullName));
        }
        
        return xmlPreloadedResolver;
    }
    

    The XmlPreloadedResolver is available on .NET Framework, .NET Core and .NET 5+ so it should be immediately available to use.