XmlReader ведет себя по-разному с переносами строк
Если данные находятся в одной строке, index=int.Parse(logDataReader.ReadElementContentAsString());
а такжеvalue=double.Parse(logDataReader.ReadElementContentAsString(),
заставить курсор двигаться вперед. Если я возьму эти вызовы, я увижу это цикл 6 раз в отладке.
В следующем только 3 <data>
читаются (и они неверны, так как значение для следующего индекса) на первом (<logData id="Bravo">
). На втором (<logData id="Bravo">
) все <data>
читаются
Это не возможность редактировать xml и вставлять разрывы строк, так как этот файл создается динамически (XMLwriter). NewLineChars
настройка - перевод строки. От XMLwriter это на самом деле всего одна строка - я разбил ее, чтобы выяснить, где она ломалась. В браузере он отображается правильно.
Как это исправить?
Вот мой XML:
<?xml version="1.0" encoding="utf-8"?>
<log>
<logData id="Alpha">
<data><index>100</index><value>150</value></data>
<data><index>110</index><value>750</value></data>
<data><index>120</index><value>750</value></data>
<data><index>130</index><value>150</value></data>
<data><index>140</index><value>0</value></data>
<data><index>150</index><value>222</value></data>
</logData>
<logData id="Bravo">
<data>
<index>100</index>
<value>25</value>
</data>
<data>
<index>110</index>
<value>11</value>
</data>
<data>
<index>120</index>
<value>1</value>
</data>
<data>
<index>130</index>
<value>25</value></data>
<data>
<index>140</index>
<value>0</value>
</data>
<data>
<index>150</index>
<value>1</value>
</data>
</logData>
</log>
И мой код:
static void Main(string[] args)
{
List<LogData> logDatas = GetLogDatasFromFile("singleVersusMultLine.xml");
Debug.WriteLine("Main");
Debug.WriteLine("logData");
foreach (LogData logData in logDatas)
{
Debug.WriteLine($" logData.ID {logData.ID}");
foreach(LogPoint logPoint in logData.LogPoints)
{
Debug.WriteLine($" logData.Index {logPoint.Index} logData.Value {logPoint.Value}");
}
}
Debug.WriteLine("end");
}
public static List<LogData> GetLogDatasFromFile(string xmlFile)
{
List<LogData> logDatas = new List<LogData>();
using (XmlReader reader = XmlReader.Create(xmlFile))
{
// move to next "logData"
while (reader.ReadToFollowing("logData"))
{
var logData = new LogData(reader.GetAttribute("id"));
using (var logDataReader = reader.ReadSubtree())
{
// inside "logData" subtree, move to next "data"
while (logDataReader.ReadToFollowing("data"))
{
// move to index
logDataReader.ReadToFollowing("index");
// read index
var index = int.Parse(logDataReader.ReadElementContentAsString());
// move to value
logDataReader.ReadToFollowing("value");
// read value
var value = double.Parse(logDataReader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
logData.LogPoints.Add(new LogPoint(index, value));
}
}
logDatas.Add(logData);
}
}
return logDatas;
}
public class LogData
{
public string ID { get; }
public List<LogPoint> LogPoints { get; } = new List<LogPoint>();
public LogData (string id)
{
ID = id;
}
}
public class LogPoint
{
public int Index { get; }
public double Value { get; }
public LogPoint ( int index, double value)
{
Index = index;
Value = value;
}
}
3 ответа
Ваша проблема заключается в следующем. Согласно документации для XmlReader.ReadElementContentAsString()
:
Этот метод читает начальный тег, содержимое элемента и перемещает читателя за тег конечного элемента.
И из документации для XmlReader.ReadToFollowing(String)
:
Он продвигает читателя к следующему следующему элементу, который соответствует указанному имени, и возвращает true, если соответствующий элемент найден.
Таким образом, после звонка ReadElementContentAsString()
, поскольку читатель был продвинут на следующий узел, он может быть уже расположен на следующем узле <value>
или же <data>
узел. Затем, когда вы звоните ReadToFollowing()
этот элементный узел пропускается, потому что метод безусловно переходит к следующему узлу с правильным именем. Но если XML имеет отступ, то следующий узел сразу после вызова ReadElementContentAsString()
будет XmlNodeType.Whitespace
узел, защищающий от этой ошибки.
Решение состоит в том, чтобы проверить, правильно ли установлен считыватель после вызова ReadElementContentAsString()
, Сначала введите следующий метод расширения:
public static class XmlReaderExtensions
{
public static bool ReadToFollowingOrCurrent(this XmlReader reader, string localName, string namespaceURI)
{
if (reader == null)
throw new ArgumentNullException(nameof(reader));
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == localName && reader.NamespaceURI == namespaceURI)
return true;
return reader.ReadToFollowing(localName, namespaceURI);
}
}
Затем измените ваш код следующим образом:
public static List<LogData> GetLogDatasFromFile(string xmlFile)
{
List<LogData> logDatas = new List<LogData>();
using (XmlReader reader = XmlReader.Create(xmlFile))
{
// move to next "logData"
while (reader.ReadToFollowing("logData", ""))
{
var logData = new LogData(reader.GetAttribute("id"));
using (var logDataReader = reader.ReadSubtree())
{
// inside "logData" subtree, move to next "data"
while (logDataReader.ReadToFollowing("data", ""))
{
// move to index
logDataReader.ReadToFollowing("index", "");
// read index
var index = XmlConvert.ToInt32(logDataReader.ReadElementContentAsString());
// move to value
logDataReader.ReadToFollowingOrCurrent("value", "");
// read value
var value = XmlConvert.ToDouble(logDataReader.ReadElementContentAsString());
logData.LogPoints.Add(new LogPoint(index, value));
}
}
logDatas.Add(logData);
}
}
return logDatas;
}
Заметки:
Всегда предпочитаю использовать
XmlReader
методы, в которых локальное имя и пространство имен указываются отдельно, например,XmlReader.ReadToFollowing (String, String)
, Когда вы используете метод, такой какXmlReader.ReadToFollowing(String)
который принимает одно квалифицированное имя, вы неявно жестко программируете выбор префикса XML, что, как правило, не очень хорошая идея. Синтаксический анализ XML не должен зависеть от выбора префикса.В то время как вы правильно проанализировали свой дубль, используя
CultureInfo.InvariantCulture
локаль, еще проще использовать методы изXmlConvert
класс для правильной обработки и форматирования.XmlReader.ReadSubtree()
оставляетXmlReader
расположен наEndElement
узел читаемого элемента, поэтому вам не нужно вызыватьReadToFollowingOrCurrent()
после этого. (Хорошее использованиеReadSubtree()
кстати, избегать читать слишком мало или слишком много; с помощью этого метода можно избежать нескольких частых ошибок сXmlReader
.)Как вы обнаружили, код, который вручную читает XML, используя
XmlReader
должен всегда подвергаться модульному тестированию как с отформатированным, так и с неформатированным XML, потому что определенные ошибки будут возникать только с одним или другим. (См., Например, этот ответ, этот и этот, а также другие примеры такого.)
Рабочий образец.Net скрипка здесь.
Действительно, этот код (который я вам предоставил в вашем другом вопросе) неверен. ReadToFollowing
будет читать следующий элемент с этим именем, даже если его курсор уже расположен на элементе с этим именем. Когда есть пробел - после прочтения index
курсор перемещается в этот пробел и ReadToFollowing("value")
работает как вы ожидаете. Однако, если пробелов нет, курсор уже включен value
узел и так ReadToFollowing("value")
читает до следующего "значения" в последующем узле "данных".
Я думаю, что следующий подход будет более безопасным:
public static List<LogData> GetLogDatasFromFile(string xmlFile) {
List<LogData> logDatas = new List<LogData>();
using (XmlReader reader = XmlReader.Create(xmlFile)) {
LogData currentData = null;
while (reader.Read()) {
if (reader.IsStartElement("logData")) {
// we are positioned on start of logData
if (currentData != null)
logDatas.Add(currentData);
currentData = new LogData(reader.GetAttribute("id"));
}
else if (reader.IsStartElement("data")) {
// we are on start of "data"
// we always have "currentData" at this point
Debug.Assert(currentData != null);
reader.ReadToFollowing("index");
var index = int.Parse(reader.ReadElementContentAsString());
// check if we are not already on "value"
if (!reader.IsStartElement("value"))
reader.ReadToFollowing("value");
var value = double.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
currentData.LogPoints.Add(new LogPoint(index, value));
}
}
if (currentData != null)
logDatas.Add(currentData);
}
return logDatas;
}
Я нашел решение, но для меня не приемлемый ответ. XMLreader не должен вести себя по-разному с переносами строк.
В XmlWriter
это вставит разрывы строк в тексте:
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.NewLineOnAttributes = true;
xmlWriterSettings.Indent = true;
using (XmlWriter xmlWriter = XmlWriter.Create(fileNameXML, xmlWriterSettings))
{
Я нашел это здесь.