Получение текущей позиции из XmlReader

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

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

Приложение:

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

Ответ 1

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

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

EDIT: Я только что заметил, что вы хотите, чтобы иметь возможность искать эту позицию позже... в этом случае строка информации может быть не очень полезно. Это отлично подходит для поиска чего-то в текстовом редакторе, но не так хорошо для перемещения указателя файла. Не могли бы вы дать дополнительную информацию о том, что вы пытаетесь сделать? Возможно, есть лучший способ приблизиться к проблеме.

Ответ 2

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

Ваш XmlReader должен быть создан из StreamReader с использованием базового FileStream (я еще не пробовал другие 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
}

Ответ 3

Как говорит Джон Скит, 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, но должно работать и для других.

Ответ 4

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

Итак, я решил манипулировать двумя файловыми потоками только для чтения: один для 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, но это может быть сложно из-за кодирования.

Ответ 5

Спасибо Джеффу за ответ. Он отлично работал на 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;
    }