不使用try/catch如何检查格式正确的XML?

27

有没有人知道如何在不使用 XmlDocument.LoadXml() 的 try/catch 块的情况下检查一个字符串是否包含格式良好的 XML?我有可能是 XML 的输入,也可能不是,我希望代码能够识别到输入可能不是 XML,而不依赖于 try/catch,这样可以提高速度,并且根据一般原则,非异常情况不应该引发异常。我目前已经有了这样的代码;

private bool IsValidXML(string value)
    {
        try
        {
            // Check we actually have a value
            if (string.IsNullOrEmpty(value) == false)
            {
                // Try to load the value into a document
                XmlDocument xmlDoc = new XmlDocument();

                xmlDoc.LoadXml(value);

                // If we managed with no exception then this is valid XML!
                return true;
            }
            else
            {
                // A blank value is not valid xml
                return false;
            }
        }
        catch (System.Xml.XmlException)
        {
            return false;
        }
    }

但是这似乎不应该需要try/catch。异常在调试过程中会产生严重问题,因为每次检查字符串时,调试器都会在这里中断,"帮助"我解决烦人的问题。


如果调试器是您的问题,您可以关闭XmlExceptions的用户处理。在VS中使用快捷方式:Ctrl + Alt + E,找到System.Xml.XmlException并将其切换关闭。 - bytedev
3
真是惊人,每一个答案都是一个try/catch的答案;尽管你明确指出,你正在寻找一个没有try/catch的解决方案。Try/catch不是IF语句;它不应该成为过程的一部分。 它是用来处理异常的。从名字上就可以看出来 :) 希望有一天你能找到一个好的答案。 - Christian
11个回答

23

我不知道在没有异常的情况下如何进行验证,但你可以更改调试器设置,仅在 XmlException 未处理时中断 - 即使代码仍然笨拙,这应该能解决你的问题。

要做到这一点,请转到 Debug / Exceptions... / Common Language Runtime Exceptions,并找到 System.Xml.XmlException,然后确保只选中“User-unhandled”(而不是Thrown)。


这个解决方案真是救命稻草啊!只有在需要调试失败的代码时,我才会启用已处理异常的中断。 - OregonGhost

8

史蒂夫,

我们曾经遇到一个第三方有时会不小心发送JSON而不是XML的情况。这是我实施的解决方案:

public static bool IsValidXml(string xmlString)
{
    Regex tagsWithData = new Regex("<\\w+>[^<]+</\\w+>");

    //Light checking
    if (string.IsNullOrEmpty(xmlString) || tagsWithData.IsMatch(xmlString) == false)
    {
        return false;
    }

    try
    {
        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.LoadXml(xmlString);
        return true;
    }
    catch (Exception e1)
    {
        return false;
    }
}

[TestMethod()]
public void TestValidXml()
{
    string xml = "<result>true</result>";
    Assert.IsTrue(Utility.IsValidXml(xml));
}

[TestMethod()]
public void TestIsNotValidXml()
{
    string json = "{ \"result\": \"true\" }";
    Assert.IsFalse(Utility.IsValidXml(json));
}

6

这是一个合理的方法,但 IsNullOrEmpty 是多余的(LoadXml 可以很好地处理)。如果您确实保留 IsNullOrEmpty,请使用 if(!string.IsNullOrEmpty(value))。

基本上,您的调试器是问题所在,而不是代码本身。


我已经同意了。我已经使用调试器属性[DebuggerStepThrough]标记了该方法,这将阻止调试器在异常处停止。 - Steve Cooper
IsNullOrEmpty只是一种优化,用于避免在调用IsValidXml("")时产生异常的开销 - 这在我的程序中经常发生。 - Steve Cooper

4
[System.Diagnostics.DebuggerStepThrough] 属性添加到 IsValidXml 方法中。这将抑制 XmlException 被调试器捕获,这意味着您可以打开首次更改异常的捕获,并且此特定方法将不会被调试。

2

使用XmlDocument时要小心,因为它有可能加载类似于<0>some text</0>这样的元素,而不会抛出异常。数字元素名称不是有效的xml,在我的情况下,直到我尝试将xmlDoc.innerText写入Sql服务器的xml数据类型时才会发生错误。

现在我是这样验证的,如果出错就会抛出异常:
XmlDocument tempDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(formData.ToString(), "data"); doc.LoadXml(tempDoc.InnerXml);


1
好的观点 - xml标准表示“后者后跟零个或多个名称字符 - [4] NameChar :: = Letter | Digit |'.'| '-'| ''| ':'| CombiningChar | Extender [5] Name :: =(Letter |''| ':')(NameChar)*” - Steve Cooper

1
XmlTextReader类是XmlReader的一种实现,提供了快速、高效的解析器。它强制执行XML必须是格式良好的规则。它既不是验证解析器也不是非验证解析器,因为它没有DTD或模式信息。它可以按块读取文本,也可以从流中读取字符。

以下是另一篇MSDN文章中的示例,我添加了代码以读取XML流的全部内容。

string str = "<ROOT>AQID</ROOT>";
XmlTextReader r = new XmlTextReader(new StringReader(str));
try
{
  while (r.Read())
  {
  }
}
finally
{
  r.Close();
}

来源:http://bytes.com/topic/c-sharp/answers/261090-check-wellformedness-xml


0
我正在使用这个函数来验证字符串/片段。
<Runtime.CompilerServices.Extension()>
Public Function IsValidXMLFragment(ByVal xmlFragment As String, Optional Strict As Boolean = False) As Boolean
    IsValidXMLFragment = True

    Dim NameTable As New Xml.NameTable

    Dim XmlNamespaceManager As New Xml.XmlNamespaceManager(NameTable)
    XmlNamespaceManager.AddNamespace("xsd", "http://www.w3.org/2001/XMLSchema")
    XmlNamespaceManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")

    Dim XmlParserContext As New Xml.XmlParserContext(Nothing, XmlNamespaceManager, Nothing, Xml.XmlSpace.None)

    Dim XmlReaderSettings As New Xml.XmlReaderSettings
    XmlReaderSettings.ConformanceLevel = Xml.ConformanceLevel.Fragment
    XmlReaderSettings.ValidationType = Xml.ValidationType.Schema
    If Strict Then
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.ProcessInlineSchema)
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.ReportValidationWarnings)
    Else
        XmlReaderSettings.ValidationFlags = XmlSchemaValidationFlags.None
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.AllowXmlAttributes)
    End If

    AddHandler XmlReaderSettings.ValidationEventHandler, Sub() IsValidXMLFragment = False
    AddHandler XmlReaderSettings.ValidationEventHandler, AddressOf XMLValidationCallBack

    Dim XmlReader As Xml.XmlReader = Xml.XmlReader.Create(New IO.StringReader(xmlFragment), XmlReaderSettings, XmlParserContext)
    While XmlReader.Read
        'Read entire XML
    End While
End Function

我正在使用这个函数来验证文件:
Public Function IsValidXMLDocument(ByVal Path As String, Optional Strict As Boolean = False) As Boolean
    IsValidXMLDocument = IO.File.Exists(Path)
    If Not IsValidXMLDocument Then Exit Function

    Dim XmlReaderSettings As New Xml.XmlReaderSettings
    XmlReaderSettings.ConformanceLevel = Xml.ConformanceLevel.Document
    XmlReaderSettings.ValidationType = Xml.ValidationType.Schema
    If Strict Then
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.ProcessInlineSchema)
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.ReportValidationWarnings)
    Else
        XmlReaderSettings.ValidationFlags = XmlSchemaValidationFlags.None
        XmlReaderSettings.ValidationFlags = (XmlReaderSettings.ValidationFlags Or XmlSchemaValidationFlags.AllowXmlAttributes)
    End If
    XmlReaderSettings.CloseInput = True

    AddHandler XmlReaderSettings.ValidationEventHandler, Sub() IsValidXMLDocument = False
    AddHandler XmlReaderSettings.ValidationEventHandler, AddressOf XMLValidationCallBack

    Using FileStream As New IO.FileStream(Path, IO.FileMode.Open)
        Using XmlReader As Xml.XmlReader = Xml.XmlReader.Create(FileStream, XmlReaderSettings)
            While XmlReader.Read
                'Read entire XML
            End While
        End Using
    End Using
End Function

0

这只是我的个人看法 - 关于这个问题有很多不同的问题,大多数人都同意“垃圾进垃圾出”的事实。我不反对这个观点 - 但我个人发现了以下快速而简单的解决方案,特别是在处理来自第三方的xml数据时,他们很难与您进行通信...它并没有避免使用try/catch - 但它使用更细粒度的方式,因此在无效xml字符的数量不是很大的情况下,它会有所帮助...我使用了XmlTextReader,并为每个父元素使用其方法ReadChars(),这是一种不执行良好形式检查的命令之一,就像ReadInner/OuterXml一样。因此,当Read()遇到父节点时,它是Read()和ReadChars()的组合。当然,这有效是因为我可以假设XML的基本结构是正确的,但某些节点的内容(值)可能包含尚未替换为&..;等价物的特殊字符...(我在某个地方找到了一篇关于此的文章,但目前找不到源链接)


0

我不同意问题出在调试器上。通常情况下,应该避免使用异常,这意味着如果有人正在寻找像IsWellFormed()这样的方法,该方法基于输入是否为格式良好的XML返回true/false,无论是否捕获和处理异常,都不应在此实现中抛出异常。

异常是昂贵的,正常成功执行时不应遇到它们。例如,编写一个检查文件是否存在并使用File.Open,在文件不存在的情况下捕获异常的方法将是一种糟糕的实现。相反,应该使用File.Exists()(希望其实现不仅仅是在某些方法周围放置try/catch,如果文件不存在则抛出异常,我相信它不会这样做)。


我不确定这个答案是否有帮助。您没有提供一种无需抛出异常即可检查格式正确性的替代方法。这似乎是关于您对抛出异常方法的看法的声明。 - Duncan Jones
Steve明确要求在不使用try-catch的情况下完成此操作,因此告诉他应该在没有try-catch的情况下完成它并不真正有帮助,而且有点讽刺。 - Paul Groke
我并不是在讽刺,也知道我没有回答问题。我认为这很明显。我只是在评论其他人的评论。我猜我应该把我的回复添加为我所评论的回复的评论。 - nickdu

0
此外,当仅验证 XML 字符串的语法正确性时(无需解析外部模式),我认为添加 XmlResolver = null 设置可能是一个好主意。这既确保了安全性(无 Web 访问),也避免了恶意 XML 内容引导代码访问不良站点的情况。以下是代码(需要 C# 2.0 或更高版本):
public static bool IsValidXml(string candidateString)
{
    try
    {
        XmlReaderSettings settings = new XmlReaderSettings();
        settings.XmlResolver = null;
        XmlDocument document = new XmlDocument();
        document.XmlResolver = null;
        document.Load(XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(candidateString)), settings));
        return true;
    }
    catch (XmlException)
    {
        return false;
    }
}

针对C# 6.0或更高版本进行了优化的版本:

public static bool IsValidXml(string candidateString)
{
    try
    {
        var settings = new XmlReaderSettings { XmlResolver = null };
        var document = new XmlDocument() { XmlResolver = null };
        document.Load(XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(candidateString)), settings));
        return true;
    }
    catch (XmlException)
    {
        return false;
    }
}

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