user4622594
user4622594

Reputation:

compile schemas with included xsd files

I have static method, which i use to validate a XML File against a XSD File. This works fine, until there is an XSD File which includes another XSD File.

Example, where i got troubles:

TYPES.XSD:

<xs:simpleType name="MY_AMOUNT">
    <xs:restriction base="xs:decimal">
        <xs:maxInclusive value="999999999999.99"/>
        <xs:minInclusive value="-999999999999.99"/>
        <xs:totalDigits value="14"/>
        <xs:fractionDigits value="2"/>
    </xs:restriction>
</xs:simpleType>

MAIN.XSD:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:include schemaLocation="TYPES.xsd"/>
    <xs:element name="ROOT">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="SOMEREF1"/>
                <xs:element ref="SOMEREF2"/>
                <xs:element name="AMOUNT" type="MY_AMOUNT" minOccurs="0"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

VALIDATION CODE:

public static class XmlUtils
{
    private static string Errors = string.Empty;

    public static bool ValidateAgainstXSD(string xmlFilePath, string xsdFilePath, ref string message)
    {
        try
        {
            var settings = new XmlReaderSettings();

            settings.ValidationType = ValidationType.Schema;
            settings.ValidationFlags = XmlSchemaValidationFlags.ProcessInlineSchema
                | XmlSchemaValidationFlags.ProcessInlineSchema
                | XmlSchemaValidationFlags.ReportValidationWarnings;
            settings.Schemas.Add(null, xsdFilePath);
            settings.Schemas.Compile();

            settings.ValidationEventHandler += (sender, args) =>
            {
                if (args.Severity == XmlSeverityType.Error)
                {
                    Errors += args.Message + "\n";
                }
            };

            using (var reader = XmlReader.Create(xmlFilePath, settings))
            {
                while (reader.Read()) { }
            }

            message = Errors ?? string.Empty;
            return string.IsNullOrEmpty(Errors);
        }
        catch (Exception e)
        {
            message = "# error validating xml file: " + e.Message;
            return false;
        }
    }
}

Somehow it seems i have to specify the path of the included XSD File but i have no idea where.

The error occurs at settings.Schemas.Compile(); , where it says that the type "MY_AMOUNT" is not declared. I read about custom XmlResolvers but to be honest i didn't get that working.

If this is important for an answer: The xsd files are always located in the same directory!

The method is called likes this:

string msg = string.Empty;
string basedir = @"C:\Temp";
string xml = Path.Combine(basedir, "XML_FILE.xml");
string xsd = Path.Combine(basedir, "MAIN.xsd");

if (XmlUtils.ValidateAgainstXSD(xml, xsd, ref msg))
{
    // do some work
}
else
{
    Console.WriteLine(msg);
}

Console.ReadLine();

Any help is highly appreciated - Thank you!

UPDATE 2016-12-05:

I wrote my own XmlUrlResolver, to see what happens behind the scenes:

internal class XUrlResolver : XmlUrlResolver
{
    public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
    {
        return base.GetEntity(absoluteUri, role, ofObjectToReturn);
    }

    public override Uri ResolveUri(Uri baseUri, string relativeUri)
    {
        return base.ResolveUri(baseUri, relativeUri);
    }
}

And the i just try to do:

XmlSchemaSet xset = new XmlSchemaSet();
xset.XmlResolver = new XUrlResolver();
xset.Add("", xsdFilePath);
xset.Compile();

What happens now (on line xset.Add):

  1. XmlUrlResolver.ResolveUri(null,"C:\\Temp\\MAIN.XSD") --> {file:///C:/Temp/MAIN.xsd}
  2. XmlUrlResolver.ResolveUri(null,"C:\\Temp\\MAIN.XSD") --> {file:///C:/Temp/MAIN.xsd}
  3. XmlUrlResolver.GetEntity({file:///C:/Temp/MAIN.xsd}) --> Filestream to MAIN.xsd
  4. XmlUrlResolver.ResolveUri({file:///C:/Temp/MAIN.xsd},"TYPES.XSD") --> {file:///C:/Temp/TYPES.xsd}
  5. XmlUrlResolver.GetEntity({file:///C:/Temp/TYPES.xsd}) --> Filestream to TYPES.xsd

Looks good to me (except the first 2 Calls are equal!?!) - the path to TYPES.XSD is resolved as it should.

Nevertheless, xset.Compile() throws an Exception: "Type MY_AMOUNT is not declared"

And i have no idea why :/

Upvotes: 5

Views: 3391

Answers (3)

Matthias Loerke
Matthias Loerke

Reputation: 2187

I know this topic is rather old, but I just bumped into the same problem:

  • None of the solutions and corrections worked for me
  • The relative include paths were validated to be correct.
  • A different runtime (the VS T4-host) was resolving the schema successful

I ended up creating a custom resolver instance, implementing the "GetEntity"-method, returning the "ofObjectToReturn"-type, which was "Stream" in my case. I openend and returned a stream for the local path of the resolved include and it worked.

I noticed that the requested URIs differ in syntax, so this might be the source of trouble.

/// <summary>
/// Custom XML-resolver for locating schema files
/// </summary>
private class SchemaXmlResolver : XmlResolver
{
    /// <summary>
    /// <see cref="XmlResolver.GetEntity"/>
    /// </summary>
    /// <param name="absoluteUri"><see cref="XmlResolver.GetEntity"/></param>
    /// <param name="role"><see cref="XmlResolver.GetEntity"/></param>
    /// <param name="ofObjectToReturn"><see cref="XmlResolver.GetEntity"/></param>
    /// <returns><see cref="XmlResolver.GetEntity"/></returns>
    public override object GetEntity(Uri absoluteUri, 
                                     string role, 
                                     Type ofObjectToReturn)
    {
        // Open file
        return new FileStream(path: absoluteUri.LocalPath,
                              mode: FileMode.Open,
                              access: FileAccess.Read,
                              share: FileShare.Read);
    }
}

Note: This implementation is for sure highly specific to the use case, since it does not respect different URI-kinds and return types.

Upvotes: 0

Dan
Dan

Reputation: 11

I ran into this very same issue.

I'm not suggesting this is the correct answer, but I got around it by setting the Environment.CurrentDirectory property to be the path where the included XSDs were located. Then it all processed just fine.

Upvotes: 1

Evk
Evk

Reputation: 101583

First you need to make your xsd files valid.

Types.xsd (added schema root element and xs namespace)

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="MY_AMOUNT">
    <xs:restriction base="xs:decimal">
        <xs:maxInclusive value="999999999999.99"/>
        <xs:minInclusive value="-999999999999.99"/>
        <xs:totalDigits value="14"/>
        <xs:fractionDigits value="2"/>
    </xs:restriction>
</xs:simpleType>
</xs:schema>

Main.xsd (removed invalid refs).

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:include schemaLocation="TYPES.xsd"/>
    <xs:element name="ROOT">
        <xs:complexType>
            <xs:sequence>                
                <xs:element name="AMOUNT" type="MY_AMOUNT" minOccurs="0"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

After that, given that both xsd files are in the same directory, your schemas will compile fine.

Upvotes: 2

Related Questions