复制&将元素附加到XML文档而不缓冲到RAM

时间:2013-04-24 11:39:02

标签: c# xml

正如标题所示,我需要将日志数据附加到XML文件而不缓冲到RAM。 XML文件由LogEntry元素组成,其中包含82个包含数据的子元素。这些文件可能变得非常大,因为它将构成Windows CE6程序的一部分,我们的内存非常有限。

经过大量研究后,最常见的方法是使用XDocumentLinq to XML来阅读现有文档,然后再附加并写出新文档。在演唱会中使用XmlWriterXmlReader似乎是我追加到文件的最佳方式,但到目前为止我的所有尝试都非常不切实际,并且需要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();
   }
}

我确信我可以通过这种方式附加到文件中,但这样做是非常不切实际的 - 有没有人知道我的搜索时间没有出现的任何内存有效方法?

如果需要,我很乐意发布我目前的代码来执行此操作 - 但正如我所提到的那样,它非常大并且实际上非常讨厌,所以我暂时将其删除。

5 个答案:

答案 0 :(得分:3)

如果您已了解xml结构,请考虑使用流编写器。 1.打开文件作为文件流 2.将点移动到要替换的标记,例如:,将您的点(位置)移动到“<” 3.以正确的xml格式写入日志数据,并在写入结束时写下“”

“使用文本编辑器处理xml文件”

答案 1 :(得分:2)

如果hack是合理的,我会转到文件的末尾,回过end标签并写入新元素和结束标记。为了进一步改进,您甚至可以缓存最后一个元素开头的偏移量。

答案 2 :(得分:1)

只有使用XmlReader才能在内存中加载完整的XML。它也不支持修改,但您可以通过修改从源文档复制XML。没有其他办法。

将XML解析为文本文档看起来很难。

最好使用类XmlReader / XmlWriter进行解析,并且已经使用您自己的类实现使用Visitor或State GoF模式实现了crud逻辑。访问者模式将减少if-s的数量,并使您的设计易于扩展。即使你不想使用XmlReader / XmlWriter解析XML文档,我建议你在这种情况下使用它们。

答案 3 :(得分:1)

假设日志文件是这样的(只有两个级别):

<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);
    }
}

答案 4 :(得分:1)

您使用XmlReader的方法实际上是要走的路......但正如您所说,这是非常不切实际的。

黑客是否合理?

原因是XML有许多您可能遇到的功能,需要您从上到下阅读它。通常XmlReader会处理这些情况,为您留下普通标签等等。例如,给出以下声明:

<!ENTITY % pub    "&#xc9;ditions Gallimard" >
<!ENTITY   rights "All rights reserved" >
<!ENTITY   book   "La Peste: Albert Camus, &#xA9; 1947 %pub;. &rights;" >

然后实体book的替换文本是:

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,但流使用字节,读者会检测编码并开始读取。幸运的是,XmlTextReaderEncoding公开为属性,因此您可以使用它。编码很重要,因为每个字符可能需要不超过1个字节;特别是当你遇到UTF-16或UTF-32时,这可能是一个问题。处理此问题的方法是将令牌转换为字节,然后在字节上进行匹配。

root =预告片假设

因为我真的不想检查空格并跟踪&#39;&gt;&#39; (参见上面的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;
    }
}