Unicode以外のテキスト・ファイルを読み取るには?[Win 8]:WinRT/Metro TIPS
テキスト・ファイルを読み取る際に、文字エンコーディングが分かっている場合の方法と、分からない場合の設計指針を解説する。
powered by Insider.NET
テキスト・ファイルの文字エンコーディングがUnicodeならば、読み取ることは簡単だ。ところが日本でアプリを作るには、シフトJISなどのエンコーディングを無視するわけにはいかないのが現状だ。Unicode以外の文字エンコーディングのテキスト・ファイルを読み取るにはどうしたらいいだろうか? 本稿では、文字エンコーディングが分かっている場合の方法と、分からない場合の設計指針を解説する。本稿のサンプルは「Windows Store app samples:MetroTips #46(Windows 8版)」からダウンロードできる。
なお、Windows Phone 8の場合は、「WinRT/Metro TIPS:シフトJISのデータを読み取るには?[WP 8]」で解説したように、Unicode以外の文字エンコーディングを扱うにはC++/CXでWin32 APIを利用する必要がある。Windows 8(以降、Win 8)の場合とは大きく異なるため、本稿では割愛させていただく。
事前準備
Win 8向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。本稿では64bit版Win 8 ProとVS 2012 Express for Windows 8を使用している。
サンプル・ファイルの用意
VS 2012でWindowsストア・アプリのプロジェクトを作成したら、アプリから読み取るためのサンプル・ファイルを用意しよう。
まず、プロジェクトに新しいフォルダを追加して「text」と名前を付ける。ソリューション・エクスプローラで「text」フォルダを右クリックしてコンテキスト・メニューから[追加]−[新しい項目]を選択し、[新しい項目の追加]ダイアログで[テキスト ファイル]を選択して「Sample01.txt」というファイルを作る。
「Sample01.txt」ファイルの編集画面で、適当な文章を数行程度入力して保存するのだが、そのときに文字エンコーディングを指定する。それにはメニューバーから[ファイル]−[名前を付けて text\Sample01.txt を保存]を選ぶ。すると次の画像のような[名前を付けてファイルを保存]ダイアログが出てくる。
[名前を付けてファイルを保存]ダイアログ
文字エンコーディングを指定してテキスト・ファイルを保存するには、[上書き保存]ボタンの右にある「▼」マークをクリックして出てくるドロップダウンで[エンコード付きで保存]を選ぶ。
[名前を付けてファイルを保存]ダイアログの右下に[上書き保存]ボタンがあるが、その右側の[▼]マークをクリックする。出てきたドロップダウンで[エンコード付きで保存]を選ぶと、[名前を付けて保存の確認]ダイアログが出てきて上書きしてよいか尋ねられるので[はい]ボタンを選ぶ。すると、次の画像のような[保存オプションの詳細設定]ダイアログが出てくる。
[保存オプションの詳細設定]ダイアログの[エンコード]ドロップダウンで、文字エンコーディングを選ぶ。ここでは[日本語 (JIS) - コードページ 50220](=半角カナなしのJISコード)を選ぶことにする。ちなみに、マイクロソフト定義のシフトJIS(=コードページ932)は、日本語環境ではドロップダウン・リストの先頭にある。
なお、このようにして作成したUnicode以外の文字エンコーディングのテキスト・ファイルをVS 2012で開くときは、ソリューション・エクスプローラでファイルを右クリックして出てくるメニューから[ファイルを開くアプリケーションの選択]を選ぶ。すると[ファイルを開くアプリケーションの選択]ダイアログが出てくるので、[エンコード付きソース コード(テキスト)エディター]を選ぶ。そして[OK]ボタンをクリックすると出てくるダイアログで、文字エンコーディングを指定する。
画面の準備
画面には、次のコードのように「textBox1」という名前でテキストボックスを1つ置く。
……省略……
<TextBox x:Name="textBox1" Grid.Row="1" TextWrapping="Wrap" AcceptsReturn="True"
……省略…… />
……省略……
Unicodeエンコーディングとして読み込んでみる
まずは「WinRT/Metro TIPS:アプリに同梱したテキスト・ファイルを読むには?[Win 8/WP 8]」でやったようにして、読み込みを試してみよう(次のコード)。
// URIを指定してStorageFileオブジェクトを得る
Windows.Storage.StorageFile sampleFile
= await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
new Uri("ms-appx:///text/sample01.txt"));
// 【1】UNICODEだとして読み込む
this.textBox1.Text = await Windows.Storage.FileIO.ReadTextAsync(sampleFile);
// UNICODE以外のエンコーディングだと、これは例外が出ることがある
' URIを指定してStorageFileオブジェクトを得る
Dim sampleFile As Windows.Storage.StorageFile _
= Await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
New Uri("ms-appx:///text/sample01.txt"))
' 【1】UNICODEだとして読み込む
Me.textBox1.Text = Await Windows.Storage.FileIO.ReadTextAsync(sampleFile)
' UNICODE以外のエンコーディングだと、これは例外が出ることがある
このコードを記述する場所は、LoadStateメソッド(LayoutAwarePageクラスを継承した場合)またはOnNavigatedToメソッド(継承しない場合)である。
なお、非同期メソッド呼び出しを含んでいるので、メソッドにはasync/Async宣言が必要になる。
実行してみると、次の画像のように文字化けして表示されるか、例外が出てアプリが終了するかのどちらかになる*1。
*1 後述する文字エンコーディングが事前に分からない場合について。Unicode以外の文字エンコーディングの場合に必ず例外が出るのならば、まず上記のコードを実行してみて、例外が出たときだけ他の文字エンコーディングを試す、という作りにできる。実際には、例外が出ることなく上の画像のように文字化けして読み込まれてしまうこともあるため、UTF-8/UTF-16/UTF-16BEなども個別にチェックしなければならない。
文字エンコーディングを指定して読み込む
テキスト・ファイルの文字エンコーディングがあらかじめ分かっているなら、文字エンコーディングを指定して読み込めばよい。JISコードの場合は、次のコードのようになる。
// URIを指定してStorageFileオブジェクトを得る
Windows.Storage.StorageFile sampleFile
= await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
new Uri("ms-appx:///text/sample01.txt"));
// 【2】エンコーディングを指定して読み込む
using (Stream st = (await sampleFile.OpenReadAsync()).AsStream())
using (TextReader reader = new StreamReader(st,
System.Text.Encoding.GetEncoding("iso-2022-jp")))
{
this.textBox1.Text = await reader.ReadToEndAsync();
}
' URIを指定してStorageFileオブジェクトを得る
Dim sampleFile As Windows.Storage.StorageFile _
= Await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
New Uri("ms-appx:///text/sample01.txt"))
' 【2】エンコーディングを指定して読み込む
Using st As Stream = (Await sampleFile.OpenReadAsync()).AsStream(), _
reader As TextReader = New StreamReader(st, _
System.Text.Encoding.GetEncoding("iso-2022-jp"))
Me.textBox1.Text = Await reader.ReadToEndAsync()
End Using
上のコードのように、ファイルを開き、JISコード(=「iso-2022-jp」)を表すEncodingオブジェクトを第2引数としてStreamReaderオブジェクトを構築し、あとはそのStreamReaderオブジェクトに読み込ませる。
なお、上で引数として指定している文字エンコーディングの名前は、MSDNの「Encoding クラス」のページに掲載されている。
実行してみると、今度は正しく表示される。
文字エンコーディングが不明なテキスト・ファイルを読み込むには?
文字エンコーディングを確実に判別する方法はないので、ユーザーに指定してもらうのが基本になる。そうはいっても、使いやすいアプリにするには、できるだけアプリで正しく読み込む工夫をして、だめだったときに限ってユーザーの手を煩わせるべきだろう。
文字エンコーディングを推定する方法はいろいろと考えられるだろうが、簡易的には読み込んでみて文字化けしていないようならOKとするやり方がある。いったんバイト配列に読み込ませてから、いろいろな文字エンコーディングで文字列に変換してみて、正しそうな結果を選び出すのだ。その判定方法として、ここでは変換後の文字数を見ることにしよう。文字化けすると文字数は増える傾向にあるから、変換してみて一番文字数が少なかった文字エンコーディングが正解だとするのだ*2。
*2 .NET Frameworkには引数を3つ指定するEncoding.GetEncoding(Int32, EncoderFallback, DecoderFallback)メソッドがあり、これを使えば変換に失敗したことを検出できる。しかし残念なことに、このオーバーロードはWindowsストア・アプリでは利用できない。
何度も変換を実行することになるので、バイト配列を文字列に変換するメソッドをまず用意しておこう。次のコードのようにして、Encodingオブジェクトを使ってバイト配列を文字列に変換できる。
private static string GetString(byte[] bytes, string encodingName)
{
var encoding = System.Text.Encoding.GetEncoding(encodingName);
return encoding.GetString(bytes, 0, bytes.Length);
}
Private Shared Function GetString(bytes As Byte(), encodingName As String) As String
Dim encoding = System.Text.Encoding.GetEncoding(encodingName)
Return encoding.GetString(bytes, 0, bytes.Length)
End Function
読み込んだテキスト・ファイルの内容を、想定されるいくつかの文字エンコーディングで変換して、その中から文字数が最も少ないものを選び出すコードは次のようになる。ここでは、UTF-8/JIS/EUC-JP/シフトJISのどれかであろうと仮定した。
// URIを指定してStorageFileオブジェクトを得る
Windows.Storage.StorageFile sampleFile
= await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
new Uri("ms-appx:///text/sample01.txt"));
// 【3】エンコーディングが不明な場合
// ファイルからいったんバイト配列に読み込む
uint numBytes = 0;
byte[] bytesBuffer = null;
using (Windows.Storage.Streams.IRandomAccessStream ras = await sampleFile.OpenReadAsync())
using (var reader = new Windows.Storage.Streams.DataReader(ras.GetInputStreamAt(0)))
{
numBytes = (uint)ras.Size;
bytesBuffer = new byte[numBytes];
await reader.LoadAsync(numBytes);
reader.ReadBytes(bytesBuffer);
}
// いくつかのエンコーディングで文字列に変換する
string utf8 = GetString(bytesBuffer, "UTF-8");
string jis = GetString(bytesBuffer, "iso-2022-jp");
string euc = GetString(bytesBuffer, "euc-jp");
string sjis = GetString(bytesBuffer, "Shift_JIS");
// 文字列の長さが一番短いものを選ぶ
string text = utf8;
if (text.Length > jis.Length)
text = jis;
if (text.Length > euc.Length)
text = euc;
if (text.Length > sjis.Length)
text = sjis;
this.textBox1.Text = text;
' URIを指定してStorageFileオブジェクトを得る
Dim sampleFile As Windows.Storage.StorageFile _
= Await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(
New Uri("ms-appx:///text/sample01.txt"))
' 【3】エンコーディングが不明な場合
' ファイルからいったんバイト配列に読み込む
Dim numBytes As UInteger = 0
Dim bytesBuffer() As Byte = Nothing
Using ras As Windows.Storage.Streams.IRandomAccessStream = Await sampleFile.OpenReadAsync(), _
reader = New Windows.Storage.Streams.DataReader(ras.GetInputStreamAt(0))
numBytes = ras.Size
bytesBuffer = New Byte(numBytes - 1) {}
Await reader.LoadAsync(numBytes)
reader.ReadBytes(bytesBuffer)
End Using
' いくつかのエンコーディングで文字列に変換する
Dim utf8 As String = GetString(bytesBuffer, "UTF-8")
Dim jis As String = GetString(bytesBuffer, "iso-2022-jp")
Dim euc As String = GetString(bytesBuffer, "euc-jp")
Dim sjis As String = GetString(bytesBuffer, "Shift_JIS")
' 文字列の長さが一番短いものを選ぶ
Dim text As String = utf8
If (Text.Length > jis.Length) Then
text = jis
ElseIf (text.Length > euc.Length) Then
text = euc
ElseIf (text.Length > sjis.Length) Then
text = sjis
End If
Me.textBox1.Text = text
実際には、このコードで試している4とおりのほかにも、UTF-16/UTF-16BEや、そのほか想定される文字エンコーディングを全て試す必要があるだろう。
上のコードでは、まずテキスト・ファイルをバイト配列に読み込む。それから4とおりのエンコーディングで文字列に変換し、その中から最も文字数の小さいものを選択している。これでかなりの精度で正しく表示できるはずだ。「sample01.txt」ファイルの文字エンコーディングをいろいろと変えて、試してみてほしい。
なお、非常に大きなファイルにも対応する場合には、ファイルの先頭から例えば1Kbytesだけバイト配列に読み込み、上記のようにして文字エンコーディングを決定してから、あらためて全体を読み込むようにする。
まとめ
Unicode以外の文字エンコーディングのテキスト・ファイルは、文字エンコーディングを指定して読み込む。事前に文字エンコーディングが分かっているなら、これで問題はない。事前に分からない場合は完璧に文字エンコーディングを判定することは不可能なので、ユーザーに指示してもらうことを基本に、アプリ側でも文字エンコーディングを推測するロジックを用意するとよい。
Copyright© Digital Advantage Corp. All Rights Reserved.