Я никогда не могу предсказать поведение XMLReader. Любые советы по пониманию?

Кажется, каждый раз, когда я использую XMLReader, я получаю кучу проб и ошибок, пытаясь понять, что я собираюсь читать, и то, что я читаю, и то, что я только что прочитал. Я всегда понимаю это в конце, но я все же, после использования его несколько раз, похоже, не имеет четкого понимания того, что делает XMLReader, когда я называю различные функции. Например, когда я вызываю Read в первый раз, если он читает начальный тег элемента, находится ли он в конце тега элемента или готов начать чтение атрибутов элемента? Знает ли он значения атрибутов, если я вызываю GetAttribute? Что произойдет, если я назову ReadStartElement? По завершении чтения элемента запуска или поиска следующего, пропустив все атрибуты? Что делать, если я хочу прочитать несколько элементов - лучший способ попробовать прочитать следующий элемент и определить его имя. Будет ли Read, за которым следует IsStartElement, или IsStartElement будет возвращать информацию о node после элемента, который я только что прочитал?

Как вы видите, мне действительно не хватает понимания того, где находится 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"));
         }
      }
   }
}

Первая проблема, машина node полностью пропущена. Кажется, MoveToContent перемещается к содержимому элемента машины, заставляя его никогда не разбираться. Кроме того, если вы пропустите MoveToContent, вы получите сообщение об ошибке: "Элемент" является недопустимым XmlNodeType ". пытаясь прочитать элемент ReadElementString, который я не могу объяснить.

Следующая проблема заключается в том, что, читая элемент первой части, ReadElementString, кажется, позиционирует читателя в начале следующего элемента элемента после прочтения. Это заставляет читателя. Прочитайте в начале следующего цикла, чтобы пропустить следующий элемент детали, прыгающий прямо к последнему элементу детали. Таким образом, окончательный вывод этого кода:

Код детали 01a: Правая рука

Код детали 03: Big Toe

Это яркий пример противоречивого поведения XMLReader, который я пытаюсь понять.

Ответы

Ответ 1

Моим последним решением (которое работает для моего текущего случая) является использование 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();
}

После чтения IsStartElement вернет true, если вы просто прочитали начальный элемент (проверяя имя элемента чтения), и вы можете сразу получить доступ ко всем атрибутам этого элемента. Если все, что вам нужно прочитать, это элементы и атрибуты, это довольно просто.

Edit Новый пример, поставленный в вопросе, создает некоторые другие проблемы. Правильный способ чтения этого 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, чтобы избежать чтения конечного элемента и пропустить его в начало следующего элемента (пусть следующий Read() пропустит конечный элемент, чтобы он не пропустил следующий запуск элемент). Тем не менее это кажется несколько запутанным и потенциально ненадежным, но оно работает для этого случая.

После некоторой дополнительной мысли, я считаю, что XMLReader просто слишком запутан, если вы используете какие-либо методы для чтения контента, отличного от метода Read. Я думаю, что это намного проще, если вы ограничитесь методом 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);
   }
}

Ответ 2

Вот что... Я написал довольно много кода сериализации (включая много обработки XML), и я оказался в точно той же лодке, что и вы. Поэтому у меня очень простая инструкция: не делайте этого.

Я с удовольствием использую XmlWriter как способ быстро написать xml, но я бы пошел по горячим углям, прежде чем выбрать IXmlSerializable в другой раз - просто написать отдельный DTO и сопоставить данные в это; это также означает, что схема (для "mex", "wsdl" и т.д.) предоставляется бесплатно.