如何从C#中混合的xml /二进制文件的头读取XML数据

时间:2015-03-17 12:48:38

标签: c# xml xml-parsing

我的任务是为具有以下规范的文件格式编写阅读器:

  1. 第一部分是带有元数据的简单xml(utf-8);
  2. 最后一节是16位值的流(二进制);
  3. 这两个部分用值29的一个字节分隔(ASCII表中的组分隔符)。
  4. 我看到两种方法来读取文件的xml部分。 第一个是逐字节构建一个字符串,直到找到分隔符。

    另一种方法是使用一些解析xml的库并自动检测格式良好的xml的结尾。

    问题是:是否有任何.NET库会在XML中的最后一个结束标记之后自动停止?

    (或者,任何人都可以建议一种更健全的方式来阅读这种文件格式吗?)


    更新:根据Peter Duniho的回答,稍作修改,我最终得到了这个(虽然没有经过彻底的单元测试,但仍有效)。

            int position = 0;
            MemoryStream ms;
    
            using (FileStream fs = File.OpenRead("file.xml"))
            using (ms = new MemoryStream())
            {
                int current;
                while ((current = fs.ReadByte()) > 0)
                {
                    position++;
    
                    if (current == 29)
                        break;
    
                    ms.WriteByte((byte)current);
                }
            }
    
            var xmlheader = new XmlDocument();
            xmlheader.LoadXml(Encoding.UTF8.GetString(ms.ToArray()));
    

2 个答案:

答案 0 :(得分:2)

虽然“读取结束标记”听起来很吸引人,但你需要一个解析器,它不会最终缓冲所有数据。

我会将所有数据读入byte[],然后在那里搜索分隔符 - 然后您可以将二进制数据拆分为两个,并适当地解析每个部分。我会完全使用二进制文件,不涉及任何字符串 - 您可以使用MemoryStream为每个部分创建new MemoryStrem(byte[], int, int),然后将其传递给XML解析器以及最终的部分解析器。这样您就不必担心处理UTF-8,或者检测更新版本的XML 是否使用UTF-8等。

类似于:

byte[] allData = File.ReadAllBytes(filename);
int separatorIndex = Array.IndexOf(allData, (byte) 29);
if (separatorIndex == -1)
{
    // throw an exception or whatever
}
var xmlStream = new MemoryStream(allData, 0, separatorIndex);
var lastPartStream = new MemoryStream(
      allData, separatorIndex + 1, allData.Length - separatorIndex - 1);

答案 1 :(得分:2)

根据您提供的信息,只需搜索值为29的字节就可以使用,因为XML是UTF8,只有当文件中存在29的字符代码点时才会出现值为29的字节。现在,我猜它可以存在,但是因为它在ASCII值的控制字符范围内会令人惊讶。

来自XML 1.0规范:

  

Char :: =#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] / *任何Unicode字符,不包括代理块,FFFE和FFFF。 * /

虽然评论意味着29将是XML文件中的有效代码点(因为它本身是一个有效的Unicode字符),我认为实际的语法规范。即除了制表符,换行符和回车符之外,它特别排除了代码点32下面的字符,因此29 是一个有效的XML字符(正如Jon Skeet所说)。

那就是说,如果没有完整的输入规范,我不能排除这种可能性。因此,如果您真的想要安全,那么您必须继续解析XML,希望为根元素找到合适的结束标记。然后,您可以搜索字节29(因为在结束标记之后可能有空格),以识别二进制数据的开始位置。

(注意:要求一个库是“偏离主题”。但你可以使用XmlReader来执行此操作,因为它在迭代的基础上运行;即你可以在命中后终止它的操作最后的结束标记,然后开始抱怨找到无效的XML。然而,这将取决于XmlReader可能做的缓冲;如果它通过结束标记缓冲额外的数据,那么底层流的位置会超过29字节,使其更难找到。坦率地说,只搜索29字节似乎是要走的路。)

您可以像这样搜索29个字节的标题(警告:浏览器代码...未编译,未经测试):

MemoryStream xmlStream = new MemoryStream();

using (FileStream stream = File.OpenRead(path))
{
    int offset = 0, bytesRead = 0;

    // arbitrary size...whatever you think is reasonable would be fine
    byte[] buffer = new byte[1024];

    while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
    {
        bool found = false;

        for (int i = 0; i < bytesRead; i++)
        {
            if (buffer[i] == 29)
            {
                offset += i;
                found = true;
                xmlStream.Write(buffer, 0, i - 1);
                break;
            }
        }

        if (found)
        {
            break;
        }

        offset += bytesRead;
        xmlStream.Write(buffer, 0, bytesRead);
    }

    if (bytesRead > 0)
    {
        // found byte 29 at offset "offset"

        xmlStream.Position = 0;

        // pass "xmlStream" object to your preferred XML-parsing API to
        // parse the XML, or just return it or "xmlStream.ToArray()" as
        // appropriate to the caller to let the caller deal with it.
    }
    else
    {
        // byte 29 not found!
    }
}

修改

我已经更新了上面的代码示例以写入MemoryStream对象,这样一旦找到了byte 29值,就会有一个流已准备好进行XML解析。当然,如果你真的需要,我相信你可以自己补充一下。在任何情况下,显然您可以修改代码,无论是否有该功能,都可以满足您的需求。

(在您搜索时写入MemoryStream时存在明显的危险:如果您没有找到字节29的值,您将在内存中找到整个文件的完整副本,您建议您可能更愿意避免。但鉴于这是错误情况,这可能没问题。)