开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来

DotNetNuke作为开源项目,很多地方为我们提供了优良的示范,得以一窥前人的智慧。前几日,因为研究一个DNN的BUG,对文件编码和文件编码相关方面的处理有一些认识。

我们经常需要把一个Text文件(如XML,SQL Script)上传到服务器,然后进行处理(如显示或者执行),这里就涉及到文本文件编码的问题了。


什么是文件编码

首先我们来复习一下编码的基本概念,由于历史原因,Text文件存在ASCII,Unicode,UTF-8,UTF-7等等编码方式;对于中文,还有GB2312;对于Unicode还有Unicode-16,Unicode-32;对于Unicode-16又分为Unicode-16 Little Endian,Unicode-16 Big Endian。要把所有的编码方式列举出来是相当的复杂。想仔细的研究一下各种编码的规则和由来可以参考一下这篇文章:编码,charset,乱码,unicode,utf-8与net简单释义。我们读取一个文本文件时,总是使用某一种编码方式去解码这个文本文件,如果我们使用的解码方式和文本文件本身的编码方式不一致,最后的结果就是得到一个乱码的文件。


我可以不用关心这个麻烦的文件编码吗

大致了解了什么是文件编码,我们来看看在DNN里为什么要和文件编码打交道,这么麻烦,我们不能绕开它吗?

在DNN里,人们可以制作和上传皮肤,模块,语言包的。就拿模块包说吧,模块包里包含各种文本文件,比如定义模块的.dnn文件,数据库的SQL 脚本文件等等。因为DNN是一个开源软件,世界上任何一个地方的人群都可能使用它,所以这些文本文件可能以各种编码格式存储,你无法强制别人只用某一种格式来储存,我们只能侦测每一个遇到文本文件的编码方式,并做对应的解码。

这里要强调的一点是:对于DNN,对文本文件的编码方式做了一些限制,那就是一定要使用带有BOM的Unicode格式,其它格式都一律按不支持处理。所以DNN的代码并不是一个彻底的解决方案,但事情总是取一个平衡,为20%的应用在多做80%的工作,有时候是没必要的。


如何解决文件编码转换的问题

回到我们的问题,对于一个上传到服务器的Text文件,我们要解决的问题就是:“如何得知这个文件的编码方式,并用正确的方式解码,得到 文本文件中的内容。”


如何得知这个文件的编码方式

首先我们来看看如何得知文本文件的编码方式,为了简化问题,我们只讨论Unicode编码这种形式(实际上DNN里也只针对Unicode做了处理),对于其它各种编码的判别方式我们不做讨论。


BOM

这里涉及到一个BOM(Byte Order Mark) 的概念。简单的讲,在Unicode标准中,为了标示文本文件的编码类型,可以在文本文件的开始插入几个特殊的byte,通过这几个特殊的byte,应用程序就可以鉴别文本文件使用的是那种编码了。那几个特殊的byte也被称之为BOM(参考:http://unicode.org/faq/utf_bom.html )。

对于Unicode,几种编码的BOM如下:

  • UTF-32, big-endian 文件的前4个byte是:00 00 FE FF
  • UTF-32, little-endian文件的前4个byte是:FF FE 00 00
  • UTF-16, big-endian文件的前2个byte是:FE FF
  • UTF-16, little-endian文件的前2个byte是:FF FE
  • UTF-8文件的前3个byte是:EF BB BF
  • UTF-7的规律特殊一点,不是前几个byte,而是所有的byte转换为十进制都小于127。


判定文件编码方式

知道了这一点,你也应该能想到如何判定一个文本文件的编码方式了吧。读取文件的前面几个字节,跟上面的表对比,就可以知道这个文件使用的哪一种编码了。

看看DNN的代码:这个函数在DotNetNuke.Modules.Admin.ResourceInstaller命名空间下的PaFile类里。


开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来GetTextEncodingType
 1开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来        Private Function GetTextEncodingType()Function GetTextEncodingType(ByVal Buffer As Byte()) As PaTextEncoding
 2开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'UTF7 = No byte higher than 127
 3开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'UTF8 = first three bytes EF BB BF
 4开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'UTF16BigEndian = first two bytes FE FF
 5开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'UTF16LittleEndian = first two bytes FF FE
 6开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
 7开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'Lets do the easy ones first
 8开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            If Buffer(0= 255 And Buffer(1= 254 Then
 9开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                Return PaTextEncoding.UTF16LittleEndian
10开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            End If
11开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            If Buffer(0= 254 And Buffer(1= 255 Then
12开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                Return PaTextEncoding.UTF16BigEndian
13开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            End If
14开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            If Buffer(0= 239 And Buffer(1= 187 And Buffer(2= 191 Then
15开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                Return PaTextEncoding.UTF8
16开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            End If
17开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
18开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'This does a simple test to verify that there are no bytes with a value larger than 127
19开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'which would be invalid in UTF-7 encoding
20开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            Dim i As Integer
21开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            For i = 0 To 100
22开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                If Buffer(i) > 127 Then
23开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    Return PaTextEncoding.Unknown
24开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                End If
25开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            Next
26开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            Return PaTextEncoding.UTF7
27开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
28开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来        End Function

29开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来

代码很好懂,PaTextEncoding是一个枚举类型,枚举各种编码格式。唯一要注意的就是对于UTF-7编码,采用了一种比较简单的判定方式——只检查了前101个byte是否小于127。


System.Text

知道了编码方式,接下来的工作就是解码了。这里我们要用到.Net的System.Text命名空间下的一些类。

  • Encoding
  • UnicodeEncoding
  • ASCIIEncoding
  • UTF32Encoding
  • UTF8Encoding
  • UTF7Encoding
  • 等等

Encoding是基类,UnicodeEncoding、ASCIIEncoding、 UTF32Encoding、UTF8Encoding、UTF7Encoding等类继承自Encoding类,专门用来处理各种编码。

  • 使用Encoding.Convert (Encoding, Encoding, Byte[])方法,可以把字节数组从一种编码的转换为另一种编码
  • 使用GetString(Byte[])方法,比如UTF8Encoding.GetString(Byte[])就可以把UTF8编码得到字节数组还原成一个String.

复习了Sytem.Text下关于编码转换的一些类,回到我们的问题,你也许已经在想,判断完文件编码的类型后,只需要调用相应的GetString()函数就可以解码了。如下:


开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来Code
 1开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            PaTextEncoding EncodingType = GetTextEncodingType(Buffer);
 2开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
 3开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            string DecodedString = "";
 4开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
 5开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            switch (EncodingType)
 6开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来{
 7开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                case PaTextEncoding.UTF16LittleEndian:
 8开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    DecodedString = System.Text.Encoding.Unicode.GetString(buffer);
 9开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    break;
10开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                case PaTextEncoding.UTF16BigEndian:
11开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    DecodedString = System.Text.Encoding.BigEndianUnicode.GetString(buffer);
12开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    break;
13开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                case PaTextEncoding.UTF8:
14开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    DecodedString = System.Text.Encoding.UTF8.GetString(buffer);
15开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    break;
16开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                case PaTextEncoding.UTF7:
17开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    DecodedString = System.Text.Encoding.UTF7.GetString(buffer);
18开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    break;
19开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                case PaTextEncoding.Unknown:
20开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    throw new Exception("Unkonw Encoding");
21开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    break;
22开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            }

23开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来

想法是没错的,但有一个小小的问题,之前我们提到过BOM,不同的编码文件前面几个字节会有不同的BOM标示,这几个字节唯一的作用就是指明编码类型,在解码时应该去掉这几个字节,但问题是,GetString()函数不会自动去掉这几个字节,如果直接把所有的字节数组传给GetString()函数,因为BOM的影响,解码得到的字符串前面几个字是乱码。

DNN里用了一个比较巧妙的办法,首先侦测字节数组的编码方式,之后把所有的字节数组都转换为ASCII编码方式的字节数组,最后通过ASCIIEncoding的GetString()函数得到字符串。因为BOM的影响,转换得到的ASCII字符串前面会有一些”?”字符,查找这些字符并去掉即可。代码如下:

这一部分代码在DNN的DotNetNuke.Modules.Admin.ResourceInstaller命名空间下的PaDnnInstallerBase类里。

GetAsciiString()函数实现转换为ASCII编码,并解码为String


开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来Code
 1开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来        Protected Function GetAsciiString()Function GetAsciiString(ByVal Buffer As Byte(), ByVal SourceEncoding As Encoding) As String
 2开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
 3开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            ' Create two different encodings.
 4开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            Dim TargetEncoding As Encoding = Encoding.ASCII
 5开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
 6开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            ' Perform the conversion from one encoding to the other.
 7开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            Dim asciiBytes As Byte() = Encoding.Convert(SourceEncoding, TargetEncoding, Buffer)
 8开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
 9开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            ' Convert the new byte[] into an ascii string.
10开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            Dim asciiString As String = System.Text.Encoding.ASCII.GetString(asciiBytes)
11开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
12开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            Return asciiString
13开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来        End Function

14开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来

根据不同的编码方式,传入不同的参数:

开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来Code
 1开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来        Dim strScript As String = ""
 2开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            Select Case sqlFile.Encoding
 3开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                Case PaTextEncoding.UTF16LittleEndian
 4开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    strScript = GetAsciiString(sqlFile.Buffer, System.Text.Encoding.Unicode)     'System.Text.Encoding.Unicode.GetString(sqlFile.Buffer)
 5开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                Case PaTextEncoding.UTF16BigEndian
 6开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    strScript = GetAsciiString(sqlFile.Buffer, System.Text.Encoding.BigEndianUnicode)     'System.Text.Encoding.BigEndianUnicode.GetString(sqlFile.Buffer)
 7开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                Case PaTextEncoding.UTF8
 8开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    strScript = GetAsciiString(sqlFile.Buffer, System.Text.Encoding.UTF8)     'System.Text.Encoding.UTF8.GetString(sqlFile.Buffer)
 9开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                Case PaTextEncoding.UTF7
10开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    strScript = GetAsciiString(sqlFile.Buffer, System.Text.Encoding.UTF7)     'System.Text.Encoding.UTF7.GetString(sqlFile.Buffer)
11开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                Case PaTextEncoding.Unknown
12开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                    Throw New Exception(String.Format(SQL_UnknownFile, sqlFile.Name))
13开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            End Select
14开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来
15开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'This check needs to be included because the unicode Byte Order mark results in an extra character at the start of the file
16开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            'The extra character - '?' - causes an error with the database.
17开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            If strScript.StartsWith("?"Then
18开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来                strScript = strScript.Substring(1)
19开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来            End If
20开源ASP.NET程序是如何处理文件编码的-从DotNetNuke看过来


最后的一点问题

DNN里这种避免BOM影响解码的方法有一个问题,那就是它把所有的文件都转为ASCII编码,而ASCII编码是不支持双字节的,也就是说如果文件中包含中文,中文在解码后就成为乱码了。具体现象可以参考这个文章;SQL SERVER 2005 EXPRESS与ASP.net出现中文变成问号的奇怪问题。很可能不是通常的utf-8编码问题。

我想解决方案是,把所有的文件都转为UTF编码,针对BOM影响编码的问题,使用UTF8Encoding.GetString(buffer, 3, buffer.length)跳过字节数组的前三个字节。




参考文献:

编码,charset,乱码,unicode,utf-8与net简单释义

编码,charset,乱码,unicode,utf-8与net简单释义(转载合集版本)

让你知道codepage的重要,关于多语言编码[转载]

Byte Order Mark (BOM) FAQ

Display problems caused by the UTF-8 BOM

字节流编码获取原来这么复杂

Every character has a story #4: U+feff (alternate title: UTF-8 is the BOM, dude!)

上一篇:系统架构师-基础到企业应用架构系列之--介绍篇


下一篇:centos7的网卡名修改为eth0