有没有办法获取 XmlReader 检查的节点流中的当前位置?
我想使用 XmlReader 来解析文档并保存某些元素的位置,以便以后可以查找它们。
附录:
我正在获取由 WPF 控件生成的 Xaml。Xaml 不应经常更改。Xaml 中有我需要替换项目的占位符,有时是循环的。我认为用代码而不是转换可能更容易(我可能错了)。我的想法是将其解析为需要替换的内容和位置的简单数据结构,然后使用 StringBuilder 通过从 xaml 字符串中复制块来生成最终输出。
正如 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 进行了测试,但也适用于其他人。
只是在提出一个建议之前阻止它:您可以保留对您传入的底层流的引用XmlReader
,并记下它的位置 - 但这会给您带来错误的结果,因为读者几乎肯定会缓冲它的输入(即它将读取前 1024 个字符或其他任何内容 - 因此您的第一个节点可能“出现”在字符 1024 处)。
如果你使用XmlTextReader
而不是 just XmlReader
,那么它就会实现IXmlLineInfo
,这意味着你可以随时要求LineNumber
and LinePosition
- 这对你来说足够了吗?(诚然,您可能应该先检查一下HasLineInfo()
。)
编辑:我刚刚注意到您希望以后能够找到那个位置......在这种情况下,线路信息可能没有太大帮助。它非常适合在文本编辑器中查找内容,但对于移动文件指针却不是很好。你能提供更多关于你想要做什么的信息吗?可能有更好的方法来解决这个问题。
我已经为此研究了一个解决方案,虽然它可能不适用于所有场景并且对 .NET Framework 类的私有成员使用反射,但我能够XmlReader
使用下面显示的扩展方法计算 的正确位置。
您XmlReader
必须StreamReader
使用底层证券创建FileStream
(我没有尝试过其他Streams
,只要他们报告自己的位置,它们就可以正常工作)。
我在这里发布了详细信息: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
}
我有同样的问题,显然没有简单的解决方案。
所以我决定操作两个只读 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;
}
}
}
这不是最好的方法,因为它使用两个阅读器。为避免这种情况,我们可以重写在 XmlReader 和行计数器之间共享的新 FileReader。但它只是为您提供了您感兴趣的行的偏移量。要获得标记的确切偏移量,我们应该使用 LinePosition,但这可能会因为编码而变得棘手。
感谢杰夫的回答。它在 Windows 7 上完美运行。但不知何故,在 mscorlib.dll 的 Windows Server 2003 上使用 .net 4 版本,我不得不更改以下 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;
}
GetPosition 方法中的底层StreamReader 也应该被窥视以推进指针。
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;
}
try
{
Auto[] Autobestand = new Auto[5];
int Autoindex = 0;
OpenFileDialog ofd = new OpenFileDialog()
{
Filter = "(*.xml)|*.xml",
Title = "Datei öffnen"
};
if (ofd.ShowDialog() == DialogResult.OK)
{
XmlReader xr = new XmlTextReader(ofd.FileName);
while (xr.Read())
{
if (xr.NodeType == XmlNodeType.Element)
{
if (xr.Name == "Auto")
{
Autobestand[Autoindex] = new Auto(xr.ReadSubtree());
Autoindex++;
}
}
}
tb_Besitzer_1.Text = Autobestand[0].GetBestitzer();
tb_Besitzer_2.Text = Autobestand[1].GetBestitzer();
tb_Besitzer_3.Text = Autobestand[2].GetBestitzer();
tb_Besitzer_4.Text = Autobestand[3].GetBestitzer();
tb_Beschleunigung_1.Text = Autobestand[0].GetBeschleunigung();
tb_Beschleunigung_2.Text = Autobestand[1].GetBeschleunigung();
tb_Beschleunigung_3.Text = Autobestand[2].GetBeschleunigung();
tb_Beschleunigung_4.Text = Autobestand[3].GetBeschleunigung();
tb_Farbe_1.Text = Autobestand[0].GetFarbe();
tb_Farbe_2.Text = Autobestand[1].GetFarbe();
tb_Farbe_3.Text = Autobestand[2].GetFarbe();
tb_Farbe_4.Text = Autobestand[3].GetFarbe();
tb_Leistung_1.Text = Autobestand[0].GetLeistung();
tb_Leistung_2.Text = Autobestand[1].GetLeistung();
tb_Leistung_3.Text = Autobestand[2].GetLeistung();
tb_Leistung_4.Text = Autobestand[3].GetLeistung();
tb_Modell_1.Text = Autobestand[0].GetModell();
tb_Modell_2.Text = Autobestand[1].GetModell();
tb_Modell_3.Text = Autobestand[2].GetModell();
tb_Modell_4.Text = Autobestand[3].GetModell();
}
}
catch (Exception ex)
{
MessageBox.Show("Fehler" + ex.Message);
}