【问题标题】:Gracefully handle validation errors in a XML file in C#在 C# 中优雅地处理 XML 文件中的验证错误
【发布时间】:2009-03-30 21:07:16
【问题描述】:

描述有点长,请多多包涵。我想处理和验证一个巨大的 XML 文件并记录触发验证错误的节点并继续处理下一个节点。 XML 文件的简化版本如下所示。

我想要执行的是遇到任何验证错误处理节点“A”或其子节点(XMLException 和 XmlSchemaValidationException)我想停止处理当前节点记录节点“A”的错误和 XML 并继续到下一个节点“A”。

<Root>
  <A id="A1">
     <B Name="B1">
        <C>
          <D Name="ID" >
            <E>Test Text 1</E>
          </D>
        <D Name="text" >
          <E>Test Text 1</E>
        </D>        
      </C>
    </B>
  </A>
  <A id="A2">
    <B Name="B2">
      <C>
        <D Name="id" >
          <E>Test Text 3</E>
        </D>
        <D Name="tab1_id"  >
          <E>Test Text 3</E>
        </D>
        <D Name="text" >
          <E>Test Text 3</E>
        </D>
      </C>
    </B>
</Root>

我目前能够通过使用带有 XMLReader 的 ValidationEventHandler 来从 XmlSchemaValidationException 中恢复,这会引发我在 XML 处理代码中处理的异常。但是,在某些情况下,会触发 XMLException,这会导致进程终止。

下面的sn-ps代码说明了我当前使用的结构;比较乱,也欢迎提出代码改进建议。

    // Setting up the XMLReader
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.ConformanceLevel = ConformanceLevel.Auto;
    settings.IgnoreWhitespace = true;
    settings.CloseInput = true;
    settings.IgnoreComments = true;
    settings.ValidationType = ValidationType.Schema;
    settings.Schemas.Add(null, "schema.xsd");
    settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
    XmlReader reader = XmlReader.Create("Sample.xml", settings);   
    // Processing XML
    while (reader.Read())
    if (reader.NodeType == XmlNodeType.Element)
       if (reader.Name.Equals("A"))
         processA(reader.ReadSubtree());            
    reader.Close(); 
   // Process Node A
   private static void processA(XmlReader A){
    try{
       // Perform some book-keeping 
       // Process Node B by calling processB(A.ReadSubTree())               
    }   
    catch (InvalidOperationException ex){

    }
    catch (XmlException xmlEx){

    } 
    catch (ImportException impEx){

    }
    finally{ if (A != null) A.Close(); }            
  }
  // All the lower level process node functions propagate the exception to caller.
  private static void processB(XmlReader B){
   try{
     // Book-keeping and call processC
   }
   catch (Exception ex){
    throw ex;
    }
   finally{ if (B != null) B.Close();}    
  } 
  // Validation event handler
  private static void ValidationCallBack(object sender, ValidationEventArgs e){
    String msg =  "Validation Error: " + e.Message +" at line " + e.Exception.LineNumber+
        " position number "+e.Exception.LinePosition;
    throw new ImportException(msg);
  }

当遇到 XMLSchemaValidationException 时,finally 块将调用 close() 并且原始 XMLReader 被定位在子树的 EndElement 上,因此 processA 中的 finally 块将导致处理下一个节点 A。

但是,当遇到 XMlException 调用 close 方法时,不会将原始读取器定位在子树的 EndElement 节点上,并且会抛出 InvalidOperationException。

我尝试使用诸如 skip、ReadToXYZ() 方法之类的方法,但是当在任何触发异常的节点上调用时,这些方法总是会导致 XMLExcpetion 的 InvalidOperationException。

以下是 MSDN 关于 ReadSubTree 方法的摘录。

当新的 XmlReader 已经 关闭,原来的 XmlReader 将 位于的 EndElement 节点上 子树。因此,如果您调用 开始标签上的 ReadSubtree 方法 book 元素,在子树之后 已阅读和新的 XmlReader 已关闭,原 XmlReader 位于结束标记上 book 元素。

注意:我不能为此使用 .Net 3.5,但是欢迎 .Net 3.5 建议。

【问题讨论】:

    标签: c# .net xml


    【解决方案1】:

    看到这个问题:
    XML Parser Validation Report

    您需要区分 格式良好的 xml(它遵循成为真实 xml 所需的规则)和 有效 xml(遵循特定 xml 架构给出的附加规则)。来自规范:

    然而,一旦检测到致命错误,处理器就不能继续正常处理(即,它不能继续以正常方式将字符数据和有关文档逻辑结构的信息传递给应用程序)。

    无论好坏,Visual Studio 中包含的 xml 工具都需要非常严格地遵循该规范,因此如果出现格式正确错误,将不会继续处理。我提供的链接可能会给你一些选择。

    【讨论】:

      【解决方案2】:

      除了吃的异常和有问题的使用 XmlReader.Close() 之外,我所做的几乎和你一样:

      XmlReaderSettings settings = new XmlReaderSettings();
      settings.ConformanceLevel = ConformanceLevel.Auto;
      settings.IgnoreWhitespace = true;
      settings.CloseInput = true;
      settings.IgnoreComments = true;
      settings.ValidationType = ValidationType.Schema;
      settings.Schemas.Add(null, "schema.xsd");
      settings.ValidationEventHandler += ValidationCallBack;
      using (XmlReader reader = XmlReader.Create("Sample.xml", settings))
      {
          while (reader.Read())
          {
              if (reader.NodeType == XmlNodeType.Element &&
                  reader.Name.Equals("A"))
              {
                  using (
                      XmlReader subtree = reader.ReadSubtree())
                  {
                      try {
                          processA(subtree);
                      }
                      catch (ImportException ex) { // log it at least }
                      catch (XmlException ex) {// log this too }
                      // I would not catch InvalidOperationException - too general
                  }
              }
          }
      }
      
      private static void processA(XmlReader A)
      {
          using (XmlReader subtree = A.ReadSubtree())
          {
              processB(subtree);
          }
      }
      
      // All the lower level process node functions propagate the exception to caller.  
      private static void processB(XmlReader B)
      {
      }
      

      除了来自 processA 的异常之外,您不想吃掉异常。让 processA 的调用者决定忽略哪些异常 - processA 不应该知道这一点。与 using 块相同 - 将它们放在外面,而不是里面。

      【讨论】:

      • 我在处理异常时删除了代码以保持简洁。我需要在 finally 块中执行一些其他操作,所以我没有看到使用“使用”块的优势。
      猜你喜欢
      • 1970-01-01
      • 2012-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-10
      • 2021-03-11
      • 1970-01-01
      • 2015-05-15
      相关资源
      最近更新 更多