26

XmlReader によって検査中のノードのストリーム内の現在の位置を取得する方法はありますか?

XmlReader を使用してドキュメントを解析し、特定の要素の位置を保存して、後でシークできるようにしたいと考えています。

補遺:

WPF コントロールによって生成された Xaml を取得しています。Xaml は頻繁に変更しないでください。Xaml には、アイテムを置き換える必要があるプレースホルダーがあり、ループすることもあります。変換するよりもコードで行う方が簡単かもしれないと思いました (これについては間違っているかもしれません)。私のアイデアは、置換する必要があるものとその場所の単純なデータ構造に解析し、S​​tringBuilder を使用して、xaml 文字列からチャンクをコピーすることで最終出力を生成することでした。

4

6 に答える 6

12

Jon Skeet が言うように、XmlTextReader実装しIXmlLineInfoていますが、XmlTextReaderそれ以来非推奨で.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

それが行われる前に 1 つの提案を回避するために: に渡す基になるストリームへの参照を保持し、その位置をメモすることができXmlReaderますが、リーダーはほぼ確実にその入力をバッファリングするため、間違った結果が得られます。 (つまり、最初の 1024 文字などを読み取るため、最初のノードが文字 1024 にあるように「見える」場合があります)。

XmlTextReaderjust の代わりにを使用するとXmlReader、それが を実装しIXmlLineInfoます。つまり、LineNumberandLinePositionをいつでも要求できます。それで十分ですか? HasLineInfo()(確かに、最初に確認する必要があります。)

編集:後でその位置をシークできるようにしたいことに気づきました...その場合、行情報はあまり役に立たないかもしれません。テキスト エディターで何かを見つけるには最適ですが、ファイル ポインターを移動するのにはあまり適していません。あなたがやろうとしていることについて、もう少し情報を提供できますか?問題にアプローチするためのより良い方法があるかもしれません。

于 2010-01-29T07:43:34.020 に答える
8

私はこれに対する解決策に取り組みました。すべてのシナリオで機能するとは限らず、.NET Framework クラスのプライベート メンバーに対してリフレクションを使用しますが、XmlReader以下に示す拡張メソッドを使用して の正しい位置を計算できます。

あなたは、基礎となる を使用してXmlReaderから作成する必要があります(私は他の を試していません。その位置を報告する限り、それらはうまく機能する可能性があります)。 StreamReaderFileStreamStreams

ここに詳細を投稿しました: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

私は同じ問題を抱えており、明らかに簡単な解決策はありません。

そこで、2 つの読み取り専用 FileStream を操作することにしました。1 つは XmlReader 用、もう 1 つは各行の位置を取得するためです。

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

2 つのリーダーを使用するため、これは最善の方法ではありません。これを回避するには、XmlReader と行カウンターの間で共有される新しい FileReader を書き直すことができます。タグの正確なオフセットを取得するには、LinePosition を使用する必要がありますが、エンコーディングのために注意が必要です。

于 2010-05-06T14:28:23.020 に答える