My issue was two-fold: when an attribute is missing, determine the line number and also what element the attribute is part of.
The first part I resolved by adding the proper options to the XDocument creation:
// create XDocument
var opts = LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo;
XDocument xDocument = XDocument.Parse(stringWriter.ToString(), opts);
Now I read in the XSD and validate:
// Read in the XSD
string schema = File.ReadAllText("c:\myschema.xsd");
XmlSchemaSet xmlSchemaSet = new();
xmlSchemaSet.Add("http://www.company.com/Import/v1_9", XmlReader.Create(new StringReader(schema)));
// Validate the XML against the XSD
xDocument.Validate(xmlSchemaSet, (sender, ex) =>
{
string validationErr = string.Format($"XML Parse Error: Severity={ex.Severity} Line={ex.Exception.LineNumber} Position={ex.Exception.LinePosition}");
Debug.Print("validationErr: {0}", validationErr);
validationErr = string.Format($"Message={ex.Exception}");
Debug.Print("validationErr: {0}", validationErr);
}, true);
My XML with a known missing attribute results in this output to debug:
validationErr: XML Parse Error: Severity=Error Line=436 Position=26
Message: System.Xml.Schema.XmlSchemaValidationException: The required attribute 'Desc' is missing.
This is great. I now know the line and position where this error occurs. Along with that I would like to know the full path of the element that contains this error. Right now all it says is the attribute 'Desc' is missing. Well, I have probably 15+ different element types that have that attribute. Here is the partial node of where I know this error happens:
<Roads Count="1">
<Road Num="1" Width="168" Distance="19388" Unit="FT">
<Ident RecordId="" /> <<--- the Desc attribute goes here
<Notes />
</Road>
</Roads>
I've been looking half this morning for a way to print something like: The required attribute 'Desc' is missing in Root\node1\node2\node3\Roads\Road\Ident. This would help the folks looking at it better than a line number and position. Although we would end up using both, the full path immediately tells us where to look in our SQL database for the missing value. So any way to get more detailed info and path of where the error occurs?
So the answer is already in the code you've posted - it's the first argument, the sender
object in your ValidationCallback
method (where you have xDocument.Validate(xmlSchemaSet, (sender, ex) =>
). All you need to do is cast the sender
object to an XElement
type and then you can compute the XPath:
// Validate the XML against the XSD
xDocument.Validate(xmlSchemaSet, (sender, ex) =>
{
string validationErr = string.Format($"XML Parse Error: Severity={ex.Severity} Line={ex.Exception.LineNumber} Position={ex.Exception.LinePosition}");
Debug.Print("validationErr: {0}", validationErr);
validationErr = string.Format($"Message={ex.Exception}");
Debug.Print("validationErr: {0}", validationErr);
// sender is typeof(XElement)
Debug.Print("Type name is: " + sender.GetType().Name);
var xElement = sender as XElement;
// now use GetAbsoluteXPath() from https://stackoverflow.com/a/454597/1376318 to get the XPath of the element:
var xPath = xElement.GetAbsoluteXPath();
Debug.Print("Xpath is: " + xPath);
}, true);
The GetAbsoluteXPath()
method above uses the code from this other StackOverflow question: https://stackoverflow.com/a/454597/1376318 that can retrieve the XPath of any given XElement
.
So now the output is:
validationErr: XML Parse Error: Severity=Error Line=0 Position=0
validationErr: Message=System.Xml.Schema.XmlSchemaValidationException: The required attribute 'Desc' is missing.
Type name is: XElement
Xpath is: /Roads/Road[1]/Ident[1]