如何获取 XML 架构验证失败位置的 XPath(或节点)
本文关键字:位置 失败 XPath 节点 验证 何获取 获取 XML | 更新日期: 2023-09-27 17:57:29
我正在使用XDocument.Valid(它的功能似乎与XmlDocument.Validate相同(来验证XSD的XML文档 - 这很好用,我被告知验证错误。
但是,似乎只有一些信息在 ValidationEventHandler(和 XmlSchemaException(中[可靠地]公开,例如:
- 错误消息(即"'X' 属性无效 - 根据其数据类型'Z',值'Y'无效 - 模式约束失败"(,
- 严重性
我想要的是获取验证失败的"失败的XPath"(在有意义的地方(:也就是说,我想获得与XML文档(而不是XML文本(相关的失败。
有没有办法从XDocument.Validate
获取"失败的XPath"信息?如果不是,"失败的 XPath"是否可以通过其他 XML 验证方法(如 XmlValidatingReader
1(获取?
背景:
XML将作为数据发送到我的 Web 服务,并自动(通过 JSON.NET(从 JSON 转换为 XML。因此,我开始处理 XDocument 数据 1 而不是文本,由于原始 JSON 数据,文本没有保证的顺序。出于我不想进入的原因,REST 客户端基本上是 XML 文档上的 HTML 表单字段的包装器,服务器上的验证分两部分进行 - XML 架构验证和业务规则验证。
在业务规则验证中,很容易为不符合的字段返回"XPath",这些字段可用于指示客户端上的失败字段。我想将其扩展到XSD模式验证,它负责基本的结构验证,更重要的是,属性的基本"数据类型"和"存在"。但是,由于所需的自动过程(即突出显示适当的失败字段(和源转换,原始文本消息和源行/列号本身并不是很有用。
下面是验证代码的片段:
// 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"?
});
更新:我在验证 XDocument 时发现了捕获架构信息,它链接到"在文档验证期间访问 XML 架构信息"(缓存(,我从中确定了两件事:
XmlSchemaException 可以专门用于具有 SourceObject 属性的 XmlSchemaValidationException - 但是,这在验证期间始终返回 null:"当验证 XmlReader 对象在验证期间引发 XmlSchemaValidationException 时,SourceObject 属性的值为 null"。
我可以通读文档(通过
XmlReader.Read
(并"记住"验证回调之前的路径。虽然这在初始测试中"似乎有效"(没有 ValidationCallback(,但对我来说感觉很不优雅 - 但我几乎找不到其他东西。
验证事件的发送方是事件的源。因此,您可以在网络上搜索获取节点 XPath 的代码(例如,生成 XPath 表达式(并为事件源生成 XPath:
doc.Validate(schemas, (sender, args) => {
if (sender is XObject)
{
xpath = ((XObject)sender).GetXPath();
}
});
接受它 :-(
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();
}
}
}
我不知道 API,但我的猜测是否定的,您无法获得 xpath,因为验证可以作为有限状态机实现。状态可能不会转换为 xpath,或者如果它对多个元素有效,并且找到的元素不在预期的集中,则 xpath 不存在。
终于以这种方式成功了!
当我使用 XmlReader.Create(xmlStream, settings( 和 xmlRdr.Read(( 来验证 XML 时,我捕获了 ValidationEventHandler 的发送方,发现它是 {System.Xml.XsdValidatingReader} 的对象,所以我将发送者转移到 xmlreader 对象,XMLReader 类中有一些函数可以帮助您找到错误属性的父节点。
有一点需要注意,当我使用 XMLReader.MoveToElement(( 时,验证函数会卡在错误属性中的一个循环中,所以我使用了 MoveToAtrribute(AttributeName(,然后是 MoveToNextAttribute 来避免卡在循环中,也许有更优雅的方式来处理这个问题。
事不宜迟,下面是我的代码。
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;
}
或者,您可以使用如何在 C# 中的行号和列号中查找 XML 节点中的代码? 使用args.Exception.LineNumber
和args.Exception.LinePosition
获取失败节点,然后根据需要导航 XML 文档,以提供有关导致验证失败的数据的详细信息。
如果您像我一样使用"XmlDocument.Validate(ValidationEventHandler validationEventHandler("方法来验证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) + ")");
}
}
秘诀是获取"args"参数的"异常"属性数据。这样做:
// 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 "";
}