如何从字节数组中移除BOM

9
我有一个包含BOM的xml数据,存储在byte[] byteArray中。在C#中,是否有标准的方法可以去除BOM?如果没有,那么什么是最好的方法,可以处理所有类型的编码,包括所有情况?
实际上,我正在修复代码中的一个错误,我不想改变太多的代码。所以,如果有人可以给我删除BOM的代码,那就更好了。
我知道可以像查找ASCII值为'<60>'的字符并忽略它之前的字节,但我不想这样做。

数据可以是UTF-8(带或不带字节顺序标记)或UTF16(带或不带BOM;小端或大端)吗? - Jeppe Stig Nielsen
我已经编辑了你的标题。请参考“问题的标题应该包含“标签”吗?”,在那里达成共识是“不应该”。 - John Saunders
5个回答

10

所有的C# XML解析器会自动为您处理BOM。我建议使用XDocument - 在我看来,它提供了最清晰的XML数据抽象。

以XDocument为例:

using (var stream = new memoryStream(bytes))
{
  var document = XDocument.Load(stream);
  ...
}

一旦您拥有了一个XDocument,您就可以使用它来省略带BOM的字节:

using (var stream = new MemoryStream())
using (var writer = XmlWriter.Create(stream))
{
  writer.Settings.Encoding = new UTF8Encoding(false);
  document.WriteTo(writer);
  var bytesWithoutBOM = stream.ToArray();
}

实际上我只想删除BOM,不必担心解析等问题。我已经更新了问题。 - Ravi Gupta
@RaviGupta 我明白了,你知道编码方式吗? - Rich O'Kelly
最好的情况是逻辑不受编码限制。 - Ravi Gupta
@RaviGupta 答案已更新。也许可以通过查看 XmlReader 的内部来寻找它们如何检测 BOM 的更有效的方法,但是我上面写的应该可以正常工作。 - Rich O'Kelly
我们能否对所有编码都这样做呢?比如说,可以这样写:writer.Settings.Encoding = new Encoding ....,而不是像这样写:writer.Settings.Encoding = new UTF8Encoding(false); - Ravi Gupta
@RaviGupta 上述代码将“规范化”编码为UTF8。在写出字节时必须指定编码,您可以选择其他编码,但UTF8被任意选择。 - Rich O'Kelly

3

您不必担心BOM问题。

如果出于某种原因需要使用XmlDocument对象,也许以下代码可以帮到您:

byte[] file_content = {wherever you get it};
XmlDocument xml = new XmlDocument();
xml.Load(new MemoryStream(file_content));

当我尝试使用Google Api从Gmail帐户下载一个包含BOM的xml附件时,对我有效。但是,使用Encoding.UTF8.GetString(file_content)不能正常工作。

最初的回答:


2
您可以这样做来跳过从流中读取时的BOM字节。但是,您需要扩展Bom.cs以包括更多编码,不过据我所知,UTF是唯一使用BOM的编码...虽然可能会有错。
我从这里获得了有关编码类型的信息。
using (var stream = File.OpenRead("path_to_file"))
{
    stream.Position = Bom.GetCursor(stream);
}


public static class Bom
{
        public static int GetCursor(Stream stream)
        {
            // UTF-32, big-endian
            if (IsMatch(stream, new byte[] {0x00, 0x00, 0xFE, 0xFF}))
                return 4;
            // UTF-32, little-endian
            if (IsMatch(stream, new byte[] { 0xFF, 0xFE, 0x00, 0x00 }))
                return 4;
            // UTF-16, big-endian
            if (IsMatch(stream, new byte[] { 0xFE, 0xFF }))
                return 2;
            // UTF-16, little-endian
            if (IsMatch(stream, new byte[] { 0xFF, 0xFE }))
                return 2;
            // UTF-8
            if (IsMatch(stream, new byte[] { 0xEF, 0xBB, 0xBF }))
                return 3;
            return 0;
        }

        private static bool IsMatch(Stream stream, byte[] match)
        {
            stream.Position = 0;
            var buffer = new byte[match.Length];
            stream.Read(buffer, 0, buffer.Length);
            return !buffer.Where((t, i) => t != match[i]).Any();
        }
    }

1

你还可以使用StreamReader。

假设你有一个MemoryStream ms

    using (StreamReader sr = new StreamReader(new MemoryStream(ms.ToArray()), Encoding.UTF8))
    {
         var bytesWithoutBOM = new UTF8Encoding(false).GetBytes(sr.ReadToEnd());
         var stringWithoutBOM = Convert.ToBase64String(bytesWithoutBOM );
    }

0

你需要在字节数组的开头识别字节顺序标记。有几种不同的组合,如http://www.unicode.org/faq/utf_bom.html#bom1所述。

只需创建一个小状态机,从字节数组的开头开始查找这些序列。

我不知道你的数组如何使用或者你使用它的其他参数是什么,所以我无法确定如何“删除”该序列。你的选择似乎是:

  1. 如果您有startcount参数,您只需更改它们以反映数组(超出BOM)的起始点。
  2. 如果您只有一个count参数(除了数组的Length属性),则可以移动数组中的数据以覆盖BOM,并相应地调整count
  3. 如果您没有startcount参数,则需要创建一个新数组,其大小为旧数组减去BOM,并将数据复制到新数组中。

要“删除”序列,您可能需要识别标记(如果存在),然后将剩余字节复制到新的字节数组中。或者,如果您维护字符计数(除了数组的Length属性)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接