Validating XML on XSD with the error line numbers
Since @chris-watts suggested to post my comment again as answer. Here it is.
The document only has line number information if it was loaded with the appropriate flags:
var opts = LoadOptions.PreserveWhitespace
| LoadOptions.SetLineInfo;
XDocument doc = XDocument.Load(fileStream, opts);
Woah, rather tricky this XSD stuff, I'm also new :)
As it was said before the position information gets lost in the XmlDocument.
I got it finally running using the XmlReader in combination with XmlDocument:
// xmlStream and xsdStream are open streams that
// point to the respective xml and xsd files
public void ReadAndVerify(Stream xmlStream, Stream xsdStream)
{
// Read the scheme validation and compile it
XmlSchemaSet schemaSet = new XmlSchemaSet();
using (XmlReader r = XmlReader.Create(xsdStream))
{
schemaSet.Add(XmlSchema.Read(r, null));
}
schemaSet.CompilationSettings = new XmlSchemaCompilationSettings();
schemaSet.Compile();
// Setup the settings for the reader.
// This includes the previously compiled schema
XmlReaderSettings settings = new XmlReaderSettings();
settings.CloseInput = true;
// This is the callback method see below
settings.ValidationEventHandler += ValidationEventHandler;
settings.ValidationType = ValidationType.Schema;
settings.Schemas = schemaSet; // <-- here the schema is set
// To be honest, this is cut'n'paste. Not sure which flags are really required.
settings.ValidationFlags =
XmlSchemaValidationFlags.ReportValidationWarnings |
XmlSchemaValidationFlags.ProcessIdentityConstraints |
XmlSchemaValidationFlags.ProcessInlineSchema |
XmlSchemaValidationFlags.ProcessSchemaLocation;
// Now the validating reader is created
using (XmlReader validatingReader = XmlReader.Create(xmlStream, settings))
{
// This has to be done BEFORE the validating while loop
XmlDocument x = new XmlDocument();
x.Load(validatingReader);
// This is the validation loop
while (validatingReader.Read()) ;
// This is the client code that actually uses the XmlDocument nodes.
XmlNode node = x[RootNode];
ReadAllParameters(node);
}
}
And the ValidationEventHandler:
private void ValidationEventHandler(object sender, ValidationEventArgs e)
{
// This actually works with this approach
string text = $"[Line: {e.Exception?.LineNumber}, Column: {e.Exception?.LinePosition}]: {e.Message}";
switch (e.Severity) {
case XmlSeverityType.Error:
Logger.Error(text);
break;
case XmlSeverityType.Warning:
Logger.Warn(e.Message);
break;
}
}
Life can be so simple ;-)
You can use XMLStarlet. That supports validating, and provides error line numbers:
$ xml val -e --xsd file.xsd file.xml
file.xml:8: Element 'atitle': This element is not expected. Expected is ( title ).
file.xml - invalid
See the original MSDN example:
http://msdn.microsoft.com/en-us/library/ms172454.aspx
ValidationEventHandler's ValidationEventArgs argument has Exception.LineNumber:
private void SchemaValidationEventHandler(object sender, ValidationEventArgs e) {
Console.WriteLine("XML {0}: {1} (Line {2})",
e.Severity,
e.Message,
e.Exception.LineNumber);
}