【问题标题】:getting the current position from an XmlReader从 XmlReader 获取当前位置
【发布时间】:2010-01-29 07:32:18
【问题描述】:

有没有办法获取XmlReader正在检查的节点在流中的当前位置?

我想使用 XmlReader 来解析文档并保存某些元素的位置,以便以后查找它们。

附录:

我正在获取由 WPF 控件生成的 Xaml。 Xaml 不应经常更改。 Xaml 中有我需要替换项​​目的占位符,有时是循环的。我认为用代码而不是转换可能更容易(我可能错了)。我的想法是将其解析为需要替换的内容和位置的简单数据结构,然后使用 StringBuilder 通过从 xaml 字符串中复制块来生成最终输出。

【问题讨论】:

  • 你在这方面有什么成功吗?我想做类似的事情(计算某些元素的流偏移量,然后寻找该偏移量并从那里解析),到目前为止我能看到的最好的方法是解析文件两次以计算偏移量。
  • @Rob 不。我最终每次都使用 XmlReader/XmlWriter 来处理文档。它们对我的目的来说足够快,我可以放弃这种优化。

标签: .net xmlreader


【解决方案1】:

正如 Jon Skeet 所说,XmlTextReader 实现了 IXmlLineInfoXmlTextReader 已被弃用,因为 .NET 2.0 并且问题仅与 XmlReader 有关。 我找到了这个解决方案:

XmlReader xr = XmlReader.Create( // MSDN recommends to use Create() instead of ctor()
    new StringReader("<some><xml><string><data>"),
    someSettings // furthermore, can't set XmlSettings on XmlTextReader
);
IXmlLineInfo xli = (IXmlLineInfo)xr;

while (xr.Read())
{
    // ... some read actions ...

    // current position in StringReader can be accessed through
    int line = xli.LineNumber;
    int pos  = xli.LinePosition;
}

附:已针对 .NET Compact Framework 3.5 进行了测试,但也适用于其他版本。

【讨论】:

  • 完美解决方案;其他的似乎很复杂。也许早在 2010 年,这不可用??
  • 需要知道整个文件的字节位置,这对我来说效果很好,因为 GB 文件对我的问题来说只有一行。
【解决方案2】:

只是在提出一个建议之前阻止它:您可以保留对您传递到 XmlReader 的底层流的引用,并记下它的位置 - 但这会给您错误的结果,因为阅读器几乎肯定会缓冲其输入(即它将读取前 1024 个字符或其他任何内容 - 因此您的第一个节点可能“出现”在第 1024 个字符处)。

如果您使用XmlTextReader 而不仅仅是XmlReader,那么它实现了IXmlLineInfo,这意味着您可以随时请求LineNumberLinePosition - 这对您来说足够了吗? (诚​​然,您应该先检查HasLineInfo()。)

编辑:我刚刚注意到您希望稍后能够找到该位置......在这种情况下,线路信息可能没有太大帮助。它非常适合在文本编辑器中查找某些内容,但对于移动文件指针却不是那么好。你能提供更多关于你想要做什么的信息吗?可能有更好的方法来解决这个问题。

【讨论】:

  • 对于我的原型,我将 xml 写入写入器,刷新它,并从该流中获取长度。它工作得很好,但我想转向更清洁、内存更少的东西。谢谢指点。
  • 看起来 XmlTextReader 实现了 IXmlLineInfo。
  • @JonSkeet:我现在有同样的问题,我有一个由Stream 对象构建的XMLReader 对象。我希望能够在阅读器上保存当前位置,然后迭代一些元素,然后恢复它的位置。所以,我试图保存 stream.Position 并在稍后通过 XMlReader 对象对其进行处理后将其设置回来,但它不起作用。你有什么想法吗?
  • @JavaSa:听起来你应该用一个简短但完整的例子提出一个新问题。
  • @JonSkeet:我想通过克隆流并处理两个不同的流来解决这个问题。如果你能和我分享你的想法,我会很高兴。问题:stackoverflow.com/questions/13962015/…
【解决方案3】:
try
            {
                Auto[] Autobestand = new Auto[5];

                int Autoindex = 0; 

                OpenFileDialog ofd = new OpenFileDialog()
                {
                    Filter = "(*.xml)|*.xml",
                    Title = "Datei öffnen"
                };

                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    XmlReader xr = new XmlTextReader(ofd.FileName);

                    while (xr.Read())
                    {
                        if (xr.NodeType == XmlNodeType.Element)
                        {
                            if (xr.Name == "Auto")
                            {
                                Autobestand[Autoindex] = new Auto(xr.ReadSubtree());

                                Autoindex++;
                            }
                        }
                    }
                    tb_Besitzer_1.Text = Autobestand[0].GetBestitzer();
                    tb_Besitzer_2.Text = Autobestand[1].GetBestitzer();
                    tb_Besitzer_3.Text = Autobestand[2].GetBestitzer();
                    tb_Besitzer_4.Text = Autobestand[3].GetBestitzer();

                    tb_Beschleunigung_1.Text = Autobestand[0].GetBeschleunigung();
                    tb_Beschleunigung_2.Text = Autobestand[1].GetBeschleunigung();
                    tb_Beschleunigung_3.Text = Autobestand[2].GetBeschleunigung();
                    tb_Beschleunigung_4.Text = Autobestand[3].GetBeschleunigung();

                    tb_Farbe_1.Text = Autobestand[0].GetFarbe();
                    tb_Farbe_2.Text = Autobestand[1].GetFarbe();
                    tb_Farbe_3.Text = Autobestand[2].GetFarbe();
                    tb_Farbe_4.Text = Autobestand[3].GetFarbe();

                    tb_Leistung_1.Text = Autobestand[0].GetLeistung();
                    tb_Leistung_2.Text = Autobestand[1].GetLeistung();
                    tb_Leistung_3.Text = Autobestand[2].GetLeistung();
                    tb_Leistung_4.Text = Autobestand[3].GetLeistung();

                    tb_Modell_1.Text = Autobestand[0].GetModell();
                    tb_Modell_2.Text = Autobestand[1].GetModell();
                    tb_Modell_3.Text = Autobestand[2].GetModell();
                    tb_Modell_4.Text = Autobestand[3].GetModell();

                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Fehler" + ex.Message);
            }

【讨论】:

    【解决方案4】:

    我已经为此制定了一个解决方案,虽然它可能不适用于所有场景,并且对 .NET Framework 类的私有成员使用反射,但我能够使用扩展方法计算 XmlReader 的正确位置如下所示。

    您的XmlReader 必须使用基础FileStreamStreamReader 创建(我没有尝试过其他Streams,只要他们报告自己的位置,它们也可以工作)。

    我在这里发布了详细信息:http://g-m-a-c.blogspot.com/2013/11/determine-exact-position-of-xmlreader.html

    public static class XmlReaderExtensions
    {
        private const long DefaultStreamReaderBufferSize = 1024;
    
        public static long GetPosition(this XmlReader xr, StreamReader underlyingStreamReader)
        {
            // Get the position of the FileStream
            long fileStreamPos = underlyingStreamReader.BaseStream.Position;
    
            // Get current XmlReader state
            long xmlReaderBufferLength = GetXmlReaderBufferLength(xr);
            long xmlReaderBufferPos = GetXmlReaderBufferPosition(xr);
    
            // Get current StreamReader state
            long streamReaderBufferLength = GetStreamReaderBufferLength(underlyingStreamReader);
            int streamReaderBufferPos = GetStreamReaderBufferPos(underlyingStreamReader);
            long preambleSize = GetStreamReaderPreambleSize(underlyingStreamReader);
    
            // Calculate the actual file position
            long pos = fileStreamPos 
                - (streamReaderBufferLength == DefaultStreamReaderBufferSize ? DefaultStreamReaderBufferSize : 0) 
                - xmlReaderBufferLength 
                + xmlReaderBufferPos + streamReaderBufferPos - preambleSize;
    
            return pos;
        }
    
        #region Supporting methods
    
        private static PropertyInfo _xmlReaderBufferSizeProperty;
    
        private static long GetXmlReaderBufferLength(XmlReader xr)
        {
            if (_xmlReaderBufferSizeProperty == null)
            {
                _xmlReaderBufferSizeProperty = xr.GetType()
                                                 .GetProperty("DtdParserProxy_ParsingBufferLength",
                                                              BindingFlags.Instance | BindingFlags.NonPublic);
            }
    
            return (int) _xmlReaderBufferSizeProperty.GetValue(xr);
        }
    
        private static PropertyInfo _xmlReaderBufferPositionProperty;
    
        private static int GetXmlReaderBufferPosition(XmlReader xr)
        {
            if (_xmlReaderBufferPositionProperty == null)
            {
                _xmlReaderBufferPositionProperty = xr.GetType()
                                                     .GetProperty("DtdParserProxy_CurrentPosition",
                                                                  BindingFlags.Instance | BindingFlags.NonPublic);
            }
    
            return (int) _xmlReaderBufferPositionProperty.GetValue(xr);
        }
    
        private static PropertyInfo _streamReaderPreambleProperty;
    
        private static long GetStreamReaderPreambleSize(StreamReader sr)
        {
            if (_streamReaderPreambleProperty == null)
            {
                _streamReaderPreambleProperty = sr.GetType()
                                                  .GetProperty("Preamble_Prop",
                                                               BindingFlags.Instance | BindingFlags.NonPublic);
            }
    
            return ((byte[]) _streamReaderPreambleProperty.GetValue(sr)).Length;
        }
    
        private static PropertyInfo _streamReaderByteLenProperty;
    
        private static long GetStreamReaderBufferLength(StreamReader sr)
        {
            if (_streamReaderByteLenProperty == null)
            {
                _streamReaderByteLenProperty = sr.GetType()
                                                 .GetProperty("ByteLen_Prop",
                                                              BindingFlags.Instance | BindingFlags.NonPublic);
            }
    
            return (int) _streamReaderByteLenProperty.GetValue(sr);
        }
    
        private static PropertyInfo _streamReaderBufferPositionProperty;
    
        private static int GetStreamReaderBufferPos(StreamReader sr)
        {
            if (_streamReaderBufferPositionProperty == null)
            {
                _streamReaderBufferPositionProperty = sr.GetType()
                                                        .GetProperty("CharPos_Prop",
                                                                     BindingFlags.Instance | BindingFlags.NonPublic);
            }
    
            return (int) _streamReaderBufferPositionProperty.GetValue(sr);
        }
    
        #endregion
    }
    

    【讨论】:

    • 不使用 PreamableSize 的反射,你可以用一种更便携和面向未来的方式来做:sr.CurrentEncoding.GetPreamble().Length。此外,如果所述方法将返回一个 long,我是否建议您返回 Array.LongLength 或只坚持返回一个 int?
    • 事实上,这些属性在 .NET 中似乎都是 32 位整数
    • 关于您检查的位: (streamReaderBufferLength == DefaultStreamReaderBufferSize ? DefaultStreamReaderBufferSize : 0) - 大概这是因为流读取器误报了最后一个数据块的 StreamReader 缓冲区位置(为零)?我发现检查 sr.EndOfStream ,如果为真,则将位置设置为长度有效(基本上是相同的逻辑) - 但不同寻常的是,实际上检索 sr.EndOfStream 属性“固定”了通过反射报告的位置。哼哼。顺便说一句,很棒的解决方案。
    【解决方案5】:

    我有同样的问题,显然没有简单的解决方案。

    所以我决定操作两个只读 FileStream :一个用于 XmlReader,另一个用于获取每一行的位置:

    private void ReadXmlWithLineOffset()
    {
        string malformedXml = "<test>\n<test2>\r   <test3><test4>\r\n<test5>Thi is\r\ra\ntest</test5></test4></test3></test2>";
        string fileName = "test.xml";
        File.WriteAllText(fileName, malformedXml);
    
        XmlTextReader xr = new XmlTextReader(new FileStream(fileName, FileMode.Open, FileAccess.Read));
        FileStream fs2 = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    
        try
        {
            int currentLine = 1;
            while(xr.Read())
            {
                if (!string.IsNullOrEmpty(xr.Name))
                {
                    for (;currentLine < xr.LineNumber; currentLine++)
                        ReadLine(fs2);
                    Console.WriteLine("{0} : LineNum={1}, FileOffset={2}", xr.Name, xr.LineNumber, fs2.Position);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception : " + ex.Message);
        }
        finally
        {
            xr.Close();
            fs2.Dispose();
        }
    }
    
    private void ReadLine(FileStream fs)
    {
        int b;
        while ((b = fs.ReadByte()) >= 0)
        {
            if (b == 10) // \n
                return;
            if (b == 13) // \r
            {
                if (fs.ReadByte() != 10) // if not \r\n, go back one byte
                    fs.Seek(-1, SeekOrigin.Current);
                return;
            }
        }            
    }
    

    这不是最好的方法,因为它使用两个阅读器。为避免这种情况,我们可以重写在 XmlReader 和行计数器之间共享的新 FileReader。 但它只是为您提供了您感兴趣的线的偏移量。 要获得标签的确切偏移量,我们应该使用 LinePosition,但由于编码,这可能会很棘手。

    【讨论】:

      【解决方案6】:

      感谢杰夫的回答。它在 Windows 7 上完美运行。但不知何故,在 Windows Server 2003 的 mscorlib.dll 上使用 .net 4 版本,我不得不更改以下 2 个函数才能工作。

      private long GetStreamReaderBufferLength(StreamReader sr)
          {
              FieldInfo _streamReaderByteLenField = sr.GetType()
                                                  .GetField("charLen",
                                                              BindingFlags.Instance | BindingFlags.NonPublic);
      
              var fValue = (int)_streamReaderByteLenField.GetValue(sr);
      
              return fValue;
          }
      
          private int GetStreamReaderBufferPos(StreamReader sr)
          {
              FieldInfo _streamReaderBufferPositionField = sr.GetType()
                                                  .GetField("charPos",
                                                              BindingFlags.Instance | BindingFlags.NonPublic);
              int fvalue = (int)_streamReaderBufferPositionField.GetValue(sr);
      
              return fvalue;
          }
      

      GetPosition 方法中的底层StreamReader 也应该被peek 以推进指针。

      private long GetPosition(XmlReader xr, StreamReader underlyingStreamReader)
          {
              long pos = -1;
              while (pos < 0)
              {
                  // Get the position of the FileStream
                   underlyingStreamReader.Peek();
                  long fileStreamPos = underlyingStreamReader.BaseStream.Position;
      
                  //            long fileStreamPos = GetStreamReaderBasePosition(underlyingStreamReader);
                  // Get current XmlReader state
                  long xmlReaderBufferLength = GetXmlReaderBufferLength(xr);
                  long xmlReaderBufferPos = GetXmlReaderBufferPosition(xr);
      
                  // Get current StreamReader state
                  long streamReaderBufferLength = GetStreamReaderBufferLength(underlyingStreamReader);
                  long streamReaderBufferPos = GetStreamReaderBufferPos(underlyingStreamReader);
                  long preambleSize = GetStreamReaderPreambleSize(underlyingStreamReader);
      
      
                  // Calculate the actual file position
                  pos = fileStreamPos
                      - (streamReaderBufferLength == DefaultStreamReaderBufferSize ? DefaultStreamReaderBufferSize : 0)
                      - xmlReaderBufferLength
                      + xmlReaderBufferPos + streamReaderBufferPos;// -preambleSize;
              }
              return pos;
          }
      

      【讨论】:

      • 好消息。在那里你有充分展示访问私有框架成员的危险...... :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-15
      • 2016-12-26
      • 2022-10-05
      • 2014-05-20
      相关资源
      最近更新 更多