XmlReader ведет себя по-разному с переносами строк - PullRequest
0 голосов
/ 28 апреля 2018

Если данные в одной строке, 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 ]

0 голосов
/ 28 апреля 2018

Ваша проблема заключается в следующем. Согласно документации для XmlReader.ReadElementContentAsString():

Этот метод считывает начальный тег, содержимое элемента и перемещает считыватель на тег конечного элемента.

А из документации для XmlReader.ReadToFollowing(String):

Он продвигает читателя к следующему элементу next , который соответствует указанному имени, и возвращает 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 fiddle here .

0 голосов
/ 28 апреля 2018

Действительно, этот код (который я предоставил вам в вашем другом вопросе) неверен. ReadToFollowing будет читать следующий элемент с этим именем, даже если его курсор уже расположен на элементе с этим именем. Когда есть пробел - после того, как вы прочитаете index, курсор переместится на этот пробел и ReadToFollowing("value") будет работать, как вы ожидаете. Однако, если пробелов нет, курсор уже находится на узле value, поэтому ReadToFollowing("value") читает следующее «значение» в последующем узле «data».

Я думаю, что следующий подход будет более безопасным:

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;
}
0 голосов
/ 28 апреля 2018

Я нашел исправление, но для меня не приемлемый ответ. XMLreader не должен вести себя по-разному с переносами строк.

В XmlWriter это приведет к разрыву строки в тексте:

XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
xmlWriterSettings.NewLineOnAttributes = true;
xmlWriterSettings.Indent = true;
using (XmlWriter xmlWriter = XmlWriter.Create(fileNameXML, xmlWriterSettings))
{

Я нашел это здесь .

...