1

I am trying to use the XDocument class and XmlSchemaSet class to validate an XMl file.

The XML file already exists but I want to add in just a single element consisting of a couple other elements and I only want to validate this node.

Here is an example of the XML file. The piece I would like to validate is the TestConfiguration node:

<?xml version="1.0" encoding="ISO-8859-1"?>
<Root>
  <AppType>Test App</AppType>
  <LabelMap>
    <Label0>
        <Title>Tests</Title>
        <Indexes>1,2,3</Indexes>
    </Label0>
  </LabelMap>

<TestConfiguration>
    <CalculateNumbers>true</CalculateNumbers>
    <RoundToDecimalPoint>3</RoundToDecimalPoint>
</TestConfiguration>
</Root>

Here is my xsd so far:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="TestConfiguration"
           targetNamespace="MyApp_ConfigurationFiles" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="TestConfiguration">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="CalculateNumbers" type="xs:boolean" minOccurs="1" maxOccurs="1"/>
        <xs:element name="RoundToDecimalPoint" type="xs:int" minOccurs="1" maxOccurs="1"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Here is the code I use to validate it:

private bool ValidateXML(string xmlFile, string xsdFile)
{
    string xsdFilePath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) ?? string.Empty, xsdFile);

    Logger.Info("Validating XML file against XSD schema file.");
    Logger.Info("XML: " + xmlFile);
    Logger.Info("XSD: " + xsdFilePath);

    try
    {
        XDocument xsdDocument = XDocument.Load(xsdFilePath);
        XmlSchemaSet schemaSet = new XmlSchemaSet();
        schemaSet.Add(XmlSchema.Read(new StringReader(xsdDocument.ToString()), this.XmlValidationEventHandler));
        XDocument xmlDocument = XDocument.Load(xmlFile);
        xmlDocument.Validate(schemaSet, this.XmlValidationEventHandler);
    }
    catch (Exception e)
    {
        Logger.Info("Error parsing XML file: " + xmlFile);
        throw new Exception(e.Message);
    }

    Logger.Info("XML validated against XSD.");
    return true;
}

Even validating the full XML file, the validation will pass successfully causing me to run into problems when I try to load the XML file into the generated class file created by xsd2code, the error: <Root xmlns=''> was not expected..

How can I validate just the TestConfiguration piece?

Thanks

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
RXC
  • 1,233
  • 5
  • 36
  • 67
  • 2
    An unknown node is a warning, not an error, so you need to set `XmlSchemaValidationFlags.ReportValidationWarnings`. To do this, see [XDocument.Validate is always successful](http://stackoverflow.com/questions/17232575/xdocument-validate-is-always-successful). – dbc May 03 '16 at 18:04

1 Answers1

1

You have a few issues here:

  1. Validating the entire document succeeds when it should fail.

    This happens because the root node is unknown to the schema, and encountering an unknown node is considered a validation warning not a validation error - even if that unknown node is the root element. To enable warnings while validating, you need to set XmlSchemaValidationFlags.ReportValidationWarnings. However, there's no way to pass this flag to XDocument.Validate(). The question XDocument.Validate is always successful shows one way to work around this.

    Having done this, you must also throw an exception in your validation handler when ValidationEventArgs.Severity == XmlSeverityType.Warning.

    (As for requiring a certain root element in your XSD, this is apparently not possible.)

  2. You need a convenient way to validate elements as well as documents, so you can validate your <TestConfiguration> piece.

  3. Your XSD and XML are inconsistent.

    You XSD specifies that your elements are in the XML namespace MyApp_ConfigurationFiles in the line targetNamespace="MyApp_ConfigurationFiles" elementFormDefault="qualified". In fact the XML elements shown in your question are not in any namespace.

    If the XSD is correct, your XML root node needs to look like:

    <Root xmlns="MyApp_ConfigurationFiles">
    

    If the XML is correct, your XSD needs to look like:

    <xs:schema id="TestConfiguration"
       elementFormDefault="unqualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    

After you have resolved the XSD and XML inconsistency from #3, you can solve issues #1 and #2 by introducing the following extension methods that validate both documents and elements:

public static class XNodeExtensions
{
    public static void Validate(this XContainer node, XmlReaderSettings settings)
    {
        if (node == null)
            throw new ArgumentNullException();
        using (var innerReader = node.CreateReader())
        using (var reader = XmlReader.Create(innerReader, settings))
        {
            while (reader.Read())
                ;
        }
    }

    public static void Validate(this XContainer node, XmlSchemaSet schemaSet, XmlSchemaValidationFlags validationFlags, ValidationEventHandler validationEventHandler)
    {
        var settings = new XmlReaderSettings();
        settings.ValidationType = ValidationType.Schema;
        settings.ValidationFlags |= validationFlags;
        if (validationEventHandler != null)
            settings.ValidationEventHandler += validationEventHandler;
        settings.Schemas = schemaSet;
        node.Validate(settings);
    }
}

Then, to validate the entire document, do:

try
{
    var xsdDocument = XDocument.Load(xsdFilePath);
    var schemaSet = new XmlSchemaSet();
    using (var xsdReader = xsdDocument.CreateReader())
        schemaSet.Add(XmlSchema.Read(xsdReader, this.XmlSchemaEventHandler));
    var xmlDocument = XDocument.Load(xmlFile);

    xmlDocument.Validate(schemaSet, XmlSchemaValidationFlags.ReportValidationWarnings, XmlValidationEventHandler);
}
catch (Exception e)
{
    Logger.Info("Error parsing XML file: " + xmlFile);
    throw new Exception(e.Message);
}

And to validate a specific node, you can use the same extension methods:

XNamespace elementNamespace = "MyApp_ConfigurationFiles";
var elementName = elementNamespace + "TestConfiguration";

try
{
    var xsdDocument = XDocument.Load(xsdFilePath);
    var schemaSet = new XmlSchemaSet();
    using (var xsdReader = xsdDocument.CreateReader())
        schemaSet.Add(XmlSchema.Read(xsdReader, this.XmlSchemaEventHandler));
    var xmlDocument = XDocument.Load(xmlFile);

    var element = xmlDocument.Root.Element(elementName);
    element.Validate(schemaSet, XmlSchemaValidationFlags.ReportValidationWarnings, this.XmlValidationEventHandler);
}
catch (Exception e)
{
    Logger.Info(string.Format("Error validating element {0} of XML file: {1}", elementName, xmlFile));
    throw new Exception(e.Message);
}

Now validating the entire document fails while validating the {MyApp_ConfigurationFiles}TestConfiguration node succeeds, using the following validation event handlers:

void XmlSchemaEventHandler(object sender, ValidationEventArgs e)
{
    if (e.Severity == XmlSeverityType.Error)
        throw new XmlException(e.Message);
    else if (e.Severity == XmlSeverityType.Warning)
        Logger.Info(e.Message);
}

void XmlValidationEventHandler(object sender, ValidationEventArgs e)
{
    if (e.Severity == XmlSeverityType.Error)
        throw new XmlException(e.Message);
    else if (e.Severity == XmlSeverityType.Warning)
        throw new XmlException(e.Message);
}
dbc
  • 104,963
  • 20
  • 228
  • 340