Получение текущей позиции из 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;
}