Reputation: 133
I searched and did not find any questions addressing this problem.
I am attempting to validate various XML against a schema and it seems to be validating ALL well-formed XML, instead of just XML that conforms to the schema. I have posted the code I am using, the Schema, a sample valid XML and a sample invalid XML.
I have been struggling with this for awhile. I am in the dark on most of this. I've had to learn how to write an XSD, write the XSD, then learn how to parse XML in C#. None of which I have ever done before. I have used many tutorials and the microsoft website to come up with the following. I think this should work, but it doesn't.
What am I doing wrong?
private bool ValidateXmlAgainstSchema(string sourceXml, string schemaUri)
{
bool validated = false;
try
{
// REF:
// This should create a SCHEMA-VALIDATING XMLREADER
// http://msdn.microsoft.com/en-us/library/w5aahf2a(v=vs.110).aspx
XmlReaderSettings xmlSettings = new XmlReaderSettings();
xmlSettings.Schemas.Add("MySchema.xsd", schemaUri);
xmlSettings.ValidationType = ValidationType.Schema;
xmlSettings.ValidationFlags = XmlSchemaValidationFlags.None;
XmlReader xmlReader = XmlReader.Create(new StringReader(sourceXml), xmlSettings);
// parse the input (not sure this is needed)
while (xmlReader.Read()) ;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlReader);
validated = true;
}
catch (XmlException e)
{
// load or parse error in the XML
validated = false;
}
catch (XmlSchemaValidationException e)
{
// Validation failure in XML
validated = false;
}
catch (Exception e)
{
validated = false;
}
return validated;
}
The XSD / Schema. The intent is to accept XML that contains either an Incident or a PersonOfInterest.
<?xml version="1.0" encoding="utf-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="MySchema.xsd"
xmlns="MySchema.xsd"
elementFormDefault="qualified"
>
<xs:element name="Incident" type="IncidentType"/>
<xs:element name="PersonOfInterest" type="PersonOfInterestType"/>
<xs:complexType name="IncidentType">
<xs:sequence>
<xs:element name="Description" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="PersonOfInterest" type="PersonOfInterestType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="PersonOfInterestType">
<xs:sequence>
<xs:element name="Name" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Here is a sample of valid XML
<?xml version="1.0" encoding="utf-8" ?>
<Incident
xmlns="MySchema.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3schools.com MySchema.xsd"
>
<Description>something happened</Description>
<PersonOfInterest>
<Name>Joe</Name>
</PersonOfInterest>
<PersonOfInterest>
<Name>Sue</Name>
</PersonOfInterest>
</Incident>
This is a sample of well-formed invalid XML which should throw an exception (I thought), but when I try it, the code returns true, indicating it is valid against the schema.
<ghost>Boo</ghost>
Upvotes: 5
Views: 6788
Reputation: 704
Just to add that you can enable warnings:
xmlSettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
so you can get a message for an entry like <ghost>Boo</ghost>
Upvotes: 0
Reputation: 31760
The reason your <ghost>Boo</ghost>
validates is that the parser cannot find any schema matching the xml. If there is no schema then the parser assumed validity, providing the xml is well-formed. It's counter-intuitive I know, and will probably differ based on parser implementation.
This notwithstanding, there are several problems with your code:
Two Root Elements
This is a big no-no in xsd - you can only have a single root element. Some parsers will actually throw an exception, others tolerate it but will only use the first root element (in your case Incident) for any subsequent validation.
Use of schemaLocation attribute
This should take the value (namespace) (URI)
where the namespace is the targetNamespace of the schema and the URI is the location of the schema. In your case you appear to be using the schema file name as your target namespace. Additionally, looking at your code, you are loading the schema into your xml reader so you don't actually need the schemaLocation attribute at all. This is an optional attribute and some parsers completely ignore it.
I would suggest the following changes:
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://MyMoreMeaningfulNamespace"
xmlns="http://MyMoreMeaningfulNamespace"
elementFormDefault="qualified"
>
<xs:element name="Root">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" name="Incident" type="IncidentType"/>
<xs:element maxOccurs="unbounded" name="PersonOfInterest" type="PersonOfInterestType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="IncidentType">
<xs:sequence>
<xs:element name="Description" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="PersonOfInterest" type="PersonOfInterestType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="PersonOfInterestType">
<xs:sequence>
<xs:element name="Name" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Which validates this instance
<Root xmlns="http://MyMoreMeaningfulNamespace">
<Incident>
<Description>something happened</Description>
<PersonOfInterest>
<Name>Joe</Name>
</PersonOfInterest>
<PersonOfInterest>
<Name>Sue</Name>
</PersonOfInterest>
</Incident>
<Incident>
...
</Incident>
<PersonOfInterest>
<Name>Manny</Name>
</PersonOfInterest>
<PersonOfInterest>
...
</PersonOfInterest>
</Root>
Upvotes: 5