Is there a way to get the current position in the stream of the node under examination by the XmlReader?
I'd like to use the XmlReader to parse a document and save the position of certain elements so that I can seek to them later.
Addendum:
I'm getting Xaml generated by a WPF control. The Xaml should not change frequently. There are placeholders in the Xaml where I need to replace items, sometimes looping. I thought it might be easier to do in code rather than a transform (I might be wrong about this). My idea was to parse it to a simple data structure of what needs to be replace and where it is, then use a StringBuilder to produce the final output by copying chunks from the xaml string.
As Jon Skeet says, XmlTextReader
implements IXmlLineInfo
but XmlTextReader
was deprecated since .NET 2.0
and the question is about XmlReader
only.
I found this solution:
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. Tested for .NET Compact Framework 3.5, but should work for others too.
Just to head off one suggestion before it's made: you could keep a reference to the underlying stream you pass into XmlReader
, and make a note of its position - but that will give you the wrong results, as the reader will almost certainly be buffering its input (i.e. it'll read the first 1024 characters or whatever - so your first node might "appear" to be at character 1024).
If you use XmlTextReader
instead of just XmlReader
, then that implements IXmlLineInfo
, which means you can ask for the LineNumber
and LinePosition
at any time - is that good enough for you? (You should probably check HasLineInfo()
first, admittedly.)
EDIT: I've just noticed that you want to be able to seek to that position later... in that case line information may not be terribly helpful. It's great for finding something in a text editor, but not so great for moving a file pointer. Could you give some more information about what you're trying to do? There may be a better way of approaching the problem.
I have worked on a solution for this, and while it may not work in every scenario and uses reflection against private members of .NET Framework classes, I am able to calculate the correct position of the XmlReader
with the extension method shown below.
Your XmlReader
must be created from a StreamReader
using an underlying FileStream
(I haven't tried other Streams
, and they may work as well so long as they report their position).
I've posted details here: 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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With