Я никогда не могу предсказать поведение XMLReader. Любые советы по пониманию? - PullRequest
15 голосов
/ 24 января 2010

Кажется, что каждый раз, когда я использую XMLReader, я сталкиваюсь с кучей проб и ошибок, пытаясь понять, что я собираюсь прочитать, что я читаю, что я только что прочитал. В конце концов, я всегда в этом разбираюсь, но, тем не менее, после многократного использования я, похоже, не совсем понимаю, что на самом деле делает XMLReader, когда я вызываю различные функции. Например, когда я вызываю Read в первый раз, если он читает начальный тег элемента, он сейчас находится в конце тега элемента или готов начать чтение атрибутов элемента? Знает ли он значения атрибутов, если я вызываю GetAttribute? Что произойдет, если я вызову ReadStartElement на этом этапе? Закончит ли он чтение начального элемента или ищет следующий, пропуская все атрибуты? Что делать, если я хочу прочитать несколько элементов - как лучше всего попытаться прочитать следующий элемент и определить его имя. Будет ли работать 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, вы получите ошибку: «Элемент» является недопустимым XmlNodeType. » пытаюсь прочитать строку, которую я не могу объяснить.

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

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

Код детали 03: Большой палец

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

Ответы [ 2 ]

5 голосов
/ 24 января 2010

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

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

3 голосов
/ 24 января 2010

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

Редактировать Новый пример, размещенный в вопросе, ставит некоторые другие проблемы. Правильный способ чтения этого 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);
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...