Getting started with XSD validation with .NET
Here is my first attempt at validating XML with XSD.
The XML file to be validated:
<?xml version="1.0" encoding="utf-8" ?>
<config xmlns="Schemas" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="config.xsd">
<levelVariant>
<filePath>SampleVariant</filePath>
</levelVariant>
<levelVariant>
<filePath>LegendaryMode</filePath>
</levelVariant>
<levelVariant>
<filePath>AmazingMode</filePath>
</levelVariant>
</config>
The XSD, located in "Schemas/config.xsd" relative to the XML file to be validated:
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="config">
<xs:complexType>
<xs:sequence>
<xs:element name="levelVariant">
<xs:complexType>
<xs:sequence>
<xs:element name="filePath" type="xs:anyURI">
&l开发者_开发问答t;/xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Right now, I just want to validate the XML file precisely as it appears currently. Once I understand this better, I'll expand more. Do I really need so many lines for something as simple as the XML file as it currently exists?
The validation code in C#:
public void SetURI(string uri)
{
XElement toValidate = XElement.Load(Path.Combine(PATH_TO_DATA_DIR, uri) + ".xml");
// begin confusion
// exception here
string schemaURI = toValidate.Attributes("xmlns").First().ToString()
+ toValidate.Attributes("xsi:noNamespaceSchemaLocation").First().ToString();
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add(null, schemaURI);
XDocument toValidateDoc = new XDocument(toValidate);
toValidateDoc.Validate(schemas, null);
// end confusion
root = toValidate;
}
Running the above code gives this exception:
The ':' character, hexadecimal value 0x3A, cannot be included in a name.
Any illumination would be appreciated.
Rather than using the XDocument.Validate extension method, I would use an XmlReader which can be configured to process an inline schema via XmlReaderSettings. You could do some thing like the following code.
public void VerifyXmlFile(string path)
{
// configure the xmlreader validation to use inline schema.
XmlReaderSettings config = new XmlReaderSettings();
config.ValidationType = ValidationType.Schema;
config.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
config.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
config.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
config.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
// Get the XmlReader object with the configured settings.
XmlReader reader = XmlReader.Create(path, config);
// Parsing the file will cause the validation to occur.
while (reader.Read()) ;
}
private void ValidationCallBack(object sender, ValidationEventArgs vea)
{
if (vea.Severity == XmlSeverityType.Warning)
Console.WriteLine(
"\tWarning: Matching schema not found. No validation occurred. {0}",
vea.Message);
else
Console.WriteLine("\tValidation error: {0}", vea.Message);
}
The code above assumes the following using statements.
using System.Xml;
using System.Xml.Schema;
Just to keep this simple I did not return a boolean
or a collection of validation errors, you could easily modify this to do so.
Note: I modified your config.xml and config.xsd to get them to validate. These are the changes I made.
config.xsd:
<xs:element maxOccurs="unbounded" name="levelVariant">
config.xml:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="config.xsd">
Following is out of a working sample:
Usage:
XMLValidator val = new XMLValidator();
if (!val.IsValidXml(File.ReadAllText(@"d:\Test2.xml"), @"D:\Test2.xsd"))
MessageBox.Show(val.Errors);
Class:
public class CXmlValidator
{
private int nErrors = 0;
private string strErrorMsg = string.Empty;
public string Errors { get { return strErrorMsg; } }
public void ValidationHandler(object sender, ValidationEventArgs args)
{
nErrors++;
strErrorMsg = strErrorMsg + args.Message + "\r\n";
}
public bool IsValidXml(string strXml/*xml in text*/, string strXsdLocation /*Xsd location*/)
{
bool bStatus = false;
try
{
// Declare local objects
XmlTextReader xtrReader = new XmlTextReader(strXsdLocation);
XmlSchemaCollection xcSchemaCollection = new XmlSchemaCollection();
xcSchemaCollection.Add(null/*add your namespace string*/, xtrReader);//Add multiple schemas if you want.
XmlValidatingReader vrValidator = new XmlValidatingReader(strXml, XmlNodeType.Document, null);
vrValidator.Schemas.Add(xcSchemaCollection);
// Add validation event handler
vrValidator.ValidationType = ValidationType.Schema;
vrValidator.ValidationEventHandler += new ValidationEventHandler(ValidationHandler);
//Actual validation, read conforming the schema.
while (vrValidator.Read()) ;
vrValidator.Close();//Cleanup
//Exception if error.
if (nErrors > 0) { throw new Exception(strErrorMsg); }
else { bStatus = true; }//Success
}
catch (Exception error) { bStatus = false; }
return bStatus;
}
}
The above code validates following xml(code3) against xsd(code4).
<!--CODE 3 - TEST1.XML-->
<address xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Test1.xsd">
<name>My Name</name>
<street>1, My Street Address</street>
<city>Far</city>
<country>Mali</country>
</address>
<!--CODE 4 - TEST1.XSD-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="address">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="street" type="xs:string"/>
<xs:element name="city" type="xs:string"/>
<xs:element name="country" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
In validating against your xml/xsd I get of errors different than yours; I think this can help you continue(add/remove xml elements) from here:
You may also try the reverse process; try generating the schema from your xml and compare with your actual xsd - see the difference; and the easiest way to do that is to use generate schema using VS IDE. Following is how you'd do that:
Hope this helps.
--EDIT--
This is upon John's request, please see updated code using non deprecated methods:
public bool IsValidXmlEx(string strXmlLocation, string strXsdLocation)
{
bool bStatus = false;
try
{
// Declare local objects
XmlReaderSettings rs = new XmlReaderSettings();
rs.ValidationType = ValidationType.Schema;
rs.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation | XmlSchemaValidationFlags.ReportValidationWarnings;
rs.ValidationEventHandler += new ValidationEventHandler(rs_ValidationEventHandler);
rs.Schemas.Add(null, XmlReader.Create(strXsdLocation));
using (XmlReader xmlValidatingReader = XmlReader.Create(strXmlLocation, rs))
{ while (xmlValidatingReader.Read()) { } }
////Exception if error.
if (nErrors > 0) { throw new Exception(strErrorMsg); }
else { bStatus = true; }//Success
}
catch (Exception error) { bStatus = false; }
return bStatus;
}
void rs_ValidationEventHandler(object sender, ValidationEventArgs e)
{
if (e.Severity == XmlSeverityType.Warning) strErrorMsg += "WARNING: " + Environment.NewLine;
else strErrorMsg += "ERROR: " + Environment.NewLine;
nErrors++;
strErrorMsg = strErrorMsg + e.Exception.Message + "\r\n";
}
Usage:
if (!val.IsValidXmlEx(@"d:\Test2.xml", @"D:\Test2.xsd"))
MessageBox.Show(val.Errors);
else
MessageBox.Show("Success");
Test2.XML
<?xml version="1.0" encoding="utf-8" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Test2.xsd">
<levelVariant>
<filePath>SampleVariant</filePath>
</levelVariant>
<levelVariant>
<filePath>LegendaryMode</filePath>
</levelVariant>
<levelVariant>
<filePath>AmazingMode</filePath>
</levelVariant>
</config>
Test2.XSD (Generated from VS IDE)
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="config">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="levelVariant">
<xs:complexType>
<xs:sequence>
<xs:element name="filePath" type="xs:anyURI">
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
This is guaranteed to work!
Your code to extract the schema location looks weird. Why do you get the value of the xmlns attribute and concatenate it with the value of the xsi:noNamespaceSchemaLocation attribute? The exception is caused by the fact that you cannot specify a prefix in a call to Attributes; you need to specify the desired XNamespace.
Try this (untested):
// Load document
XDocument doc = XDocument.Load("file.xml");
// Extract value of xsi:noNamespaceSchemaLocation
XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
string schemaURI = (string)doc.Root.Attribute(xsi + "noNamespaceSchemaLocation");
// Create schema set
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("Schemas", schemaURI);
// Validate
doc.Validate(schemas, (o, e) =>
{
Console.WriteLine("{0}", e.Message);
});
精彩评论