如何获取XML模式验证失败位置的XPath(或节点)?

8
我正在使用XDocument.Validate(它似乎与XmlDocument.Validate的功能相同)来验证XML文档是否符合XSD。这个方法可以很好地工作,我会收到有关验证错误的通知。
然而,在ValidationEventHandler(以及XmlSchemaException)中只有一些信息被可靠地公开,例如:
- 错误消息(即“'X'属性无效 - 值“Y”根据其数据类型“Z”无效 - 模式约束失败”) - 严重程度
我希望能够获得验证失败的“XPath”(如果有意义的话)。也就是说,我想要获取与XML文档相关的失败(而不是XML文本)。
是否有一种方法可以从XDocument.Validate中获得“failing XPath”信息?如果没有,是否可以通过其他XML验证方法(例如XmlValidatingReader1)获取“failing XPath”?
背景:
XML将作为数据发送到我的Web服务,并通过JSON.NET自动转换为XML。因此,由于原始JSON数据的顺序不确定,我开始处理XDocument数据而不是文本。REST客户端基本上是HTML表单字段的包装器,用于XML文档和服务器上的验证分为两个部分 - 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"?
});

更新:我找到了一个链接:Capture Schema Information when validating XDocument,其中链接到“在文档验证期间访问XML模式信息”缓存版本),从中我确定了两件事情:
  1. XmlSchemaException可以特化为XmlSchemaValidationException,它有一个SourceObject属性 - 然而,在验证过程中这个属性总是返回null:“当通过验证的XmlReader对象抛出XmlSchemaValidationException时,SourceObject属性的值为null”。

  2. 我可以通过XmlReader.Read读取文档并在验证回调之前“记住”路径。虽然这在没有验证回调的初始测试中“似乎有效”,但对我来说感觉相当不雅 - 但我几乎找不到其他方法。


请展示您正在使用的 XML,我们不是心灵读者。谢谢。 - MethodMan
@DJKRAZE 我不认为XML不重要 - 在我的情况下,我目前可以从10个XSD中选择一个,并且有更多[故意]失败的测试用例数据。我不关心为什么它失败了(这在消息中),我想获得它失败的“XPath” - XML数据最终来自REST请求,我想提供一个“更有用的响应”。我发布的消息来自“可恢复错误”,即验证可以继续进行,这是我最感兴趣的。如果文档不符合XML解析规则,则所有赌注都无效。 - user166390
6个回答

8

验证事件的发送者是事件的来源。因此,您可以在网络上搜索获取节点XPath代码(例如生成XPath表达式),并为事件源生成XPath:

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

1
谢谢 - 狡猾的发送者,我甚至没有考虑过你!这对于查找无效属性(例如,发送者是未通过模式或不是有效子元素的XAttribute的情况)非常有效,但似乎需要更多的工作来查找缺失元素的路径,因为从逻辑上讲,它们没有相应的XObject。也就是说,如何知道它是哪种“错误”类型 - 无效元素还是缺失元素的无效父级?在后一种情况下,如何获取应该存在的元素的路径。 - user166390
2
@pst 如果您缺少元素,那么我认为这是元素容器的验证错误(您将收到“元素X具有不完整内容”的错误)。因此,看起来您应该提供父元素的路径,而不是缺少的元素的路径(实际上缺少了,所以在这种情况下路径将没有任何意义)。 - Sergey Berezovskiy
1
通常我会完全同意 - 但在这种情况下,它是为了通知(相对愚蠢的)WS客户端有关问题,因此缺少必需属性和无效属性对其来说几乎相同(它将空JSON属性规范化)。 我能想到的唯一替代方案(可能只是我没有想得够好)是接受所有属性的一些""。 无论如何,针对“缺少属性”的情况,我最终使用了正则表达式匹配/捕获,适用于我的用例。 只是不要指望它在非en-US线程上运行..: D 再次感谢。 - user166390

3
拿走它吧 :-)
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();
        }
    }
}

要么是我太早了,脑子还没清醒,要么就是这段代码太混乱了。这是怎么实现的?有些地方缺少解释。): - Kristopher

1
我不熟悉 API,但我的猜测是无法获取 xpath,因为验证可能会被实现为有限状态机。一个状态可能无法转换成 xpath 或者当多个元素可以跟随并且找到的元素不在预期集合中时,xpath 不存在。

我不相信这是情况所在,我认为这是可能的:但我不确定需要做多少工作。我有一个XML文档可以开始(XDocument,但我同样可以获得XmlDocument),因此在解析后存在“有效”的XML DOM-与基于SGML的语言不同,XML语法不会根据模式而改变。但是,此文档最初未针对任何特定模式进行验证-我想针对特定模式进行验证。在应用验证时,我想找出在此文档中哪个元素/属性验证失败。 - user166390
换句话说,我认为这应该是可能的,因为DOM存在——在我的情况下,它没有在流读取时验证失败。 - user166390

1

终于用这种方式成功了!

当我使用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;
    }

0

或者您可以使用在C#中如何通过行号和列号查找XML节点?中的代码,通过使用args.Exception.LineNumberargs.Exception.LinePosition来获取失败的节点,然后按需要浏览XML文档以提供有关导致验证失败的数据的更多信息。


0
如果跟我一样,你正在使用"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”参数的“Exception”属性数据。 做法如下:
// 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 "";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接