如何在 C# 中从混合 xml/binary 文件的 header 中读取 XML 数据

How to read XML data from the header of a mixed xml/binary file in C#

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

  1. 第一部分是普通的 xml,带有元数据 (utf-8);
  2. 最后一部分是 16 位值流(二进制);
  3. 这两个部分由一个字节分隔,值为 29(ASCII 中的组分隔符 table)。

我看到了两种读取文件 xml 部分的方法。 第一个是逐字节构建字符串,直到找到分隔符。

另一种是使用一些库来解析 xml 并自动检测 well-formed xml.

的结尾

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

(或者,谁能建议一种更明智的方式来读取这种文件格式?)


更新:根据 Peter Duniho 的回答,稍作修改,我最终得到了这个(它有效,但还不完全unit-tested)。

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

虽然 "read to the closing tag" 听起来很吸引人,但您需要有一个最终不会缓冲所有数据的解析器。

我会将所有数据读入 byte[],然后在那里搜索分隔符 - 然后您可以将二进制数据分成两部分,并适当地解析每个部分。我将完全以二进制方式执行此操作,不涉及任何字符串 - 您可以使用 new MemoryStrem(byte[], int, int) 为每个部分创建一个 MemoryStream,然后将其传递给 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);

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

来自 XML 1.0 规范:

Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */

虽然评论暗示 29 将是 XML 文件中的有效代码点(因为它本身是有效的 Unicode 字符),但我认为实际语法规范。 IE。它特别排除了代码点 32 以下的字符,制表符、换行符和回车 return 除外,因此 29 不是 有效的 XML 字符(正如 Jon Skeet 所说)。

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

(注意:请求库是 "off-topic"。但是您可以使用 XmlReader 来执行此操作,因为它在迭代的基础上运行;即您可以在之后终止其操作你点击了最后一个结束标记,然后它开始抱怨发现无效 XML。然而,这取决于 XmlReader 可能做的缓冲;如果它缓冲结束标记之后的额外数据,那么底层流的位置将超过 29 字节,使其更难找到。坦率地说,只搜索 29 字节似乎是可行的方法)。

您可以像这样在 header 中搜索 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 object,这样一旦找到字节 29 的值,就可以准备好流了用于 XML 解析。当然,我相信如果确实需要,您可以自己添加。在任何情况下,显然您都可以修改代码,无论是否具有该功能,以满足您的需要。

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