如何获取 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 架构信息"(缓存(,我从中确定了两件事:

  1. XmlSchemaException 可以专门用于具有 SourceObject 属性的 XmlSchemaValidationException - 但是,这在验证期间始终返回 null:"当验证 XmlReader 对象在验证期间引发 XmlSchemaValidationException 时,SourceObject 属性的值为 null"。

  2. 我可以通读文档(通过XmlReader.Read(并"记住"验证回调之前的路径。虽然这在初始测试中"似乎有效"(没有 ValidationCallback(,但对我来说感觉很不优雅 - 但我几乎找不到其他东西。

如何获取 XML 架构验证失败位置的 XPath(或节点)

验证事件的发送方是事件的源。因此,您可以在网络上搜索获取节点 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.LineNumberargs.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 "";
}