バイト型配列を文字列に変換する方法は「バイト型配列のデータを文字コードを指定して文字列に変換する」で紹介しましたが、データの文字コードが分からなければ変換できません。ここでは、バイト型配列のデータから文字コードを判別する方法を紹介します。
.NET Frameworkでは、基本的には、文字コードを判別する方法が用意されていませんので、外部DLL、OCX等を使うか、自分でコードを書くかということになります。
BOM(バイトオーダーマーク、byte order mark)と呼ばれる「印」がデータの先頭に付いている時は、これを手掛かりに文字コードを判別することができます。
以下に、BOMから文字コード(UTF-8、UTF-16BE、UTF-16LE、UTF-32BE、UTF-32LE)を判別するメソッドの例を示します。
''' <summary> ''' BOMを調べて、文字コードを判別する。 ''' </summary> ''' <param name="bytes">文字コードを調べるデータ。</param> ''' <returns>BOMが見つかった時は、対応するEncodingオブジェクト。 ''' 見つからなかった時は、null。</returns> Public Shared Function DetectEncodingFromBOM(bytes As Byte()) _ As System.Text.Encoding If bytes.Length < 2 Then Return Nothing End If If (bytes(0) = &HFE) AndAlso (bytes(1) = &HFF) Then 'UTF-16 BE Return New System.Text.UnicodeEncoding(True, True) End If If (bytes(0) = &HFF) AndAlso (bytes(1) = &HFE) Then If (4 <= bytes.Length) AndAlso _ (bytes(2) = &H0) AndAlso (bytes(3) = &H0) Then 'UTF-32 LE Return New System.Text.UTF32Encoding(False, True) End If 'UTF-16 LE Return New System.Text.UnicodeEncoding(False, True) End If If bytes.Length < 3 Then Return Nothing End If If (bytes(0) = &HEF) AndAlso (bytes(1) = &HBB) AndAlso _ (bytes(2) = &HBF) Then 'UTF-8 Return New System.Text.UTF8Encoding(True, True) End If If bytes.Length < 4 Then Return Nothing End If If (bytes(0) = &H0) AndAlso (bytes(1) = &H0) AndAlso _ (bytes(2) = &HFE) AndAlso (bytes(3) = &HFF) Then 'UTF-32 BE Return New System.Text.UTF32Encoding(True, True) End If Return Nothing End Function
/// <summary> /// BOMを調べて、文字コードを判別する。 /// </summary> /// <param name="bytes">文字コードを調べるデータ。</param> /// <returns>BOMが見つかった時は、対応するEncodingオブジェクト。 /// 見つからなかった時は、null。</returns> public static System.Text.Encoding DetectEncodingFromBOM(byte[] bytes) { if (bytes.Length < 2) { return null; } if ((bytes[0] == 0xfe) && (bytes[1] == 0xff)) { //UTF-16 BE return new System.Text.UnicodeEncoding(true, true); } if ((bytes[0] == 0xff) && (bytes[1] == 0xfe)) { if ((4 <= bytes.Length) && (bytes[2] == 0x00) && (bytes[3] == 0x00)) { //UTF-32 LE return new System.Text.UTF32Encoding(false, true); } //UTF-16 LE return new System.Text.UnicodeEncoding(false, true); } if (bytes.Length < 3) { return null; } if ((bytes[0] == 0xef) && (bytes[1] == 0xbb) && (bytes[2] == 0xbf)) { //UTF-8 return new System.Text.UTF8Encoding(true, true); } if (bytes.Length < 4) { return null; } if ((bytes[0] == 0x00) && (bytes[1] == 0x00) && (bytes[2] == 0xfe) && (bytes[3] == 0xff)) { //UTF-32 BE return new System.Text.UTF32Encoding(true, true); } return null; }
次にこのメソッドの使い方を示します。この例では、TextBox1にテキストファイルのパスを入力し、Button1をクリックすると、テキストファイルの文字コードを調べ、デコードし、RichTextBox1にその内容を表示するようにしています。
Private Sub Button1_Click(sender As Object, e As EventArgs) _ Handles Button1.Click 'テキストファイルを開く Dim bs As Byte() = System.IO.File.ReadAllBytes(TextBox1.Text) '.NET Framework 1.1以下では、次のようにする 'Dim fs As New System.IO.FileStream(TextBox1.Text, _ ' System.IO.FileMode.Open, System.IO.FileAccess.Read) 'Dim bs(fs.Length - 1) As Byte 'fs.Read(bs, 0, bs.Length) 'fs.Close() '文字コードを判別する Dim enc As System.Text.Encoding = DetectEncodingFromBOM(bs) If enc Is Nothing Then Console.WriteLine("BOMが見つかりませんでした。") End If 'デコードして表示する 'はじめのBOMを飛ばしてデコードする Dim bomLen As Integer = enc.GetPreamble().Length RichTextBox1.Text = enc.GetString(bs, bomLen, bs.Length - bomLen) End Sub
//Button1のClickイベントハンドラ private void Button1_Click(object sender, System.EventArgs e) { //テキストファイルを開く byte[] bs = System.IO.File.ReadAllBytes(TextBox1.Text); //.NET Framework 1.1以下では、次のようにする //System.IO.FileStream fs = new System.IO.FileStream( // TextBox1.Text, System.IO.FileMode.Open, // System.IO.FileAccess.Read); //byte[] bs = new byte[fs.Length]; //fs.Read(bs, 0, bs.Length); //fs.Close(); //文字コードを判別する System.Text.Encoding enc = DetectEncodingFromBOM(bs); if (enc == null) { Console.WriteLine("BOMが見つかりませんでした。"); } //デコードして表示する //はじめのBOMを飛ばしてデコードする int bomLen = enc.GetPreamble().Length; RichTextBox1.Text = enc.GetString(bs, bomLen, bs.Length - bomLen); }
なお、StreamReaderクラスを使用してテキストファイルを開いた時は、BOMが付加されていれば、自動的に適切なエンコーディングが選択されます。よって、テキストファイルにBOMが付いていることが分かっているのであれば、普通にStreamReaderクラスで開くことができます。BOMが付いているか分からない場合でも、はじめにテキストファイルの先頭の4バイトのみを読み込み、それがBOMかどうかを調べ、BOMならばテキストファイルをStreamReaderクラスで開くという方法もあります。StreamReaderクラスについて詳しくは、「文字コードを指定してテキストファイルを読み込む」をご覧ください。
ここからは、BOMが付いていないデータの文字コードを判別する方法を紹介します。ただし、あくまで文字コードを推測するだけで、正確に判別することはできません。
次に示すコードは、私がJcode.pmのgetcodeメソッドを参考にして書かせていただいた(移植したつもり)メソッドです。バイナリ配列で渡されたデータが、JIS、Shift-JIS、EUC、UTF-8(もしくはASCII)のいずれであるかを判別し、結果をEncodingオブジェクトで返します。
''' <summary> ''' 文字コードを判別する ''' </summary> ''' <remarks> ''' Jcode.pmのgetcodeメソッドを移植したものです。 ''' Jcode.pm(http://openlab.ring.gr.jp/Jcode/index-j.html) ''' Jcode.pmの著作権情報 ''' Copyright 1999-2005 Dan Kogai <dankogai@dan.co.jp> ''' This library is free software; you can redistribute it and/or modify it ''' under the same terms as Perl itself. ''' </remarks> ''' <param name="bytes">文字コードを調べるデータ</param> ''' <returns>適当と思われるEncodingオブジェクト。 ''' 判断できなかった時はnull。</returns> Public Shared Function GetCode(ByVal bytes As Byte()) As System.Text.Encoding Const bEscape As Byte = &H1B Const bAt As Byte = &H40 Const bDollar As Byte = &H24 Const bAnd As Byte = &H26 Const bOpen As Byte = &H28 ''(' Const bB As Byte = &H42 Const bD As Byte = &H44 Const bJ As Byte = &H4A Const bI As Byte = &H49 Dim len As Integer = bytes.Length Dim b1 As Byte, b2 As Byte, b3 As Byte, b4 As Byte 'Encode::is_utf8 は無視 Dim isBinary As Boolean = False Dim i As Integer For i = 0 To len - 1 b1 = bytes(i) If b1 <= &H6 OrElse b1 = &H7F OrElse b1 = &HFF Then ''binary' isBinary = True If b1 = &H0 AndAlso i < len - 1 AndAlso bytes(i + 1) <= &H7F Then 'smells like raw unicode Return System.Text.Encoding.Unicode End If End If Next If isBinary Then Return Nothing End If 'not Japanese Dim notJapanese As Boolean = True For i = 0 To len - 1 b1 = bytes(i) If b1 = bEscape OrElse &H80 <= b1 Then notJapanese = False Exit For End If Next If notJapanese Then Return System.Text.Encoding.ASCII End If For i = 0 To len - 3 b1 = bytes(i) b2 = bytes(i + 1) b3 = bytes(i + 2) If b1 = bEscape Then If b2 = bDollar AndAlso b3 = bAt Then 'JIS_0208 1978 'JIS Return System.Text.Encoding.GetEncoding(50220) ElseIf b2 = bDollar AndAlso b3 = bB Then 'JIS_0208 1983 'JIS Return System.Text.Encoding.GetEncoding(50220) ElseIf b2 = bOpen AndAlso (b3 = bB OrElse b3 = bJ) Then 'JIS_ASC 'JIS Return System.Text.Encoding.GetEncoding(50220) ElseIf b2 = bOpen AndAlso b3 = bI Then 'JIS_KANA 'JIS Return System.Text.Encoding.GetEncoding(50220) End If If i < len - 3 Then b4 = bytes(i + 3) If b2 = bDollar AndAlso b3 = bOpen AndAlso b4 = bD Then 'JIS_0212 'JIS Return System.Text.Encoding.GetEncoding(50220) End If If i < len - 5 AndAlso _ b2 = bAnd AndAlso b3 = bAt AndAlso b4 = bEscape AndAlso _ bytes(i + 4) = bDollar AndAlso bytes(i + 5) = bB Then 'JIS_0208 1990 'JIS Return System.Text.Encoding.GetEncoding(50220) End If End If End If Next 'should be euc|sjis|utf8 'use of (?:) by Hiroki Ohzaki <ohzaki@iod.ricoh.co.jp> Dim sjis As Integer = 0 Dim euc As Integer = 0 Dim utf8 As Integer = 0 For i = 0 To len - 2 b1 = bytes(i) b2 = bytes(i + 1) If ((&H81 <= b1 AndAlso b1 <= &H9F) OrElse _ (&HE0 <= b1 AndAlso b1 <= &HFC)) AndAlso _ ((&H40 <= b2 AndAlso b2 <= &H7E) OrElse _ (&H80 <= b2 AndAlso b2 <= &HFC)) Then 'SJIS_C sjis += 2 i += 1 End If Next For i = 0 To len - 2 b1 = bytes(i) b2 = bytes(i + 1) If ((&HA1 <= b1 AndAlso b1 <= &HFE) AndAlso _ (&HA1 <= b2 AndAlso b2 <= &HFE)) OrElse _ (b1 = &H8E AndAlso (&HA1 <= b2 AndAlso b2 <= &HDF)) Then 'EUC_C 'EUC_KANA euc += 2 i += 1 ElseIf i < len - 2 Then b3 = bytes(i + 2) If b1 = &H8F AndAlso (&HA1 <= b2 AndAlso b2 <= &HFE) AndAlso _ (&HA1 <= b3 AndAlso b3 <= &HFE) Then 'EUC_0212 euc += 3 i += 2 End If End If Next For i = 0 To len - 2 b1 = bytes(i) b2 = bytes(i + 1) If (&HC0 <= b1 AndAlso b1 <= &HDF) AndAlso _ (&H80 <= b2 AndAlso b2 <= &HBF) Then 'UTF8 utf8 += 2 i += 1 ElseIf i < len - 2 Then b3 = bytes(i + 2) If (&HE0 <= b1 AndAlso b1 <= &HEF) AndAlso _ (&H80 <= b2 AndAlso b2 <= &HBF) AndAlso _ (&H80 <= b3 AndAlso b3 <= &HBF) Then 'UTF8 utf8 += 3 i += 2 End If End If Next 'M. Takahashi's suggestion 'utf8 += utf8 / 2; System.Diagnostics.Debug.WriteLine( _ String.Format("sjis = {0}, euc = {1}, utf8 = {2}", sjis, euc, utf8)) If euc > sjis AndAlso euc > utf8 Then 'EUC Return System.Text.Encoding.GetEncoding(51932) ElseIf sjis > euc AndAlso sjis > utf8 Then 'SJIS Return System.Text.Encoding.GetEncoding(932) ElseIf utf8 > euc AndAlso utf8 > sjis Then 'UTF8 Return System.Text.Encoding.UTF8 End If Return Nothing End Function
/// <summary> /// 文字コードを判別する /// </summary> /// <remarks> /// Jcode.pmのgetcodeメソッドを移植したものです。 /// Jcode.pm(http://openlab.ring.gr.jp/Jcode/index-j.html) /// Jcode.pmの著作権情報 /// Copyright 1999-2005 Dan Kogai <dankogai@dan.co.jp> /// This library is free software; you can redistribute it and/or modify it /// under the same terms as Perl itself. /// </remarks> /// <param name="bytes">文字コードを調べるデータ</param> /// <returns>適当と思われるEncodingオブジェクト。 /// 判断できなかった時はnull。</returns> public static System.Text.Encoding GetCode(byte[] bytes) { const byte bEscape = 0x1B; const byte bAt = 0x40; const byte bDollar = 0x24; const byte bAnd = 0x26; const byte bOpen = 0x28; //'(' const byte bB = 0x42; const byte bD = 0x44; const byte bJ = 0x4A; const byte bI = 0x49; int len = bytes.Length; byte b1, b2, b3, b4; //Encode::is_utf8 は無視 bool isBinary = false; for (int i = 0; i < len; i++) { b1 = bytes[i]; if (b1 <= 0x06 || b1 == 0x7F || b1 == 0xFF) { //'binary' isBinary = true; if (b1 == 0x00 && i < len - 1 && bytes[i + 1] <= 0x7F) { //smells like raw unicode return System.Text.Encoding.Unicode; } } } if (isBinary) { return null; } //not Japanese bool notJapanese = true; for (int i = 0; i < len; i++) { b1 = bytes[i]; if (b1 == bEscape || 0x80 <= b1) { notJapanese = false; break; } } if (notJapanese) { return System.Text.Encoding.ASCII; } for (int i = 0; i < len - 2; i++) { b1 = bytes[i]; b2 = bytes[i + 1]; b3 = bytes[i + 2]; if (b1 == bEscape) { if (b2 == bDollar && b3 == bAt) { //JIS_0208 1978 //JIS return System.Text.Encoding.GetEncoding(50220); } else if (b2 == bDollar && b3 == bB) { //JIS_0208 1983 //JIS return System.Text.Encoding.GetEncoding(50220); } else if (b2 == bOpen && (b3 == bB || b3 == bJ)) { //JIS_ASC //JIS return System.Text.Encoding.GetEncoding(50220); } else if (b2 == bOpen && b3 == bI) { //JIS_KANA //JIS return System.Text.Encoding.GetEncoding(50220); } if (i < len - 3) { b4 = bytes[i + 3]; if (b2 == bDollar && b3 == bOpen && b4 == bD) { //JIS_0212 //JIS return System.Text.Encoding.GetEncoding(50220); } if (i < len - 5 && b2 == bAnd && b3 == bAt && b4 == bEscape && bytes[i + 4] == bDollar && bytes[i + 5] == bB) { //JIS_0208 1990 //JIS return System.Text.Encoding.GetEncoding(50220); } } } } //should be euc|sjis|utf8 //use of (?:) by Hiroki Ohzaki <ohzaki@iod.ricoh.co.jp> int sjis = 0; int euc = 0; int utf8 = 0; for (int i = 0; i < len - 1; i++) { b1 = bytes[i]; b2 = bytes[i + 1]; if (((0x81 <= b1 && b1 <= 0x9F) || (0xE0 <= b1 && b1 <= 0xFC)) && ((0x40 <= b2 && b2 <= 0x7E) || (0x80 <= b2 && b2 <= 0xFC))) { //SJIS_C sjis += 2; i++; } } for (int i = 0; i < len - 1; i++) { b1 = bytes[i]; b2 = bytes[i + 1]; if (((0xA1 <= b1 && b1 <= 0xFE) && (0xA1 <= b2 && b2 <= 0xFE)) || (b1 == 0x8E && (0xA1 <= b2 && b2 <= 0xDF))) { //EUC_C //EUC_KANA euc += 2; i++; } else if (i < len - 2) { b3 = bytes[i + 2]; if (b1 == 0x8F && (0xA1 <= b2 && b2 <= 0xFE) && (0xA1 <= b3 && b3 <= 0xFE)) { //EUC_0212 euc += 3; i += 2; } } } for (int i = 0; i < len - 1; i++) { b1 = bytes[i]; b2 = bytes[i + 1]; if ((0xC0 <= b1 && b1 <= 0xDF) && (0x80 <= b2 && b2 <= 0xBF)) { //UTF8 utf8 += 2; i++; } else if (i < len - 2) { b3 = bytes[i + 2]; if ((0xE0 <= b1 && b1 <= 0xEF) && (0x80 <= b2 && b2 <= 0xBF) && (0x80 <= b3 && b3 <= 0xBF)) { //UTF8 utf8 += 3; i += 2; } } } //M. Takahashi's suggestion //utf8 += utf8 / 2; System.Diagnostics.Debug.WriteLine( string.Format("sjis = {0}, euc = {1}, utf8 = {2}", sjis, euc, utf8)); if (euc > sjis && euc > utf8) { //EUC return System.Text.Encoding.GetEncoding(51932); } else if (sjis > euc && sjis > utf8) { //SJIS return System.Text.Encoding.GetEncoding(932); } else if (utf8 > euc && utf8 > sjis) { //UTF8 return System.Text.Encoding.UTF8; } return null; }
次にこのメソッドの使い方を示します。このサンプルでも先と同じく、TextBox1にテキストファイルのパスを入力し、Button1をクリックすると、RichTextBox1にその内容を表示します。
'Button1のクリックイベントハンドラ Private Sub Button1_Click(sender As Object, e As EventArgs) _ Handles Button1.Click 'テキストファイルを開く Dim bs As Byte() = System.IO.File.ReadAllBytes(TextBox1.Text) '文字コードを取得する Dim enc As System.Text.Encoding = GetCode(bs) 'デコードして表示する RichTextBox1.Text = enc.GetString(bs) End Sub
//Button1のクリックイベントハンドラ private void Button1_Click(object sender, System.EventArgs e) { //テキストファイルを開く byte[] bs = System.IO.File.ReadAllBytes(TextBox1.Text); //文字コードを取得する System.Text.Encoding enc = GetCode(bs); //デコードして表示する RichTextBox1.Text = enc.GetString(bs); }
無料で使用できる(と思われる)クラスやライブラリ、サンプルコード等を、以下に紹介します。使用の際には、ライセンス等の使用条件を必ずご確認ください。
外部DLLに頼る方法としては、mlang.dllのIMultiLanguage2::DetectInputCodepageを使用する方法があります。文字コードの判別を行うソフトの多くが、これを使用しているのではないかと思います。この方法は、Internet Explorer 5以上で使用できます。
以下に.NETでIMultiLanguage2::DetectInputCodepageメソッドを使うための方法を簡単に説明します。
mlang.dllを使うためにVisual Studioのメニュー[プロジェクト]-[参照の追加]でCOMを探してみても、mlang.dllは通常見つかりません。これは、mlang.dllにタイプライブラリがないためです。よって、まず"midl.exe"を使用して"MLang.Idl"からタイプライブラリ"MLang.tlb"を作成し、これを"regtlib.exe"を使って登録します。
"midl.exe"のパスは、Visual Studio .NET 2003ならば「C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\Bin\Midl.Exe」、Visual Studio 2008ならば「C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\midl.exe」のようになるようです。
補足:midl.exeが見つからない場合は、Visual Studioのコマンドプロンプトで「where midl」と入力したり、エクスプローラの検索を使用したりして探してください。以下に紹介するツールのパスも、同様の方法で探すことができます。
"MLang.Idl"のパスは、Visual Studio .NET 2003ならば「C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include\MLang.Idl」、Visual Studio 2008ならば「C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\MLang.Idl」のようになるようです。
次のようなコマンドを実行すれば、タイプライブラリ"MLang.tlb"がカレントディレクトリに作成されます(ディレクトリのパスは省略しています)。
midl.exe MLang.Idl
注意:midl.exeで「midl : command line error MIDL1005 : cannot find C preprocessor cl.exe」のようなエラーが出る場合は、cl.exeにパスを通すか、「コマンド ライン ビルドのパスと環境変数の設定」で説明されているように、vcvars32.bat(もしくは、Vcvarsall.bat)を実行してください。
"regtlib.exe"のパスは、Visual Studio .NET 2003ならば「C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include\regtlib.exe」のようになるようです。Visual Studio 2005からは"regtlib.exe"の代わりに"regtlibv12.exe"を使うことができ、「C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regtlibv12.exe」のようなパスになるようです。
次のようなコマンドを実行すれば、MLang.tlbを登録できます(ディレクトリのパスは省略しています)。
regtlib.exe MLang.tlb
補足:登録を解除するときは、"-u"パラメータを付けます。
これで「参照の追加」の「COM」で"mlang.dll"(MultiLanguage Object Model)を選択できるようになります。ここで"mlang.dll"を選択すると、「参照設定」に「MultiLanguage」が追加され、RCWを提供するアセンブリ「Interop.MultiLanguage.dll」がビルド出力先のフォルダに自動的に作成されるようになります。
今までいろいろ面倒なことをやってきましたが、もし「Interop.MultiLanguage.dll」がすでにあるのであれば、COMを登録する必要はなく、「Interop.MultiLanguage.dll」を「参照設定」に追加するだけでOKです。
補足:Visual Studioを使わない(もしくは、COMを登録しない)場合は、"Tlbimp.exe"(タイプライブラリインポーター)を使ってタイプライブラリから「Interop.MultiLanguage.dll」を作成することもできます。また、tlbimp2というオープンソースのツールもあります。
これで、DetectInputCodepageメソッドを使う準備ができました。次にDetectInputCodepageメソッドを使って文字コードを判別するメソッドのサンプルを示します。
私が試した限りでは、このコードは64ビットでは正常に動作しないようです。よって、プロジェクトのプロパティで、「ビルド」(VBでは、「コンパイル」)タブの「プラットフォームターゲット」を「x86」とするか、「32ビットの優先」をオンにしてください。
''' <summary> ''' IMultiLanguage2::DetectInputCodepageを使用して、文字コードを判別する ''' </summary> ''' <param name="bytes">文字コードを調べたいデータ。</param> ''' <returns>第一候補のEncodingオブジェクト。 ''' 失敗した時は、null。</returns> Public Shared Function DetectEncoding(bytes As Byte()) As System.Text.Encoding 'データが短い時は長くする If bytes.Length < 256 Then Dim loopCount As Integer = 256 \ bytes.Length + 1 Dim buf As Byte() = New Byte(loopCount * bytes.Length - 1) {} For i As Integer = 0 To loopCount - 1 Array.Copy(bytes, 0, buf, bytes.Length * i, bytes.Length) Next bytes = buf End If 'byte型配列をsbyte型配列に変換する Dim sbyts As SByte() = DirectCast(DirectCast(bytes, Object), SByte()) 'Dim sbyts(bytes.Length - 1) As System.SByte 'System.Buffer.BlockCopy(bytes, 0, sbyts, 0, bytes.Length) 'CMultiLanguageClassを作成 Dim ml As MultiLanguage.IMultiLanguage2 = _ DirectCast(New MultiLanguage.CMultiLanguageClass(), _ MultiLanguage.IMultiLanguage2) If ml Is Nothing Then Return Nothing End If '取得する候補の数だけ、tagDetectEncodingInfo配列を作成 'ここでは、1つだけとする Dim scores As Integer = 1 Dim detects As MultiLanguage.tagDetectEncodingInfo() = _ New MultiLanguage.tagDetectEncodingInfo(scores - 1) {} Dim enc As System.Text.Encoding = Nothing Dim len As Integer = sbyts.Length Try '文字コードを判別 ml.DetectInputCodepage(0, 0, sbyts(0), len, detects(0), scores) '成功したか確認 If 0 < scores Then '一番初めのtagDetectEncodingInfoのEncodingを取得 'detectsはnDocPercentの大きい順番に並んでいる? enc = System.Text.Encoding.GetEncoding(CInt(detects(0).nCodePage)) End If Finally '後始末 System.Runtime.InteropServices.Marshal.FinalReleaseComObject(ml) End Try Return enc End Function End Class
/// <summary> /// IMultiLanguage2::DetectInputCodepageを使用して、文字コードを判別する /// </summary> /// <param name="bytes">文字コードを調べたいデータ。</param> /// <returns>第一候補のEncodingオブジェクト。 /// 失敗した時は、null。</returns> public static System.Text.Encoding DetectEncoding(byte[] bytes) { //データが短い時は長くする if (bytes.Length < 256) { int loopCount = 256 / bytes.Length + 1; byte[] buf = new byte[loopCount * bytes.Length]; for (int i = 0; i < loopCount; i++) { Array.Copy(bytes, 0, buf, bytes.Length * i, bytes.Length); } bytes = buf; } //byte型配列をsbyte型配列に変換する sbyte[] sbyts = (sbyte[])(object)bytes; //sbyte[] sbyts = new sbyte[bs.Length]; //System.Buffer.BlockCopy(bs, 0, sbyts, 0, bs.Length); //CMultiLanguageClassを作成 MultiLanguage.IMultiLanguage2 ml = (MultiLanguage.IMultiLanguage2) new MultiLanguage.CMultiLanguageClass(); if (ml == null) { return null; } //取得する候補の数だけ、tagDetectEncodingInfo配列を作成 //ここでは、1つだけとする int scores = 1; MultiLanguage.tagDetectEncodingInfo[] detects = new MultiLanguage.tagDetectEncodingInfo[scores]; System.Text.Encoding enc = null; int len = sbyts.Length; try { //文字コードを判別 ml.DetectInputCodepage( 0, 0, ref sbyts[0], ref len, out detects[0], ref scores); //場合によっては(Visual Studio 2008以下?)、次のようになる //ml.DetectInputCodepage( // 0, 0, ref sbyts[0], ref len, ref detects[0], ref scores); //成功したか確認 if (0 < scores) { //一番初めのtagDetectEncodingInfoのEncodingを取得 //detectsはnDocPercentの大きい順番に並んでいる? enc = System.Text.Encoding.GetEncoding((int)detects[0].nCodePage); } } finally { //後始末 System.Runtime.InteropServices.Marshal.FinalReleaseComObject(ml); } return enc; }
注意:「相互運用機能型 'MultiLanguage.CMultiLanguageClass' を埋め込むことができません。代わりに適用可能なインターフェイスを使用してください。」というエラーが出る場合は、「参照設定」にある「MultiLanguage」のプロパティで、「相互運用機能型の埋め込み」をFalseにしてください。
scoresを1より大きくすると、DetectInputCodepageメソッドにより複数の候補が返されることがあります。候補の数はscoresに入り、その数だけdetectsの要素に入ります。候補の内どれが有力かは、tagDetectEncodingInfoのnDocPercentやnConfidenceで判断します。nDocPercentが一番高い候補がdetects[0]に入るようです。
「Detect Encoding for In- and Outgoing Text」によると、判別するデータが短すぎる時は、そのデータを繰り返してでも長くした方がよいということです。私が試してもその通りでしたので、上のコードではそれをまねて、データが短い時は長くしてから判別しています。
最後に、「Interop.MultiLanguage.dll」を同梱したサンプルのプロジェクトを置いておきます。このプロジェクトはVisual Studio 2013で作成したもので、参照設定にはCOMではなく、「Interop.MultiLanguage.dll」を追加しています。ここで使用している「Interop.MultiLanguage.dll」は、別のプロジェクトでCOMを参照設定に追加した時に作成されたものです。
(この記事は、「.NETプログラミング研究 第43号」で紹介したものを基にしています。)