如何在 C# 中从混合 xml/binary 文件的 header 中读取 XML 数据
How to read XML data from the header of a mixed xml/binary file in C#
我的任务是为具有以下规范的文件格式编写 reader:
- 第一部分是普通的 xml,带有元数据 (utf-8);
- 最后一部分是 16 位值流(二进制);
- 这两个部分由一个字节分隔,值为
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 的值,您将得到整个文件的完整副本内存,你建议你可能更愿意避免。但考虑到这是错误的情况,那可能没问题。
我的任务是为具有以下规范的文件格式编写 reader:
- 第一部分是普通的 xml,带有元数据 (utf-8);
- 最后一部分是 16 位值流(二进制);
- 这两个部分由一个字节分隔,值为
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 的值,您将得到整个文件的完整副本内存,你建议你可能更愿意避免。但考虑到这是错误的情况,那可能没问题。