Это кажется каждый раз, когда я использую 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, который я не могу вполне объяснить.
Следующая проблема, читая первый элемент части, ReadElementString, кажется, располагает читателя в начале следующего элемента части после чтения. Это вызывает читателя. Читайте в начале следующего цикла для перескакивания через следующий элемент части, переходящий право на последний элемент части. Таким образом, окончательный результат этого кода:
Код части 01a: Правая рука
Код части 03: Большой палец ноги
Это - главный пример запутывающего поведения XMLReader, который я пытаюсь понять.
Мое последнее решение (которое работает в моем текущем случае) - это придерживаться 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();
}
После 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());
}
}
}
}
Вы должны использовать 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);
}
}
Вот в чем дело... Я написал достаточное количество кода сериализации (в том числе много обработки xml), и нахожусь в точно в той же лодке, что и вы. Поэтому у меня есть очень простое руководство: don't.
Я с удовольствием использую XmlWriter
как способ быстро написать xml, но я бы прогулялся по горячим углям, прежде чем выбрать реализацию IXmlSerializable
в другой раз - я бы просто написал отдельную DTO
и наложил на нее данные; это также означает, что схема (для "mex", "wsdl" и т.д.) приходит бесплатно.