我的任务是为具有以下规范的文件格式编写阅读器:
29
的一个字节分隔(ASCII表中的组分隔符)。我看到两种方法来读取文件的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()));
答案 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的值,您将在内存中找到整个文件的完整副本,您建议您可能更愿意避免。但鉴于这是错误情况,这可能没问题。)