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

Blu*_*kMN 15 .net c# 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"));
         }
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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

零件代码01a:右臂

零件代码03:大脚趾

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

Mar*_*ell 5

这就是......我写了大量的序列化代码(包括很多xml处理),我发现自己与你完全一样.因此,我有一个非常简单的指导:不要.

我很乐意用它XmlWriter作为一种快速编写xml的方法,但是在选择实现IXmlSerializable另一个时间之前我会走过热煤- 我只是单独写一个DTO并将数据映射到那个; 它还意味着模式("mex","wsdl"等)免费提供.


Blu*_*kMN 3

我最新的解决方案(适用于我当前的情况)是在实现状态机时坚持使用 Read()、IsStartElement(name) 和 GetAttribute(name)。

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();
}
Run Code Online (Sandbox Code Playgroud)

Read 后,如果您只是读取一个起始元素(可选地检查读取的元素的名称),IsStartElement 将返回 true,并且您可以立即访问该元素的所有属性。如果您需要阅读的只是元素和属性,那么这非常简单。

编辑 问题中发布的新示例提出了一些其他挑战。读取 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());
         }
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

您必须使用 ReadString 而不是 ReadElementString 以避免读取结束元素并跳到下一个元素的开头(让下面的 Read() 跳过结束元素,这样它就不会跳过下一个开始元素)。这看起来仍然有些令人困惑并且可能不可靠,但它适用于这种情况。

经过一些额外的思考,我的观点是,如果您使用除 Read 方法之外的任何方法来读取内容,XMLReader 就太混乱了。我认为如果您只使用 Read 方法从 XML 流中读取数据,那么会简单得多。以下是它如何与新示例配合使用(再次看来,IsStartElement、GetAttribute 和 Read 是关键方法,最终会得到一个状态机):

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);
   }
}
Run Code Online (Sandbox Code Playgroud)