26

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

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

附录:

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

4

6 回答 6

12

正如 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;
}

PS 已针对 .NET Compact Framework 3.5 进行了测试,但也适用于其他人。

于 2015-08-26T08:50:42.930 回答
10

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

如果你使用XmlTextReader而不是 just XmlReader,那么它就会实现IXmlLineInfo,这意味着你可以随时要求LineNumberand LinePosition- 这对你来说足够了吗?(诚​​然,您可能应该先检查一下HasLineInfo()。)

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

于 2010-01-29T07:43:34.020 回答
8

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

XmlReader必须StreamReader使用底层证券创建FileStream(我没有尝试过其他Streams,只要他们报告自己的位置,它们就可以正常工作)。

我在这里发布了详细信息:http: //gmac.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
}
于 2013-11-11T06:03:27.910 回答
3

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

所以我决定操作两个只读 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,但这可能会因为编码而变得棘手。

于 2010-05-06T14:28:23.020 回答
2

感谢杰夫的回答。它在 Windows 7 上完美运行。但不知何故,在 mscorlib.dll 的 Windows Server 2003 上使用 .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 也应该被窥视以推进指针。

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;
    }
于 2014-04-07T23:14:35.303 回答
1
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);
            }
于 2019-12-05T16:42:19.457 回答