C#如何从字节数组中检测XML编码?

6

我有一个字节数组,我知道它是一个XML序列化对象,那么有没有办法从中获取编码?

我不打算反序列化它,但我需要将它保存在SQL服务器上的XML字段中...所以我需要将它转换为字符串吗?


有没有完整的源代码示例,可以作为最终解决方案? - Kiquenet
4个回答

14

类似于这个问题的解决方案可以通过使用字节数组上的流来解决。然后您就不必在字节级别上进行操作。就像这样:

Encoding encoding;
using (var stream = new MemoryStream(bytes))
{
    using (var xmlreader = new XmlTextReader(stream))
    {
        xmlreader.MoveToContent();
        encoding = xmlreader.Encoding;
    }
}

8

W3C XML规范中有一节关于如何确定字节串的编码。

首先检查Unicode字节顺序标记

BOM只是另一个字符;它是:

'零宽不换行空格' (U+FEFF)

例如:

  • NWNBSP<?xml vers
  • "\ufeff<xml vers"
  • "\ufeff\u003c\u003f\u0078\u006d\u006c\u0020\u0076\u0065\u0072\u0073"
  • U+FEFFU+003CU+003FU+0078U+006DU+006CU+0020U+0076U+0065U+0072U+0073

字符U+FEFF和文件中的其他字符一样,使用合适的编码方案进行编码:

  • 00 00 FE FF: UCS-4,大端机器(1234顺序)
  • FF FE 00 00: UCS-4,小端机器(4321顺序)
  • 00 00 FF FE: UCS-4,不寻常的八位顺序(2143)
  • FE FF 00 00: UCS-4,不寻常的八位顺序(3412)
  • FE FF ## ##: UTF-16,大端
  • FF FE ## ##: UTF-16,小端
  • EF BB BF: UTF-8

其中## ##可以是任何内容 - 除了两个都是零

  • U+FEFFU+003CU+003FU+0078U+006DU+006CU+0020U+0076U+0065U+0072U+0073
  • ff fe3c 003f 0078 006d 006c 0020 0076 0065 0072 0073 00
  • ff fe 3c 00 3f 00 78 00 6d 00 6c 00 20 00 76 00 65 00 72 00 73 00
这是一组 Unicode 字符编码,表示为十六进制格式。

首先检查初始字节是否包含这些标识之一。如果找到其中一个,请返回代码页标识符

UInt32 GuessEncoding(byte[] XmlString)
{
   if BytesEqual(XmlString, [00, 00, $fe, $ff]) return 12001; //"utf-32BE" - Unicode UTF-32, big endian byte order
   if BytesEqual(XmlString, [$ff, $fe, 00, 00]) return 1200;  //"utf-32" - Unicode UTF-32, little endian byte order
   if BytesEqual(XmlString, [00, 00, $ff, $fe]) throw new Exception("Nobody supports 2143 UCS-4");
   if BytesEqual(XmlString, [$fe, $ff, 00, 00]) throw new Exception("Nobody supports 3412 UCS-4");
   if BytesEqual(XmlString, [$fe, $ff])
   {
      if (XmlString[2] <> 0) && (XmlString[3] <> 0)
         return 1201;  //"unicodeFFFE" - Unicode UTF-16, big endian byte order
   }
   if BytesEqual(XmlString, [$ff, $fe])
   {
      if (XmlString[2] <> 0) && (XmlString[3] <> 0)
         return 1200;  //"utf-16" - Unicode UTF-16, little endian byte order
   }
   if BytesEqual(XmlString, [$ef, $bb, $bf])    return 65001; //"utf-8" - Unicode (UTF-8)

否则查找<?xml

如果XML文档没有字节顺序标记字符,那么您需要查找每个XML文档必须具有的前五个字符:

<?xml

知道以下内容会很有帮助:

  • < 是 #x0000003C
  • ? 是 #x0000003F

有了这些信息,我们就可以查看前四个字节:

  • 00 00 00 3C: UCS-4,大端机器(1234顺序)
  • 3C 00 00 00: UCS-4,小端机器(4321顺序)
  • 00 00 3C 00: UCS-4,不寻常的八位顺序(2143)
  • 00 3C 00 00: UCS-4,不寻常的八位顺序(3412)
  • 00 3C 00 3F: UTF-16,大端
  • 3C 00 3F 00: UTF-16,小端
  • 3C 3F 78 6D: UTF-8
  • 4C 6F A7 94: EBCDIC的某种风味

那么我们可以继续添加更多的代码:

   if BytesEqual(XmlString, [00, 00, 00, $3C])    return 12001; //"utf-32BE" - Unicode UTF-32, big endian byte order
   if BytesEqual(XmlString, [$3C, 00, 00, 00])    return 1200;  //"utf-32" - Unicode UTF-32, little endian byte order
   if BytesEqual(XmlString, [00, 00, $3C, 00])    throw new Exception("Nobody supports 2143 UCS-4");
   if BytesEqual(XmlString, [00, $3C, 00, 00])    throw new Exception("Nobody supports 3412 UCS-4");
   if BytesEqual(XmlString, [00, $3C, 00, $3F])   return return 1201;  //"unicodeFFFE" - Unicode UTF-16, big endian byte order
   if BytesEqual(XmlString, [$3C, 00, $3F, 00])   return 1200;  //"utf-16" - Unicode UTF-16, little endian byte order
   if BytesEqual(XmlString, [$3C, $3F, $78, $6D]) return 65001; //"utf-8" - Unicode (UTF-8)
   if BytesEqual(XmlString, [$4C, $6F, $A7, $94])
   {
      //Some variant of EBCDIC, e.g.:
      //20273   IBM273  IBM EBCDIC Germany
      //20277   IBM277  IBM EBCDIC Denmark-Norway
      //20278   IBM278  IBM EBCDIC Finland-Sweden
      //20280   IBM280  IBM EBCDIC Italy
      //20284   IBM284  IBM EBCDIC Latin America-Spain
      //20285   IBM285  IBM EBCDIC United Kingdom
      //20290   IBM290  IBM EBCDIC Japanese Katakana Extended
      //20297   IBM297  IBM EBCDIC France
      //20420   IBM420  IBM EBCDIC Arabic
      //20423   IBM423  IBM EBCDIC Greek
      //20424   IBM424  IBM EBCDIC Hebrew
      //20833   x-EBCDIC-KoreanExtended IBM EBCDIC Korean Extended
      //20838   IBM-Thai    IBM EBCDIC Thai
      //20866   koi8-r  Russian (KOI8-R); Cyrillic (KOI8-R)
      //20871   IBM871  IBM EBCDIC Icelandic
      //20880   IBM880  IBM EBCDIC Cyrillic Russian
      //20905   IBM905  IBM EBCDIC Turkish
      //20924   IBM00924    IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
      throw new Exception("We don't support EBCDIC. Sorry");
   }

   //Otherwise assume UTF-8, and fail to decode it anyway
   return 65001; //"utf-8" - Unicode (UTF-8)

   //Any code is in the public domain. No attribution required.
}

7
你可以查看前40个字节左右1。它们应该包含文档声明(假设有文档声明),其中应该包含编码,或者您可以假定它是UTF-8或UTF-16,这应该可以从您理解的<?xml部分明显地看出来(只需检查两种模式即可)。
现实情况下,你认为除了UTF-8或UTF-16之外还会得到其他编码吗?如果不会,你可以检查这两种编码开头的模式,并在不遵循任何一种模式时引发异常。或者,如果您想再尝试一次,您可以尝试将文档解码为UTF-8,重新编码并查看是否获得相同的字节。这不是最理想的方法,但可能有效。
我相信还有更严格的方法来处理这个问题,但它们可能会很棘手:)

1 可能少于这个数字。我觉得20个字符应该足够了,在UTF-16中是40个字节。


1
给那些要点踩的人:如果你要点踩,请留下评论。否则,点踩就没有任何实际意义。 - Jon Skeet
1
有没有完整的源代码示例,可以作为最终解决方案? - Kiquenet
@Kiquenet:很抱歉,这不是我做的。我现在没有时间回来处理这个问题。 - Jon Skeet

7

前2或3个字节可能是字节顺序标记(BOM),可以告诉您流是UTF-8、Unicode-LittleEndian还是Unicode-BigEndian。

UTF-8 BOM为0xEF 0xBB 0xBF Unicode-Bigendian为0xFE 0xFF Unicode-LittleEndiaon为0xFF 0xFE

如果没有这些内容,则可以使用ASCII测试 <?xml (注意,大多数现代XML生成都遵循标准,即不允许空格出现在xml声明之前)。

ASCII一直使用到?>,因此您可以找到编码的存在并查找其值。如果不存在编码或<?xml声明不存在,则可以假定为UTF-8。


1
有没有完整的源代码示例,可以使最终解决方案正常工作? - Kiquenet

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