【问题标题】:Strip the byte order mark from string in C#从 C# 中的字符串中去除字节顺序标记
【发布时间】:2022-02-21 09:41:47
【问题描述】:

在 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?

【问题讨论】:

    标签: c# string encoding


    【解决方案1】:

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

    String.Trim()

    在 .NET 3.5 之前删除 BOM。

    但是,在 .NET 4 中,您需要对其稍作更改:

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

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

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

    您也可以使用它来删除其他不需要的字符。

    更多信息来自 String.Trim Method

    .NET Framework 3.5 SP1 及更早版本维护此方法修剪的空白字符的内部列表。从 .NET Framework 4 开始,该方法会修剪所有 Unicode 空白字符(即,在将它们传递给 Char.IsWhiteSpace 方法时产生真正返回值的字符)。由于此更改,.NET Framework 3.5 SP1 及更早版本中的 Trim 方法删除了 .NET Framework 3.5 SP1 和更早版本中的 Trim 方法中的两个字符,零宽度空格 (U+200B) 和零宽度无间断空格 (U+FEFF)。 NET Framework 4 及更高版本不会删除。此外,.NET Framework 3.5 SP1 及更早版本中的 Trim 方法不会修剪三个 Unicode 空白字符:蒙古元音分隔符 (U+180E)、窄无间断空格 (U+202F) 和中等数学空格(U+205F)。

    【讨论】:

    • 抱歉,您的示例似乎不起作用。在 .NET 4 下尝试使用字符串“\x00EF\x00BB\x00BF”。
    • 没有完全理解我在使用标准 BOM 时遇到的问题,甚至没有认识到您必须处理的 \x00EF\x00BB\x00BF 疯狂问题
    • '\uFEFF' 不是 UTF16 的 BOM,而不是 UTF8?
    • 你知道,你就在那里,我从来没有遇到过 UTF8 BOM 的问题(这反映了问题所问的问题 - 这确实是 UTF8 的)UTF16 BOM 就是我当时遇到了麻烦。
    • @Cocowalla 对应的 bytes 在 big-endian UTF16 中是 FEFF,是的,但前导 character 在所有编码中都是相同的。
    【解决方案2】:

    我有一些不正确的测试数据,这让我有些困惑。基于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 仍然不会读取该字符串。这是迄今为止我想出的最干净的版本。

    【讨论】:

    • 似乎对我不起作用。甚至 "".StartsWith(_byteOrderMarkUtf8) 返回 true
    • @pingo 刚刚在 LINQPad 4 中尝试了您的代码,它返回 False。
    • 令人惊讶的是,StartsWith 方法的实现差异会在不同的操作系统上产生不同的结果。见stackoverflow.com/questions/19495318/…
    • @RamiA。所以我需要为StartsWith指定StringComparison.Ordinal
    • @TrueWill,是的。否则,例如在 Windows 7 与 Windows 8 或 Windows Server 2012 上运行时,结果会有所不同。
    【解决方案3】:

    这个也可以

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

    【讨论】:

    • 对我来说看起来很简单,解决了我的问题,我认为它也可以解决其他编码
    • 嗨,Vivek,您可以在有时间的时候访问一下 Tridion StackExchange 提案吗? area51.stackexchange.com/proposals/38335/tridion 我们认为承诺分数需要不时访问,因此不包括您在“具有 > 200 代表的用户”数字中。谢谢!
    • 这段代码值得放在框架里,WTF!我咨询时的典型......请宁愿使用@PJUK解决方案
    • 我在字符串的开头和结尾有一个不可见的废话字符,所以我必须执行此处提供的代码以及类似于字符串结尾的代码: int closingBracket = result.LastIndexOf ('>'); if (result.Length > closingBracket + 1) 结果 = result.Remove(closingBracket + 1);
    【解决方案4】:

    直接从字符串中删除它的快速简单的方法:

    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);
    

    【讨论】:

    • 就我而言,我需要剥离 UTF-16 BOM。在该方法中将“Encoding.UTF8”更改为“Encoding.Unicode”对我有用。
    • 这实际上与@TrueWill 的答案相同。
    • 不是@MatthewDresser。它更小、更简单、更干净。 :)
    【解决方案5】:

    如果变量 xml 是字符串类型,那么您已经做错了 - 在字符串中,BOM 不应表示为三个单独的字符,而应表示为单个代码点。

    不使用 DownloadString,而是使用 DownloadData,并解析字节数组。 XML 解析器应该识别 BOM 本身,并跳过它(除了自动检测文档编码为 UTF-8)。

    【讨论】:

    • XDocument.Parse 没有接受字节数组的重载。我发现“你做错了什么”这句话居高临下。我本来希望 DownloadString 能够检测 BOM 并选择正确的编码。
    • 我认为你也可以通过.Load获取XDocument,传递一个XmlReader,你可以通过传递一个Stream来获取它,你可以使用MemoryStream。我并不是要居高临下。我只是试图指出你得到的中间结果似乎是不正确的,所以真正的问题不是你必须去掉这些字符,而是它们首先存在。可能是 DownloadString 存在缺陷,在这种情况下您不应该使用它。可能缺陷在于 Web 服务器报告了错误的字符集。
    • 好的,谢谢。我确实发现我没有为 DownloadString 正确设置客户端编码,这给了我一个代码点(正如你提到的)。在这一点上有点没有实际意义,因为提供“REST”服务的公司决定删除多余的(用于 utf-8 中的 XML)BOM。
    • 好电话。使用 XDocument.Load 对我来说效果很好。不过,没有必要使用 XmlReader,因为 XDocument.Load 需要一个流作为参数。
    【解决方案6】:

    我有一个非常相似的问题(我需要解析一个表示为字节数组的 XML 文档,该数组的开头有一个字节顺序标记)。我在他的答案中使用了 Martin 的一个 cmets 来解决问题。我使用了我拥有的字节数组(而不是将其转换为字符串)并用它创建了一个MemoryStream 对象。然后我把它传递给XDocument.Load,它就像一个魅力。例如,假设xmlBytes 包含以UTF-8 编码的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);
    

    【讨论】:

    • 这对我很有用,但我必须添加一个中间 StreamReader
    • 即。 var doc = XDocument.Load(new StreamReader(new MemoryStream(batchfile)));
    • 我也是,Steven 的代码无法编译。没有采用 Stream 的 XDocument.Load() 重载。
    • 这里是XDocument.Load(Stream)重载的文档:msdn.microsoft.com/en-us/library/cc838349.aspx。我猜它特定于 .NET 4,所以您必须使用 .NET 3.5。在这种情况下,您将不得不使用不同的重载。
    【解决方案7】:

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

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

    【讨论】:

      【解决方案8】:

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

      用法:

                  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;
          }
      

      【讨论】:

      • 按预期工作。
      【解决方案9】:

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

      Martin's answer 更好,因为它避免为仍然需要解析的 XML 分配整个字符串。我给出的最佳答案适用于不需要解析为 XML 的一般字符串。

      【讨论】:

      • 感谢您的回复;不幸的是,这没有用。我使用了 DownloadData 并且有效;但是,Encoding.UTF8.GetString(byte[]) 没有剥离 BOM。我尝试了新的 UTF8Encoding(false) 和 (true) 的变体,但没有成功。请注意,这是 UTF-8 数据 - 在 XML 标头中指定了 encoding="utf-8",一旦删除 BOM,它就会正确解析。
      • 有趣。我打算将其标记下来,因为我一直在使用 UTF8Encoding.ASCII.GetString(bytes) 将 BOM 保留在其中,但 Encoding.UTF8.GetString(bytes) 将其删除。改为赞成
      • 在我的测试中,Encoding.UTF8.GetString(byte[] s)new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetString(byte[] s) 都不修剪 BOM。
      【解决方案10】:

      当我有一个Base64 编码文件要转换为字符串时,我遇到了这个问题。虽然我可以将其保存到文件中然后正确读取,但这是我能想到的从文件的byte[] 到字符串的最佳解决方案(基于TrueWill's answer):

      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;
      }
      

      【讨论】:

      • 我没有看到任何将这里的概念限制为 UTF-8 的东西。由于GetPreamble()属于Encoding,所以应该可以泛化以将Encoding作为参数。
      【解决方案11】:
      StreamReader sr = new StreamReader(strFile, true);
      XmlDocument xdoc = new XmlDocument();
      xdoc.Load(sr);
      

      【讨论】:

      • 这如何解决问题?你能扩展它吗?
      • StreamReader() 将处理 BOM。
      【解决方案12】:

      另一个摆脱 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);
      

      【讨论】:

        【解决方案13】:

        使用正则表达式替换过滤掉除正常证书指纹值中包含的字母数字字符和空格之外的任何其他字符:

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

        然后就可以了。瞧!!它对我有用。

        【讨论】:

          【解决方案14】:

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

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

          【讨论】:

            猜你喜欢
            • 2010-11-07
            • 2015-01-01
            • 2013-08-10
            • 2010-09-22
            • 1970-01-01
            • 1970-01-01
            • 2012-06-06
            • 1970-01-01
            • 2010-10-21
            相关资源
            最近更新 更多