XmlReader 的行为与换行符不同

pap*_*zzo 5 .net c# xml xmlwriter xmlreader

如果数据在一行上 index=int.Parse(logDataReader.ReadElementContentAsString()); ,则 value=double.Parse(logDataReader.ReadElementContentAsString(), 使光标向前移动。如果我取消这些调用,我会看到它在调试中循环了 6 次。

在下面<data>的第一个 ( <logData id="Bravo">) 中只读取了3 个(并且它们是错误的,因为该值用于下一个索引)。在第二个 ( <logData id="Bravo">) 上,所有内容<data>都已读取。

由于该文件是动态创建的(由 XMLwriter),因此无法编辑 xml 并插入换行符。该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>
Run Code Online (Sandbox Code Playgroud)

还有我的代码:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

dbc*_*dbc 6

你的问题如下。根据该文件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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后修改你的代码如下:

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;
}       
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 总是更喜欢使用XmlReader分别指定本地名称和命名空间的方法,例如XmlReader.ReadToFollowing (String,?String). 当您使用诸如XmlReader.ReadToFollowing(String)接受单个限定名称的方法时,您隐式地硬编码了 XML前缀的选择,这通常不是一个好主意。XML 解析应该独立于前缀选择。

  • 虽然您使用CultureInfo.InvariantCulture语言环境正确解析了 double ,但使用XmlConvert类中的方法来正确处理解析和格式设置会更容易。

  • XmlReader.ReadSubtree()XmlReader定位EndElement正在读取的元素的节点上,因此您无需ReadToFollowingOrCurrent()事后调用。(ReadSubtree()顺便说一下,避免阅读太少或太多的好用法;通过使用这种方法,可以避免使用 的几个常见错误XmlReader。)

  • 正如您所发现的,使用手动读取 XML 的代码XmlReader应该始终使用格式化和未格式化的 XML 进行单元测试,因为某些错误只会出现在其中一个中。(参见例如此答案这一个这一个也针对这样的其它实例。)

工作示例 .Net fiddle在这里