复制&;将元素附加到XML文档而不缓冲到RAM
本文关键字:文档 XML RAM 缓冲 amp 元素 复制 | 更新日期: 2023-09-27 18:10:45
正如标题所示,我需要将日志数据附加到XML文件中,而不需要缓冲RAM。XML文件由LogEntry元素组成,其中包含82个子元素,这些子元素包含数据。这些文件可能会变得很大,由于它将成为Windows CE6程序的一部分,我们的内存非常有限。
经过大量的研究,很明显,最常见的方法是使用XDocument
或Linq to XML
在附加到现有文档并写出新文档之前先读入现有文档。协同使用XmlWriter
和XmlReader
似乎是我附加到文件的最佳方式,但到目前为止,我的所有尝试都非常不切实际,并且需要IF语句来指导要写什么,以防止写入重复或无数据元素。
我所做的事情的本质是:
//Create an XmlReader to read current WorkLog.
using (XmlReader xmlRead = XmlTextReader.Create("WorkLog.xml"))
{
//Create a XmlWriterSettings and set indent
//to true to correctly format the document
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.Indent = true;
writerSettings.IndentChars = "'t";
//Create a new XmlWriter to output to
using (XmlWriter xmlWriter = XmlWriter.Create("New.xml", writerSettings))
{
//Starts the document
xmlWriter.WriteStartDocument();
//While the XmlReader is still reading (essentially !EOF)
while (xmlRead.Read())
{
//FSM to direct writing of OLD Log data to new file
switch (xmlRead.NodeType)
{
case XmlNodeType.Element:
//Handle the copying of an element node
//Contains many if statements to handle root node &
//attributes and to skip nodes that contain text
break;
case XmlNodeType.Text:
//Handle the copying of an text node
break;
case XmlNodeType.EndElement:
//Handle the copying of an End Element node
break;
}
}
xmlWriter.WriteEndDocument();
}
}
我相信我可以用这种方式附加到文件中,但这样做是非常不切实际的——有人知道我搜索了几个小时还没有找到任何内存高效的方法吗?
如果需要的话,我很乐意发布我当前的代码来完成这项工作,但正如我所提到的,它非常大,目前实际上非常糟糕,所以我暂时不考虑它。
如果您已经知道您的xml结构,请考虑使用流编写器。1.将文件作为文件流打开2.将点移动到你想要替换的标签,比如:,将你的点(位置(移动到"<"3.用正确的xml格式写入日志数据,并在写入的末尾写入">
"用文本编辑器处理xml文件">
如果黑客攻击是合理的,我会走到文件的末尾,回放结束标记,并写入新元素和结束标记。为了进一步改进,您甚至可以缓存最后一个元素开头的偏移量。
您使用XmlReader
的方法实际上是一条。。。但正如你所说,这是非常不切实际的。
那么黑客攻击是合理的吗
这样做的原因是XML有一系列您可能会遇到的特性,这些特性要求您从上到下阅读它。通常情况下,XmlReader
会处理这些情况,只留下简单的标记等等
<!ENTITY % pub "Éditions Gallimard" >
<!ENTITY rights "All rights reserved" >
<!ENTITY book "La Peste: Albert Camus, © 1947 %pub;. &rights;" >
则实体CCD_ 7的替换文本为:
La Peste: Albert Camus,
© 1947 Éditions Gallimard. &rights;
如果您还没有阅读ENTITY
标记,就不可能将其"翻译"为正确的XML。也就是说,幸运的是,没有太多人使用这类构造,所以可以假设XML不使用它们来重写根标记。
也就是说,在XML中关闭标记的唯一有效方法是使用</Foo>
,在后面的>
之前加上可选空格。(参见http://www.w3.org/TR/2008/REC-xml-20081126/#sec-starttags(。这基本上意味着你可以跳到最后,读取足够的数据,检查它是否包含结束标记——如果包含,你可以插入自己的代码。如果没有,再找回来再试一次。
讨厌的小编码
最后需要注意的是文件的编码。虽然您可以从流中构造XmlTextReader
,但流使用字节,读取器检测编码并开始读取。幸运的是,XmlTextReader
将Encoding
公开为属性,因此您可以使用它。编码很重要,因为每个字符可能需要超过1个字节;尤其是当你遇到UTF-16或UTF-32时,这可能是一个问题。处理此问题的方法是将令牌转换为字节,然后对字节进行匹配。
根=尾部假设
由于我真的不想检查空格和尾随的'>'(请参阅上面的W3C链接(,我还认为它是一个有效的XML文件,这意味着每个打开的标记都是关闭的。这意味着您可以简单地检查</root
,从而使匹配过程更加简单。(注意:您甚至可以只检查文件中的最后一个</
,但我更希望我的代码对不正确的XML更加健壮(
将其整合在一起
给。。。(我还没有测试过,但它应该或多或少能起作用(
public bool FindAppendPoint(Stream stream)
{
XmlTextReader xr = new XmlTextReader(stream);
string rootElement = null;
while (xr.Read())
{
if (xr.NodeType == XmlNodeType.Element)
{
rootElement = xr.Name;
break;
}
}
if (rootElement == null)
{
// Well, apparently there's no root... You can start a new file I suppose
return false;
}
else
{
long start = stream.Position; // the position we're currently reading (end of start tag)
long len = stream.Length;
long end = Math.Min(start, len - 1024);
byte[] endTag = xr.Encoding.GetBytes("</" + rootElement);
while (end >= start)
{
byte[] data = new byte[len - end];
stream.Seek(start, SeekOrigin.Begin);
stream.Read(data, 0, data.Length); // FIXME: read returns an int that we should use!!!
// Loop backwards till we find the end tag
for (int i = data.Length - endTag.Length; i >= 0; --i)
{
int j;
for (j = 0; j < endTag.Length && endTag[j] == data[i + j]; ++j) { }
if (j == endTag.Length)
{
// We found a match!
stream.Seek(len - data.Length - i, SeekOrigin.Begin);
AppendXml(stream, xr.Encoding)
return true;
}
}
// Hmm, we've found </xml with a lot of spaces... oh well
//
// It's okay to skip back a bit, just have to make sure that we don't skip <0
if (end == start)
{
end = start - 1; // end the loop
}
else
{
end = Math.Min(start, end - 1024);
}
}
// Nope, no go.
return false;
}
}
只有使用XmlReader才能在内存中加载完整的XML。它也不支持修改,但您可以通过修改从源文档中复制XML。没有其他办法。
将XML解析为文本文档看起来很困难。
最好使用XmlReader/XmlWriter正在解析的类,并且crud逻辑已经通过使用Visitor或State GoF模式的自己的类实现来实现。访问者模式将减少if-s的数量,并使您的设计易于扩展。即使您希望不使用XmlReader/XmlWriter来解析XML文档,我也建议您在这种情况下使用它们。
假设日志文件是这样的(只有两个级别(:
<logs>
<Log>abc1</Log>
<Log>abc1</Log>
<Log>abc1</Log>
</logs>
我使用FileStream
来寻找结束并读取结束元素。
private static void Append(string xmlElement)
{
const byte lessThan = (byte) '<';
using (FileStream stream = File.Open(@"C:'log.xml", FileMode.OpenOrCreate))
{
if (stream.Length == 0)
{
byte[] rootElement = Encoding.UTF8.GetBytes("<Logs></Logs>");
stream.Write(rootElement, 0, rootElement.Length);
}
List<byte> buffer = new List<byte>();
stream.Seek(0, SeekOrigin.End);
do
{
stream.Seek(-1, SeekOrigin.Current);
buffer.Insert(0, (byte) stream.ReadByte());
stream.Seek(-1, SeekOrigin.Current);
} while (buffer[0] != lessThan);
byte[] toAdd = Encoding.UTF8.GetBytes(xmlElement);
stream.Write(toAdd, 0, toAdd.Length);
stream.Write(buffer.ToArray(), 0, buffer.Count);
}
}