получение текущей позиции из XmlReader - PullRequest
21 голосов
/ 29 января 2010

Есть ли способ получить текущую позицию в потоке исследуемого узла XmlReader?

Я хотел бы использовать XmlReader для анализа документа и сохранения позиции определенных элементов, чтобычто я могу искать их позже.

Приложение:

Я получаю Xaml, сгенерированный элементом управления WPF.Xaml не должен часто меняться.В Xaml есть заполнители, где мне нужно заменить элементы, иногда зацикливаясь.Я думал, что это может быть легче сделать в коде, чем в преобразовании (я могу ошибаться в этом).Моя идея состояла в том, чтобы проанализировать простую структуру данных того, что нужно заменить и где это находится, а затем использовать StringBuilder для получения окончательного результата путем копирования фрагментов из строки xaml.

Ответы [ 5 ]

9 голосов
/ 26 августа 2015

Как говорит Джон Скит, 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;
}

P.S. Протестировано для .NET Compact Framework 3.5, но должно работать и для других.

8 голосов
/ 11 ноября 2013

Я работал над решением для этого, и, хотя оно может работать не во всех сценариях и использует отражение против частных членов классов .NET Framework, я могу рассчитать правильную позицию XmlReader с помощью метода расширения. показано ниже.

Ваш XmlReader должен быть создан из StreamReader с использованием базового FileStream (я не пробовал другие Streams, и они могут работать так же хорошо, пока сообщают о своей позиции).

Я разместил подробности здесь: http://g -m-a-c.blogspot.com / 2013/11 / define-точное-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
}
8 голосов
/ 29 января 2010

Просто чтобы отклонить одно предложение до того, как оно будет сделано: вы могли бы сохранить ссылку на базовый поток, который вы передаете в XmlReader, и записать его позицию - но это даст вам неправильную информацию результаты, поскольку читатель почти наверняка будет буферизовать свои входные данные (то есть он будет читать первые 1024 символа или что-то еще - так что ваш первый узел может «казаться» находящимся в символе 1024).

Если вы используете XmlTextReader вместо просто XmlReader, то это реализует IXmlLineInfo, что означает, что вы можете запросить LineNumber и LinePosition в любое время - достаточно ли это для вас? (Вам, вероятно, следует сначала проверить HasLineInfo()).

РЕДАКТИРОВАТЬ: Я только что заметил, что вы хотите иметь возможность искать эту позицию позже ... в этом случае информация строки может быть не очень полезна. Он отлично подходит для поиска чего-либо в текстовом редакторе, но не так хорош для перемещения указателя файла. Не могли бы вы дать больше информации о том, что вы пытаетесь сделать? Возможно, есть лучший способ решения проблемы.

3 голосов
/ 06 мая 2010

У меня та же проблема, и, видимо, простого решения не существует.

Поэтому я решил манипулировать двумя 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;
        }
    }            
}

Это не лучший способ сделать это, потому что он использует двух читателей. Чтобы избежать этого, мы могли бы переписать новый FileReader, совместно используемый XmlReader и счетчиком строк. Но это просто дает вам смещение линии, в которой вы заинтересованы. Чтобы получить точное смещение тега, мы должны использовать LinePosition, но это может быть сложно из-за кодировки.

2 голосов
/ 08 апреля 2014

Спасибо, Джефф за ответ. Он отлично работал на Windows 7. Но почему-то с .net 4 версии на Windows Server 2003 mscorlib.dll, мне пришлось изменить следующие 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;
    }

Также для получения указателя следует использовать метод underStreamReader в методе GetPosition.

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