19

Here is an XSD:

<?xml version="1.0"?>
<xsd:schema 
elementFormDefault='unqualified' 
attributeFormDefault='unqualified' 
xmlns:xsd='http://www.w3.org/2001/XMLSchema' 
>

  <xsd:simpleType name='TheSimpleType'>
    <xsd:restriction base='xsd:string' />
  </xsd:simpleType>
</xsd:schema>

Here is a second XSD that includes the one above:

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

  <xsd:include schemaLocation='Include.xsd' />

  <xsd:element name = "TheElement" >
  <xsd:complexType>
  <xsd:attribute name="Code" type="TheSimpleType" use="required"/>
  </xsd:complexType>
  </xsd:element>
</xsd:schema>

I need to read the (second) XSD into C# and:

  1. check that it is a valid XSD, and
  2. validate documents against it.

Here is some C# to read in the schemata:

    XmlSchemaSet schemaSet = new XmlSchemaSet();
    foreach (string sd in Schemas)
    {
        using (XmlReader r = XmlReader.Create(new FileStream(sd, FileMode.Open)))
        {
            schemaSet.Add(XmlSchema.Read(r, null));
        }
    }
    schemaSet.CompilationSettings = new XmlSchemaCompilationSettings();
    schemaSet.Compile();

The .Compile() fails because "Type 'a:TheSimpleType' is not declared, or is not a simple type."

However, it works if either:

  • the namespace is removed from the schema, or
  • the namespace is added to the include.

The question is: how do I get C# to accept it without editing the schemata?

I suspect the problem is that although I have put both schemata into the XmlSchemaSet, I still need to tell C# that one is included into the other, i.e., it hasn't worked it out for itself. Indeed, if I only tell the XmlSchemaSet about the main XSD (and not the include) (both without (or with) namespaces) then "Type 'TheSimpleType' is not declared, or is not a simple type."

Thus this seems to be a question about resolving includes: how?!

M.Babcock
  • 18,753
  • 6
  • 54
  • 84
Richard Barraclough
  • 2,625
  • 3
  • 36
  • 54

6 Answers6

29

The problem is with the way the schema is opened for reading on the line:

XmlReader.Create(new FileStream(sd, FileMode.Open)

I had to write my own XmlResolver before I could see how the paths to the include files were being resolved: it was from the directory of the executable and not from the directory of the parent schema. The problem is that the parent schema was not getting its BaseURI set. Here's how the schema must be opened:

XmlReader.Create(new FileStream(pathname, FileMode.Open, FileAccess.Read),null, pathname)
Richard Barraclough
  • 2,625
  • 3
  • 36
  • 54
  • Likewise, thanks for that Richard + 1. Spent way to long pulling my hair out over this one! – CountZero Dec 03 '13 at 10:36
  • 1
    This worked for me when I used `System.IO.Path(pathname)` for the `baseUri` argument of Create. – p0lar_bear May 20 '15 at 20:23
  • Yeah, Hello, from 20 years later! I had to add a trailing slash so the includes and imports could be found. And adding a Reolver – peni4142 Sep 26 '22 at 12:11
6

You can use the XmlSchema.Includes to link them together. Then, you just need to add the main schema to the schema set:

var includeSchema = XmlSchema.Read(XmlReader.Create(...), null);
var mainSchema = XmlSchema.Read(XmlReader.Create(...), null);

var include = new XmlSchemaInclude();
include.Schema = includeSchema;
mainSchema.Includes.Add(include);

var schemaSet = new XmlSchemaSet();
schemaSet.Add(mainSchema);
schemaSet.Compile();
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • 1
    +1 never knew about the `XmlSchemaInclude` class. Great answer. – psubsee2003 Apr 12 '12 at 17:53
  • 2
    OK, good. But now suppose that I have to determine all of the includes at run-time, i.e., I give you an arbitrary XSD with includes and you have to go and fetch them all. – Richard Barraclough Apr 12 '12 at 17:59
  • `s = XmlSchema.Read(r, null);` Now I see we have `s.Includes` which are `XmlSchemaInclude` objects, and it's correctly populated (with the 1 include). – Richard Barraclough Apr 12 '12 at 18:02
  • yes, that should be done automatically by the `XmlSchema` class. My guess is that it's not finding the right include based on the `schemaLocation` attribute. You could also look at the [`XmlResolver`](http://msdn.microsoft.com/en-us/library/system.xml.xmlresolver.aspx) class. Look [here](http://stackoverflow.com/questions/7982275/how-can-i-resolve-the-schemalocation-attribute-of-an-xsd-when-all-of-my-xsds) for an example. – Jordão Apr 12 '12 at 18:22
  • Is there a way to write the schemaSet object (the schemas) into an XML file ? – Rohit Sep 11 '14 at 10:29
  • @psubsee2003 may I focus your attention to `XmlSchemaImport`, which should be used if the target namespace is different: from [here]: (https://msdn.microsoft.com/en-us/library/system.xml.schema.xmlschemaimport(v=vs.110).aspx) _**XmlSchemaImport** allows references to schema components from other schemas with different target namespaces._ – x y Aug 07 '18 at 10:06
2

The default behaviour of the XmlSchemaSet is to not try to resolve any XSD included schemas. To do this, the XmlResolver property must be initialised.

XmlSchemaSet schemas = new XmlSchemaSet
{
    XmlResolver = new XmlUrlResolver()
};

Additionally, you have to set baseUri for XmlReader as per @Richard Barraclough's answer.

Michel
  • 51
  • 5
1

Try this :D

public static XmlSchema LoadSchema(string pathname)
{
    XmlSchema s = null;
    XmlValidationHandler h = new XmlValidationHandler();
    using (XmlReader r = XmlReader.Create(new FileStream(pathname, FileMode.Open)))
    {
        s = XmlSchema.Read(r, new ValidationEventHandler(h.HandleValidationEvent));
    }

    if (h.Errors.Count > 0)
    {
        throw new Exception(string.Format("There were {1} errors reading the XSD at {0}. The first is: {2}.", pathname, h.Errors.Count, h.Errors[0]));
    }

    return s;
}

public static XmlSchema LoadSchemaAndResolveIncludes(string pathname)
{
    FileInfo f = new FileInfo(pathname);
    XmlSchema s = LoadSchema(f.FullName);

    foreach(XmlSchemaInclude i in s.Includes)
    {
        XmlSchema si = LoadSchema(f.Directory.FullName + @"\" + i.SchemaLocation);
        si.TargetNamespace = s.TargetNamespace;
        i.Schema = si;
    }

    return s;
}

public static List<ValidationEventArgs> Validate(string pathnameDocument, string pathnameSchema)
{
    XmlSchema s = LoadSchemaAndResolveIncludes(pathnameSchema);

    XmlValidationHandler h = new XmlValidationHandler();

    XmlDocument x = new XmlDocument();
    x.Load(pathnameDocument);
    x.Schemas.Add(s);
    s.Compile(new ValidationEventHandler(h.HandleValidationEvent));
    x.Validate(new ValidationEventHandler(h.HandleValidationEvent));
    return h.Errors;
}

Note in particular the si.TargetNamespace = s.TargetNamespace;.

Obviously, this assumes that the includes are specified as file paths relative to the schema into which they are included.

Richard Barraclough
  • 2,625
  • 3
  • 36
  • 54
  • This fails in the case when (A includes B and C) and (B references but does not include C). It is indeed necessary to write a custom XmlResolver to resolve the filenames. – Richard Barraclough Apr 16 '12 at 17:22
0

Here is the method I wrote to handle xsd validation. Hope this helps some one.

        /// <summary>
        /// Ensure all xsd imported xsd documented are in same folder as master xsd
        /// </summary>
        public XsdXmlValidatorResult Validate(string xmlPath, string xsdPath, string xsdNameSpace)
        {
            var result = new XsdXmlValidatorResult();
            var readerSettings = new XmlReaderSettings {ValidationType = ValidationType.Schema};
            readerSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema; 
            readerSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
            readerSettings.Schemas.Add(null, xsdPath);

            readerSettings.ValidationEventHandler += (sender, args) =>
                {
                    switch (args.Severity)
                    {
                        case XmlSeverityType.Warning:
                            result.Warnings.Add(args.Message);
                            break;
                        case XmlSeverityType.Error:
                            result.IsValid = false;
                            result.Warnings.Add(args.Message);
                            break;
                    }
                };

            var reader = XmlReader.Create(xmlPath, readerSettings);

            while (reader.Read()) { }

            return result;
        }
CountZero
  • 6,171
  • 3
  • 46
  • 59
  • This does not work in my case. All XSD (and XML) files are in the same folder. Nevertheless, it tells's me that the types defined in the included xsd are not defined – CeOnSql Dec 01 '16 at 12:58
0

With .net6 the code goes as follows -


XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.XmlResolver = new XmlUrlResolver();     // Need this for resolving include and import
settings.ValidationType = ValidationType.Schema; // This might not be needed, I am using same settings to validate the input xml
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
settings.Schemas.Add(null, "yourpath\\yourxsd.xsd");

settings.Schemas.Compile();

string xmlFilePath = "yourpath\\your.xml";
ValidationEventHandler eventHandler = new ValidationEventHandler(ValidationEventHandler);

// Create an XmlReader for the XML file
using (XmlReader reader = XmlReader.Create(xmlFilePath, settings))
{
}

Wanted to highlight that with net6, we have to set settings.Schemas.XmlResolver = new XmlUrlResolver() instead of settings.XmlResolver = new XmlUrlResolver()