从C#中的字符串剥离字节顺序标记

时间:2009-08-23 03:39:37

标签: c# string encoding

我已经阅读了类似的帖子,他们没有回答我的问题。

在C#中,我有一个我从WebClient.DownloadString获取的字符串。我已经尝试将client.Encoding设置为新的UTF8Encoding(false),但这没有任何区别 - 我仍然在结果字符串的开头以UTF-8的字节顺序标记结束。我需要删除它(用LINQ解析生成的XML),并希望在内存中执行此操作。

所以我有一个以\ x00EF \ x00BB \ x00BF开头的字符串,如果它存在,我想删除它。现在我正在使用

if (xml.StartsWith(ByteOrderMarkUtf8))
{
    xml = xml.Remove(0, ByteOrderMarkUtf8.Length);
}

但这感觉不对。我已尝试过各种带有流,GetBytes和编码的代码,但没有任何作用。任何人都可以提供“正确”的算法来从字符串中剥离BOM吗?

谢谢!

14 个答案:

答案 0 :(得分:45)

我最近遇到了.net 4升级的问题,但在此之前,简单的答案是

String.Trim()

删除BOM直到.net 3.5 但是在.net 4中你需要稍微改变它

String.Trim(new char[]{'\uFEFF'});

这也将摆脱字节顺序标记,尽管您可能还想删除ZERO WIDTH SPACE U + 200B

String.Trim(new char[]{'\uFEFF','\u200B'});

这也可以用来删除其他不需要的字符

来自的一些进一步信息 http://msdn.microsoft.com/en-us/library/t97s7bs3.aspx

  

.NET Framework 3.5 SP1及更早版本维护此方法修剪的内部空白字符列表。从.NET Framework 4开始,该方法修剪所有Unicode空格字符(即,在传递给Char.IsWhiteSpace方法时生成真实返回值的字符)。由于此更改,.NET Framework 3.5 SP1和更早版本中的Trim方法删除了两个字符,ZERO WIDTH SPACE(U + 200B)和ZERO WIDTH NO-BREAK SPACE(U + FEFF),即Trim方法。 NET Framework 4及更高版本不会删除。此外,.NET Framework 3.5 SP1和更早版本中的Trim方法不会修剪三个Unicode空白字符:MONGOLIAN VOWEL SEPARATOR(U + 180E),NARROW NO-BREAK SPACE(U + 202F)和MEDIUM MATHEMATICAL SPACE (U + 205F)。

答案 1 :(得分:41)

我有一些不正确的测试数据,这让我有些困惑。基于How to avoid tripping over UTF-8 BOM when reading files,我发现这有效:

private readonly string _byteOrderMarkUtf8 =
    Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());

public string GetXmlResponse(Uri resource)
{
    string xml;

    using (var client = new WebClient())
    {
        client.Encoding = Encoding.UTF8;
        xml = client.DownloadString(resource);
    }

    if (xml.StartsWith(_byteOrderMarkUtf8, StringComparison.Ordinal))
    {
        xml = xml.Remove(0, _byteOrderMarkUtf8.Length);
    }

    return xml;
}

正确设置客户端编码属性会将BOM缩减为单个字符。但是,XDocument.Parse仍然不会读取该字符串。这是我迄今为止提出的最干净的版本。

答案 2 :(得分:29)

这也适用

int index = xmlResponse.IndexOf('<');
if (index > 0)
{
    xmlResponse = xmlResponse.Substring(index, xmlResponse.Length - index);
}

答案 3 :(得分:19)

如果变量xml是string类型,那么你已经做错了 - 在字符串中,BOM不应该表示为三个单独的字符,而应该表示为单个代码点。而不是使用DownloadString,使用DownloadData,而是解析字节数组。 XML解析器应该识别BOM本身,并跳过它(除了自动检测文档编码为UTF-8)。

答案 4 :(得分:12)

一种快速简单的方法,可以直接从字符串中删除它:

private static string RemoveBom(string p)
{
     string BOMMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
     if (p.StartsWith(BOMMarkUtf8))
         p = p.Remove(0, BOMMarkUtf8.Length);
     return p.Replace("\0", "");
}

使用方法:

string yourCleanString=RemoveBom(yourBOMString);

答案 5 :(得分:10)

我有一个非常类似的问题(我需要解析一个XML文档,表示为一个字节数组,在其开头有一个字节顺序标记)。我用马丁的一个评论来回答他的答案。我接受了我拥有的字节数组(而不是将其转换为字符串)并使用它创建了一个MemoryStream对象。然后我将它传递给XDocument.Load,它就像一个魅力。例如,假设xmlBytes包含UTF8编码的XML,并在其开头有一个字节标记。然后,这将是解决问题的代码:

var stream = new MemoryStream(xmlBytes);
var document = XDocument.Load(stream);

就这么简单。

如果以字符串开头,它应该仍然很容易(假设xml是包含带字节顺序标记的XML的字符串):

var bytes = Encoding.UTF8.GetBytes(xml);
var stream = new MemoryStream(bytes);
var document = XDocument.Load(stream);

答案 6 :(得分:8)

我在遇到这个问题后写了following post

基本上不是使用BinaryReader类读取文件内容的原始字节,而是使用具有特定构造函数的StreamReader类,该构造函数会自动从我试图检索的文本数据中删除字节顺序标记字符。

答案 7 :(得分:5)

将字节缓冲区(通过DownloadData)传递给string Encoding.UTF8.GetString(byte[])以获取字符串,而不是将缓冲区AS下载为字符串。当前方法可能比修剪字节顺序标记有更多问题。除非你按照我的建议正确解码它,否则unicode字符可能会被误解,导致字符串损坏。

编辑:Martin的答案更好,因为它避免为仍然需要解析的XML分配整个字符串。我给出的答案最适用于不需要解析为XML的通用字符串。

答案 8 :(得分:3)

当我有一个base-64编码文件转换成字符串时,我碰到了这个。虽然我本可以将其保存到文件然后正确读取,但这是我能想到的从文件的byte[]到字符串的最佳解决方案(基于TrueWill的答案):

public static string GetUTF8String(byte[] data)
{
    byte[] utf8Preamble = Encoding.UTF8.GetPreamble();
    if (data.StartsWith(utf8Preamble))
    {
        return Encoding.UTF8.GetString(data, utf8Preamble.Length, data.Length - utf8Preamble.Length);
    }
    else
    {
        return Encoding.UTF8.GetString(data);
    }
}

StartsWith(byte[])是逻辑扩展名:

public static bool StartsWith(this byte[] thisArray, byte[] otherArray)
{
   // Handle invalid/unexpected input
   // (nulls, thisArray.Length < otherArray.Length, etc.)

   for (int i = 0; i < otherArray.Length; ++i)
   {
       if (thisArray[i] != otherArray[i])
       {
           return false;
       }
   }

   return true;
}

答案 9 :(得分:2)

StreamReader sr = new StreamReader(strFile, true);
XmlDocument xdoc = new XmlDocument();
xdoc.Load(sr);

答案 10 :(得分:2)

当然最好是在仍处于字节数组级别的情况下将其剥离,以避免不必要的子字符串/分配。但是,如果您已经有了一个字符串,这也许是处理此字符串的最简单,最高效的方法。

用法:

            string feed = ""; // input
            bool hadBOM = FixBOMIfNeeded(ref feed);

            var xElem = XElement.Parse(feed); // now does not fail

    /// <summary>
    /// You can get this or test it originally with: Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble())[0];
    /// But no need, this way we have a constant. As these three bytes `[239, 187, 191]` (a BOM) evaluate to a single C# char.
    /// </summary>
    public const char BOMChar = (char)65279;

    public static bool FixBOMIfNeeded(ref string str)
    {
        if (string.IsNullOrEmpty(str))
            return false;

        bool hasBom = str[0] == BOMChar;
        if (hasBom)
            str = str.Substring(1);

        return hasBom;
    }

答案 11 :(得分:0)

还有另一种通用变体来摆脱UTF-8 BOM序言:

var preamble = Encoding.UTF8.GetPreamble();
if (!functionBytes.Take(preamble.Length).SequenceEqual(preamble))
    preamble = Array.Empty<Byte>();
return Encoding.UTF8.GetString(functionBytes, preamble.Length, functionBytes.Length - preamble.Length);

答案 12 :(得分:0)

我用以下代码解决了这个问题

using System.Xml.Linq;

void method()
{
    byte[] bytes = GetXmlBytes();
    XDocument doc;
    using (var stream = new MemoryStream(docBytes))
    {
        doc = XDocument.Load(stream);
    }
 }

答案 13 :(得分:0)

使用正则表达式替换来过滤普通证书指纹值中包含的字母数字字符和空格以外的其他任何字符

certficateThumbprint = Regex.Replace(certficateThumbprint, @"[^a-zA-Z0-9\-\s*]", "");

在那里,瞧!它对我有用,可能也应该对您有用! :)