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

Tru*_*ill 42 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);
}
Run Code Online (Sandbox Code Playgroud)

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

谢谢!

PJU*_*JUK 46

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

String.Trim()

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

String.Trim(new char[]{'\uFEFF'});
Run Code Online (Sandbox Code Playgroud)

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

String.Trim(new char[]{'\uFEFF','\u200B'});
Run Code Online (Sandbox Code Playgroud)

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

来自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).

  • 不是''\ uFEFF'`是UTF16的BOM,而不是UTF8? (2认同)

Tru*_*ill 44

我有一些不正确的测试数据,这让我有些困惑.基于如何避免在读取文件时跳过UTF-8 BOM我发现这有效:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这是唯一对我有用的方法。我使用 string.Replace() 来替换 BOM。谢谢 (3认同)
  • 似乎对我不起作用.甚至"".StartsWith(_byteOrderMarkUtf8)返回true (2认同)
  • 令人惊讶的是,StartsWith 方法存在实现差异,在不同的操作系统上会产生不同的结果。请参阅http://stackoverflow.com/questions/19495318/startswith-change-in-windows-server-2012 (2认同)
  • @TrueWill,是的.否则,在Windows 7与Windows 8或Windows Server 2012上运行时,结果会有所不同. (2认同)

Viv*_*yer 31

这也有效

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

  • 这个代码值得放在框架中,WTF!从我的咨询日来看是典型的...请使用@PJUK解决方案 (3认同)

Mar*_*wis 19

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

  • XDocument.Parse没有接受字节数组的重载.我发现声明"你做错了什么"居高临下.我原以为DownloadString可以检测BOM并选择正确的编码. (9认同)
  • 我认为你也可以通过.Load获取XDocument,传递一个XmlReader,你可以通过传递一个Stream来获得,你可以使用一个MemoryStream.我不是故意屈尊俯就; 我只是想指出你得到的中间结果看似不正确,所以真正的问题不是你必须剥离这些字符,而是它们首先存在.也许是因为DownloadString存在缺陷,在这种情况下你不应该使用它.也许这个缺陷是在Web服务器上报告错误的字符集. (4认同)

Tia*_*vêa 13

从字符串中删除directyl的快速简单方法:

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", "");
}
Run Code Online (Sandbox Code Playgroud)

如何使用:

string yourCleanString=RemoveBom(yourBOMString);
Run Code Online (Sandbox Code Playgroud)


Ste*_*ley 10

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

var stream = new MemoryStream(xmlBytes);
var document = XDocument.Load(stream);
Run Code Online (Sandbox Code Playgroud)

就这么简单.

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

var bytes = Encoding.UTF8.GetBytes(xml);
var stream = new MemoryStream(bytes);
var document = XDocument.Load(stream);
Run Code Online (Sandbox Code Playgroud)

  • 这对我很有用,但我不得不添加一个中间StreamReader (2认同)
  • 这是`XDocument.Load(Stream)`重载的文档:http://msdn.microsoft.com/en-us/library/cc838349.aspx.我想这是.NET 4特有的,所以你必须使用.NET 3.5.在这种情况下,您将不得不使用不同的重载. (2认同)

And*_*son 8

遇到这个问题后,我写了以下帖子.

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


And*_*ott 5

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

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


Nic*_*sen 5

当然,最好还是在字节数组级别时将其删除,以避免不需要的子字符串/分配。但是如果你已经有了一个字符串,这可能是处理这个问题的最简单、最高效的方法。

用法:

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

            var xElem = XElement.Parse(feed); // now does not fail
Run Code Online (Sandbox Code Playgroud)
    /// <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;
    }
Run Code Online (Sandbox Code Playgroud)