【问题标题】:What is the fastest way to programmatically check the well-formedness of XML files in C#?在 C# 中以编程方式检查 XML 文件格式正确的最快方法是什么?
【发布时间】:2023-03-23 09:37:01
【问题描述】:

我有大量手动更新的 XHTML 文件。在更新的审查阶段,我想以编程方式检查文件的格式是否正确。 我目前正在使用 XmlReader,但平均 CPU 所需的时间比我预期的要长得多。

XHTML 文件的大小从 4KB 到 40KB 不等,每个文件的验证需要几秒钟的时间。检查是必不可少的,但我希望尽可能缩短时间,因为在将文件读入下一个流程步骤时执行检查。

是否有更快的方法来进行简单的 XML 格式良好检查?也许使用外部 XML 库?


我可以确认,使用 XmlReader 验证“常规”基于 XML 的内容非常快,并且正如建议的那样,问题似乎与每次验证文件时都会读取 XHTML DTD 的事实有关。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

请注意,除了 DTD 之外,还会下载相应的 .ent 文件(xhtml-lat1.ent、xhtml-symbol.ent、xhtml-special.ent)。

因为完全忽略 DTD 并不是 XHTML 的真正选择,因为格式正确与允许的 HTML 实体密切相关(例如,当我们忽略 DTD 时,  会立即引入验证错误)。


按照建议,使用自定义 XmlResolver 并结合 DTD 和实体文件的本地(嵌入)副本解决了问题。

我会在清理完代码后在这里发布解决方案

【问题讨论】:

  • 我遇到了类似的问题。您介意为此发布您的代码解决方案吗?
  • @STLDeveloper 我在以下代码 (VB.NET) 中实现了建议:ValidateXMLClass.vb。所需的 .dtd、.ent 和 .mod 文件(本地)作为嵌入式资源包含以加速验证。代码调用如下:'Dim validateFile As New ValidateXMLClass validateFile.fileIn = tgtFile If Not validateFile.wellFormed Then ...'
  • 感谢您在您原来的问题经过这么长时间后提供代码。

标签: c# xml well-formed


【解决方案1】:

我希望XmlReaderwhile(reader.Read)() {} 将是最快的托管 方法。读取 40KB 肯定不需要 ...您使用的输入方法是什么?

您是否有一些外部(架构等)实体需要解决?如果是这样,您也许可以编写一个自定义的XmlResolver(通过XmlReaderSettings 设置),它使用本地缓存的模式而不是远程获取......

以下内容几乎立即执行 ~300KB:

    using(MemoryStream ms = new MemoryStream()) {
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.CloseOutput = false;
        using (XmlWriter writer = XmlWriter.Create(ms, settings))
        {
            writer.WriteStartElement("xml");
            for (int i = 0; i < 15000; i++)
            {
                writer.WriteElementString("value", i.ToString());
            }
            writer.WriteEndElement();
        }
        Console.WriteLine(ms.Length + " bytes");
        ms.Position = 0;
        int nodes = 0;
        Stopwatch watch = Stopwatch.StartNew();
        using (XmlReader reader = XmlReader.Create(ms))
        {
            while (reader.Read()) { nodes++; }
        }
        watch.Stop();
        Console.WriteLine("{0} nodes in {1}ms", nodes,
            watch.ElapsedMilliseconds);
    }

【讨论】:

  • 这基本上是我现在使用的。我正在直接读取文件(我也尝试先将它们读入 FileStream,但这并没有太大变化)。我将 XmlWriterSettings .ProhibitDtd 设置为 False。
  • 我会看看问题是否出在引用的 DTD 和命名空间中。
  • 就像您指出的那样,问题似乎出在 XHTML DTD 上。 渡//EN" "w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
【解决方案2】:

通过传入具有ConformanceLevel.DocumentXmlReaderSettings 对象来创建XmlReader 对象。

这将验证格式正确。

这个MSDN article应该解释一下细节。

【讨论】:

  • 我尝试将 ConformanceLevel.Document 添加到设置中,但速度没有明显提高。
  • 这是最快的方法之一。正如 Marc 暗示的那样,您的速度问题可能是由于其他原因造成的。您应该编辑您的帖子以包含您正在使用的代码。
【解决方案3】:

在我相当普通的笔记本电脑上,使用XmlReader 从头到尾读取 250K XML 文档需要 6 毫秒。除了解析 XML 之外,还有其他东西是罪魁祸首。

【讨论】:

  • 谢谢,问题可能是每次检查都使用了建议的 DTD。
【解决方案4】:

我知道我在发帖,但我认为这可能是一个解决方案

  1. 使用 HTML Tidy 清除您的 xml。设置删除文档类型的选项
  2. 然后从tidy读取生成的xhtml/xml。

这是相同的代码

public void GetDocumentStructure(int documentID)
    {
        string scmRepoPath = ConfigurationManager.AppSettings["SCMRepositoryFolder"];
        string docFilePath = scmRepoPath + "\\" + documentID.ToString() + ".xml";

        string docFilePath2 = scmRepoPath + "\\" + documentID.ToString() + "_clean.xml";

        Tidy tidy = new Tidy();
        tidy.Options.MakeClean = true;
        tidy.Options.NumEntities = true;
        tidy.Options.Xhtml = true;
        // this option removes the DTD on the generated output of Tidy
        tidy.Options.DocType = DocType.Omit;

        FileStream input = new FileStream(docFilePath, FileMode.Open);            
        MemoryStream output = new MemoryStream();
        TidyMessageCollection msgs = new TidyMessageCollection();
        tidy.Parse(input, output, msgs);            
        output.Seek(0, SeekOrigin.Begin);

        XmlReader rd = XmlReader.Create(output);            
        int node = 0;

        System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
        while (rd.Read())
        {                
            ++node;                
        }
        watch.Stop();

        Console.WriteLine("Duration was : " + watch.Elapsed.ToString());
    }

【讨论】:

    【解决方案5】:

    正如其他人所提到的,瓶颈很可能不是 XmlReader。

    检查如果没有字符串生成器,您是否不会碰巧进行大量字符串连接。

    这真的会影响你的表现。

    【讨论】:

    • 问题似乎在于每次检查都会重新读取 DTD(当使用常规 XML 文件而不是 XHTML 文件时,检查会按预期运行得很快)。
    【解决方案6】:

    就我个人而言,我很懒……所以我寻找已经解决问题的 .NET 库。尝试使用DataSet.ReadXML() 函数并捕获异常。它在解释 XML 格式错误方面做得非常出色。

    【讨论】:

      【解决方案7】:

      我正在使用这个函数来验证字符串/片段

      <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
      

      【讨论】:

        猜你喜欢
        • 2018-12-22
        • 1970-01-01
        • 2016-12-23
        • 2011-01-22
        • 1970-01-01
        • 2022-11-14
        • 1970-01-01
        • 2021-02-17
        • 1970-01-01
        相关资源
        最近更新 更多