在C#中优雅地处理XML文件中的验证错误

时间:2009-03-30 21:07:16

标签: c# .net xml

描述有点长,请耐心等待。我想处理并验证一个巨大的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 Processing代码中处理的Exception。但是在某些情况下,触发XMLException会导致进程终止。

以下代码片段说明了我正在使用的当前结构;它很混乱,也欢迎代码改进建议。

    // 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()方法之类的方法,但是当在触发异常的任何节点上调用时,这些方法总是会导致InvalidOperationException的XMLExcpetion。

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

  

新的XmlReader时   关闭,原来的XmlReader将是   位于EndElement节点上   子树。因此,如果你打电话给   开始标记上的ReadSubtree方法   子元素之后的book元素   已被阅读和新的XmlReader   已关闭,原来   XmlReader位于结束标记上   书元素。

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

2 个答案:

答案 0 :(得分:5)

看到这个问题:
XML Parser Validation Report

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

  

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

无论好坏,Visual Studio附带的xml工具都需要非常密切地遵循该规范,因此如果存在格式错误,则不会继续处理。我提供的链接可能会为您提供一些替代方案。

答案 1 :(得分: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不应该意识到这一点。与使用块相同 - 将它们放在外面,而不是内部。