How to get XPath (or Node) to localize an XML schema validation error?

I use XDocument.Validate (it seems to work the same as XmlDocument.Validate) to validate an XML document on XSD - this works well and I am informed of validation errors.

However, only some information appears to be [reliable] displayed in the ValidationEventHandler (and XmlSchemaException), for example:

  • error message (ie, the "X" attribute is invalid - the value "Y" is invalid according to its data type "Z" - template constraint failure "),
  • heaviness

I would like to get a “failing XPath” for a validation failure (where this makes sense): that is, I would like to receive a failure in relation to an XML document (as opposed to XML text).

Is there a way to get XPath failure information from XDocument.Validate ? If not, is it possible to get "failing XPath" using another XML validation method such as XmlValidatingReader 1 ?


Background:

XML will be sent as data to my web service with automatic conversion (via JSON.NET) from JSON to XML. Because of this, I am starting to process XDocument 1 data and not text that does not have a guaranteed order due to the original JSON data. For reasons that I don’t need, the REST client is basically a wrapper for HTML form fields over an XML document, and the server validation takes place in two parts: validation of the XML schema and validation of business rules.

In confirming the business rule, it is easy to return “XPath” for fields that do not meet the requirements, which can be used to indicate a field failure (s) on the client. I would like to extend this to validating the XSD schema, which takes care of validating the underlying structure and, more importantly, the underlying “data type” and “existence” of the attributes. However, due to the desired automatic process (i.e., highlighting the corresponding failure field) and source conversion, the raw text message and the source row / column numbers are not very useful in themselves.


Here is a snippet of verification code:

 // Start with an XDocument object - created from JSON.NET conversion XDocument doc = GetDocumentFromWebServiceRequest(); // Load XSD var reader = new StringReader(EmbeddedResourceAccess.ReadResource(xsdName)); var xsd = XmlReader.Create(reader, new XmlReaderSettings()); var schemas = new XmlSchemaSet(); schemas.Add("", xsd); // Validate doc.Validate(schemas, (sender, args) => { // Process validation (not parsing!) error, // but how to get the "failing XPath"? }); 

Update: I discovered Capture Schema Information while validating an XDocument that references "Accessing the XML Schema Information while Validating a Document" ( cached ), from which I determined two things:

  • An XmlSchemaException can be specialized in an XmlSchemaValidationException that has a SourceObject property, however this always returns null during validation: When an XmlSchemaValidationException is thrown when the validator checks the XmlReader, the value of the SourceObject property is null.

  • I can read the document (via XmlReader.Read ) and "remember" the path to the validation callback. Although this “looks like it works” in the initial tests (without the ValidationCallback), it feels completely inelegant for me, but I still have little to find.

+6
source share
6 answers

The sender of the validation event is the source of the event. Thus, you can search the network for the code that receives XPath for the node (for example, Creating an XPath expression ) and generate XPath for the event source:

 doc.Validate(schemas, (sender, args) => { if (sender is XObject) { xpath = ((XObject)sender).GetXPath(); } }); 
+7
source

Take it: -)

 var xpath = new Stack<string>(); var settings = new XmlReaderSettings { ValidationType = ValidationType.Schema, ValidationFlags = XmlSchemaValidationFlags.ReportValidationWarnings, }; MyXmlValidationError error = null; settings.ValidationEventHandler += (sender, args) => error = ValidationCallback(sender, args); foreach (var schema in schemas) { settings.Schemas.Add(schema); } using (var reader = XmlReader.Create(xmlDocumentStream, settings)) { // validation while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { xpath.Push(reader.Name); } if (error != null) { // set "failing XPath" error.XPath = xpath.Reverse().Aggregate(string.Empty, (x, y) => x + "/" + y); // your error with XPath now error = null; } if (reader.NodeType == XmlNodeType.EndElement || (reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement)) { xpath.Pop(); } } } 
+3
source

I do not know the API, but I assume that no, you cannot get xpath, because the check can be implemented as a state machine. The state cannot translate to xpath or in the case when it is valid for more than one element and the element found is not in the expected set, xpath does not exist.

+1
source

Finally, it succeeded!

When I use XmlReader.Create (xmlStream, settings) and xmlRdr.Read () to validate the XML, I grabbed the sender of ValidationEventHandler and found that this is a {System.Xml.XsdValidatingReader} object, so I am transferring the sender of the xmlreader object, in the XMLReader class there is Some features to help you find the parent node attribute errors.

There is one thing to make sure that when I use XMLReader.MoveToElement (), the Validation function will loop around the loop in the error attribute, so I used MoveToAtrribute (AttributeName) and then MoveToNextAttribute to avoid getting stuck in the loop, maybe There is a more elegant way to handle this.

Without further ado, below is my code.

 public string XMLValidation(string XMLString, string SchemaPath) { string error = string.Empty; MemoryStream xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(XMLString)); XmlSchemaSet schemas = new XmlSchemaSet(); schemas.Add(null, SchemaPath); XmlReaderSettings settings = new XmlReaderSettings(); settings.ValidationType = ValidationType.Schema; settings.Schemas.Add(schemas); settings.ValidationEventHandler += new ValidationEventHandler(delegate(object sender, ValidationEventArgs e) { switch (e.Severity) { case XmlSeverityType.Error: XmlReader senRder = (XmlReader)sender; if (senRder.NodeType == XmlNodeType.Attribute) {//when error occurs in an attribute,get its parent element name string attrName = senRder.Name; senRder.MoveToElement(); error += string.Format("ERROR:ElementName'{0}':{1}{2}", senRder.Name, e.Message, Environment.NewLine); senRder.MoveToAttribute(attrName); } else { error += string.Format("ERROR:ElementName'{0}':{1}{2}", senRder.Name, e.Message, Environment.NewLine); } break; case XmlSeverityType.Warning: break; } }); XmlReader xmlRdr = XmlReader.Create(xmlStream, settings); while (xmlRdr.Read()) ; return error; } 
+1
source

Alternatively, you can use the code. How to find an XML node from a row and column number in C #? to get the node to fail using args.Exception.LineNumber and args.Exception.LinePosition , and then move the XML document as needed to provide additional information about what data caused the validation to fail.

0
source

If, like me, you use the "XmlDocument.Validate (ValidationEventHandler validationEventHandler)" method to validate your XML:

 // Errors and alerts collection private ICollection<string> errors = new List<String>(); // Open xml and validate ... { // Create XMLFile for validation XmlDocument XMLFile = new XmlDocument(); // Validate the XML file    XMLFile.Validate(ValidationCallBack); } // Manipulator of errors occurred during validation private void ValidationCallBack(object sender, ValidationEventArgs args) { if (args.Severity == XmlSeverityType.Warning) { errors.Add("Alert: " + args.Message + " (Path: " + GetPath(args) + ")"); } else if (args.Severity == XmlSeverityType.Error) { errors.Add("Error: " + args.Message + " (Path: " + GetPath(args) + ")"); } } 

The secret is to get the "Exception" property of the "args" parameter. Do this:

 // Return this parent node private string GetPath(ValidationEventArgs args) { var tagProblem =((XmlElement)((XmlSchemaValidationException)args.Exception).SourceObject); return iterateParentNode(tagProblem.ParentNode) + "/" +tagProblem.Name; } private string iterateParentNode(XmlNode args) { var node = args.ParentNode; if (args.ParentNode.NodeType == XmlNodeType.Element) { return interateParentNode(node) + @"/" + args.Name; } return ""; } 
0
source

All Articles