我永远无法预测XMLReader的行为。理解的任何提示?

时间:2010-01-23 23:28:50

标签: c# .net xml parsing

似乎每当我使用XMLReader时,我最终会尝试一些试验和错误,试图找出我要阅读的内容,而不是我正在阅读的内容与我刚刚阅读的内容。我总是把它弄清楚,但是在我多次使用它之后,我似乎并没有牢牢掌握当我调用各种函数时XMLReader实际上在做什么。例如,当我第一次调用Read时,如果它读取元素开始标记,它现在是在元素标记的末尾,还是准备开始读取元素的属性?如果我调用GetAttribute,它是否知道属性的值?如果我此时调用ReadStartElement会发生什么?它会完成读取start元素,还是寻找下一个元素,跳过所有属性?如果我想阅读许多元素怎么办?尝试阅读下一个元素并确定其名称的最佳方法是什么。将Read继续IsStartElement工作,或者IsStartElement将返回有关我刚读过的元素后节点的信息吗?

正如您所看到的,我真的不了解XMLReader在读取的各个阶段所处的位置以及各种读取函数对其状态的影响。是否有一些我根本没注意到的简单模式?

这是问题的另一个例子(取自回复):

string input = "<machine code=\"01\">The Terminator" +
   "<part code=\"01a\">Right Arm</part>" +
   "<part code=\"02\">Left Arm</part>" +
   "<part code=\"03\">Big Toe</part>" +
   "</machine>";

using (System.IO.StringReader sr = new System.IO.StringReader(input))
{
   using (XmlTextReader reader = new XmlTextReader(sr))
   {
      reader.WhitespaceHandling = WhitespaceHandling.None;
      reader.MoveToContent();

      while(reader.Read())
      {
         if (reader.Name.Equals("machine") && (reader.NodeType == XmlNodeType.Element))
         {
            Console.Write("Machine code {0}: ", reader.GetAttribute("code"));
            Console.WriteLine(reader.ReadElementString("machine"));
         }
         if(reader.Name.Equals("part") && (reader.NodeType == XmlNodeType.Element))
         {
            Console.Write("Part code {0}: ", reader.GetAttribute("code"));
            Console.WriteLine(reader.ReadElementString("part"));
         }
      }
   }
}

第一个问题,机器节点被完全跳过。 MoveToContent似乎移动到机器元素的内容,导致它永远不会被解析。此外,如果您跳过MoveToContent,则会收到错误:“'Element'是无效的XmlNodeType。”尝试ReadElementString,我无法解释。

接下来的问题是,在读取第一部分元素时,ReadElementString似乎在阅读后将阅读器定位在下一个部分元素的开头。这会导致reader.Read在下一个循环的开头跳过下一个跳转到最后一个part元素的part元素。所以这段代码的最终输出是:

零件代码01a:右臂

零件代码03:大脚趾

这是我试图理解的XMLReader混淆行为的一个主要例子。

2 个答案:

答案 0 :(得分:5)

这就是......我写了大量的序列化代码(包括很多xml处理代码),而且我发现自己和完全在同一条船上。因此,我有一个非常简单的指导:

我很乐意使用XmlWriter作为快速编写xml的方法,但是在选择另一次实现IXmlSerializable之前我会走过热煤 - 我只是写一个单独的{{ 1}}并将数据映射到那个;它还意味着模式(对于“mex”,“wsdl”等)是免费的。

答案 1 :(得分:3)

我的最新解决方案(这适用于我目前的情况下)正在实施的状态机读(),IsStartElement(名称)和的getAttribute(名称)要坚持。

using (System.Xml.XmlReader xr = System.Xml.XmlTextReader.Create(stm))
{
   employeeSchedules = new Dictionary<string, EmployeeSchedule>();
   EmployeeSchedule emp = null;
   WeekSchedule sch = null;
   TimeRanges ranges = null;
   TimeRange range = null;
   while (xr.Read())
   {
      if (xr.IsStartElement("Employee"))
      {
         emp = new EmployeeSchedule();
         employeeSchedules.Add(xr.GetAttribute("Name"), emp);
      }
      else if (xr.IsStartElement("Unavailable"))
      {
         sch = new WeekSchedule();
         emp.unavailable = sch;
      }
      else if (xr.IsStartElement("Scheduled"))
      {
         sch = new WeekSchedule();
         emp.scheduled = sch;
      }
      else if (xr.IsStartElement("DaySchedule"))
      {
         ranges = new TimeRanges();
         sch.daySchedule[int.Parse(xr.GetAttribute("DayNumber"))] = ranges;
         ranges.Color = ParseColor(xr.GetAttribute("Color"));
         ranges.FillStyle = (System.Drawing.Drawing2D.HatchStyle)
            System.Enum.Parse(typeof(System.Drawing.Drawing2D.HatchStyle),
            xr.GetAttribute("Pattern"));
      }
      else if (xr.IsStartElement("TimeRange"))
      {
         range = new TimeRange(
            System.Xml.XmlConvert.ToDateTime(xr.GetAttribute("Start"),
            System.Xml.XmlDateTimeSerializationMode.Unspecified),
            new TimeSpan((long)(System.Xml.XmlConvert.ToDouble(xr.GetAttribute("Length")) * TimeSpan.TicksPerHour)));
         ranges.Add(range);
      }
   }
   xr.Close();
}

阅读后,IsStartElement将返回true,如果你只是读一开始元素(optinally检查元素读的名称),并可以立即访问该元素的所有属性。如果您需要阅读的只是元素和属性,这非常简单。

修改 问题中发布的新示例带来了一些其他挑战。读取XML的正确方法似乎是这样的:

using (System.IO.StringReader sr = new System.IO.StringReader(input))
{
   using (XmlTextReader reader = new XmlTextReader(sr))
   {
      reader.WhitespaceHandling = WhitespaceHandling.None;

      while(reader.Read())
      {
         if (reader.Name.Equals("machine") && (reader.NodeType == XmlNodeType.Element))
         {
            Console.Write("Machine code {0}: ", reader.GetAttribute("code"));
            Console.WriteLine(reader.ReadString());
         }
         if(reader.Name.Equals("part") && (reader.NodeType == XmlNodeType.Element))
         {
            Console.Write("Part code {0}: ", reader.GetAttribute("code"));
            Console.WriteLine(reader.ReadString());
         }
      }
   }
}

您必须使用ReadString,而不是ReadElementString为了避免读取结束元素和跳跃到下一个元素的开始(让下面的阅读()跳过结束元素,因此不会在下次启动时跳过元件)。这仍然有点混乱,可能不可靠,但它适用于这种情况。

经过一些额外的考虑之后,我的观点是XMLReader太混乱如果你使用任何方法来读取除Read方法之外的内容。我认为如果您将自己局限于Read方法以从XML流中读取,则会更加简单。下面是它如何与新的工作,例如(再次,似乎IsStartElement,和的getAttribute读取的关键方法,你结束了一个状态机):

while(reader.Read())
{
   if (reader.IsStartElement("machine"))
   {
      Console.Write("Machine code {0}: ", reader.GetAttribute("code"));
   }
   if(reader.IsStartElement("part"))
   {
      Console.Write("Part code {0}: ", reader.GetAttribute("code"));
   }
   if (reader.NodeType == XmlNodeType.Text)
   {
      Console.WriteLine(reader.Value);
   }
}