Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2010年02月16日] [更新 2017年04月21日] Delphi サンプルプログラム集
   
883_文字コードページとテキストファイルの保存と表示
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro + Indy 10.5.7
883_CodePageTextFile.zip [10,269 KB] 2017年04月21日版 (EXE 同梱)







文字コードページとテキストファイルの保存と表示


テキストの保存と,保存したテキストを読み込んで表示するテストサンプルです.コードページの指定と自動判定関係のテストも行っています.


[備考]
文字のコードポイントの値の取得関係については以下に記事があります.




01_文字コード ( コードページ ) とテキストの表示


Delphi 2009 では,表示コントロールは,UTF-16 の文字コードのユニコード表示となりました.
そのため,テキストをファイルから読出して表示するには文字コードの変換が必要な場合があります.ただし,日本語環境の場合は,文字コードが (内部表現である) Shift-JIS の時は自動的に文字化けしないで表示してくれます.
また UTF-8 の時にも,BOM (バイトオーダーマーク) 付きであれば GetBufferEncoding の説明にあるように,文字化けしないで表示できます.一般に,BOM なしの場合,UTF-8N と表記します.

このサンプルは,[000.txt] という UTF-8N のテキストファイルを読み込んで 文字コードの変換,つまり,エンコードをしないで TMemo に表示すると,どうなるかを確認するテストです.


図1
設計時画面

図2
実行してボタンクリック

  • UTF-8N のファイルを読み込んで表示
  • どちらのボタンでも見事文字化けする

リスト1
UTF-8N (BOM なし) のテキストを読み出して,エンコードしないで表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  テキストファイルを読込んで表示
//  000.txtはUTF-8(BOMなし)のテキストファイル
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.LoadFromFile('000.txt');
end;

//=============================================================================
//  テキストファイルを読込んで表示
//  000.txtはUTF-8(BOMなし)のテキストファイル
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  AStream : TMemoryStream;
begin
  AStream := TMemoryStream.Create;
  try
    AStream.LoadFromFile('000.txt');
    Memo1.Lines.LoadFromStream(AStream);
  finally
    AStream.Free;
  end;
end;

end.




02_文字化けを防止するにはコードページを指定する


文字化けなしで表示するには,コードページを指定して文字コードを変換します.このサンプルはそのテストコードです.
このテストでは保存もします.保存は文字コードを指定しないと Shift-JIS で保存されます.文字コードの変換には TEncoding クラスの GetEncoding メソッドが使用できます.
GetEncoding で生成したインスタンスは,Free メソッド等で解放する必要があります.

文字列や文字列配列の文字列が,正常な文字列ではない状態で,TMemo や TRichEdit に代入しても正常な文字列として表示できません.注意が必要です.


本サンプルに添付している UTF-8 のファイルは BOM なしになっています.テキストファイルの保存の際,明示的に BOM なしで保存するには TStringList (TStrings) の WirteBOM プロパティの値を False にします.

    //意識的にBOMなして保存
    Memo1.Lines.WriteBOM := False;
    Memo1.Lines.SaveToFile('003.txt', TEncoding.Unicode);


[備考]
コードページによっては,表示用コントロールの Font.Charset と Font.Name の値を設定しないとテキストが正しく表示されないことがあります.



図3
設計時画面

図4
実行してボタンクリック

  • UTF-8N のファイルを読み込んで表示
  • どのボタンクリックでも文字化けしない

リスト2
UTF-8N のテキストファイルをエンコードして,TMemo に読込んで,保存する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  テキストファイルを読込んで表示,その後保存
//  000.txtはUTF-8(BOMなし)のテキストファイル
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  Enc : TEncoding;
begin
  Enc := TEncoding.GetEncoding(CP_UTF8);
  try
    //ファイルの文字コードを設定して読み込み
    Memo1.Lines.LoadFromFile('000.txt', Enc);

    //保存
    //日本語環境の場合はShift-JISで保存される
    Memo1.Lines.SaveToFile('001.txt');
  finally
    Enc.Free;
  end;
end;

//=============================================================================
//  テキストファイルを読込んで表示,その後保存
//  000.txtはUTF-8(BOMなし)のテキストファイル
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  SL  : TStringList;
  Enc : TEncoding;
begin
  SL := TStringList.Create;
  //ファイルの文字コードを設定して読み込み
  Enc := TEncoding.GetEncoding(CP_UTF8);
  try
    SL.LoadFromFile('000.txt', Enc);
    //Memo1のテキストに代入
    Memo1.Text := SL.Text;

    //Shift-JISで保存する
    SL.Clear;
    SL.Text := Memo1.Text;
    Enc.Free;
    Enc := TEncoding.GetEncoding(932);
    SL.SaveToFile('002.txt', Enc);
  finally
    SL.Free;
    Enc.Free;
  end;
end;

//=============================================================================
//  テキストファイルを読込んで表示,その後保存
//  000.txtはUTF-8(BOMなし)のテキストファイル
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  Enc : TEncoding;
begin
  Enc := TEncoding.GetEncoding(CP_UTF8);
  try
    //ファイルの文字コードを設定して読み込み
    Memo1.Lines.LoadFromFile('000.txt', Enc);

    //保存
    //Unicodeで保存される
    Memo1.Lines.SaveToFile('003.txt', TEncoding.Unicode);
  finally
    Enc.Free;
  end;
end;

//=============================================================================
//  テキストファイルを読込んで表示,その後保存
//  000.txtはUTF-8(BOMなし)のテキストファイル
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  AStream : TMemoryStream;
  Enc     : TEncoding;
begin
  AStream := TMemoryStream.Create;
  try
    AStream.LoadFromFile('000.txt');
    //UTF-8 --> Unicode(UTF-16)変換
    //AStreamがUTF-8の文字コードのバイナリであると解釈させる
    Enc := TEncoding.GetEncoding(CP_UTF8);
    Memo1.Lines.LoadFromStream(AStream, Enc);
    Enc.Free;

    //保存
    //Unicodeで保存される
    AStream.Clear;
    Enc := TEncoding.Unicode;
    Memo1.Lines.SaveToStream(AStream, Enc);
    AStream.SaveToFile('004.txt');
  finally
    AStream.Free;
  end;
end;

end.




03_TRichEdit を書式なしで扱うには PlainText を True にする


テキストの表示用コントロールとして,TRichEdit があります.これに表示するサンプルです.
TRichEdit は文字の色やサイズを設定した書式付きの文字列を扱うことができますが,本ページのサンプルでは書式付きの文字列は扱っていません.プレーンテキストという書式なしのテキストだけを扱っています.

プレーンテキストだけを扱う時は,TRichEdit の PlainText プロパティの値を True にします.デフォルトの False のままでも,プレーンテキストを読み込んで表示するだけであれば場合は問題ありませんが,保存するとリッチテキスト形式で保存されてしまいます.

動作は,上のサンプルと同じです.表示用のコントロールを TRichEdit にしただけです.


図5
PlainText プロパティを True にする

リスト3
UTF-8N のテキストファイルをエンコードして TRichEdit に読込んで,保存する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    RichEdit1: TRichEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  TRichEditを書式なしのテキストだけを扱うため,PlainTextをTrueにする
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //Clearしないと設計時の書式が適用される
  RichEdit1.Lines.Clear;
  RichEdit1.PlainText := True;
end;

//=============================================================================
//  テキストファイルを読込んで表示,その後保存
//  000.txtはUTF-8(BOMなし)のテキストファイル
//
//  GetEncodingを使用した場合はFreeが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  Enc : TEncoding;
begin
  Enc := TEncoding.GetEncoding(CP_UTF8);
  try
    //ファイルの文字コードを設定して読み込み
    RichEdit1.Lines.LoadFromFile('000.txt', Enc);

    //保存
    //日本語環境の場合はShift-JISで保存される
    RichEdit1.Lines.SaveToFile('001.txt');
  finally
    Enc.Free;
  end;
end;

//=============================================================================
//  テキストファイルを読込んで表示,その後保存
//  000.txtはUTF-8(BOMなし)のテキストファイル
//
//  GetEncodingを使用した場合はFreeが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  SL  : TStringList;
  Enc : TEncoding;
begin
  SL := TStringList.Create;
  //ファイルの文字コードを設定して読み込み
  Enc := TEncoding.GetEncoding(CP_UTF8);
  try
    SL.LoadFromFile('000.txt', Enc);
    //RichEdit1のテキストに代入
    RichEdit1.Text := SL.Text;

    //Shift-JISで保存する
    SL.Clear;
    SL.Text := RichEdit1.Text;
    Enc.Free;
    Enc := TEncoding.GetEncoding(932);
    SL.SaveToFile('002.txt', Enc);
  finally
    SL.Free;
    Enc.Free;
  end;
end;

//=============================================================================
//  テキストファイルを読込んで表示,その後保存
//  000.txtはUTF-8(BOMなし)のテキストファイル
//
//  GetEncodingを使用した場合はFreeが必要
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  Enc : TEncoding;
begin
  Enc := TEncoding.GetEncoding(CP_UTF8);
  try
    //ファイルの文字コードを設定して読み込み
    RichEdit1.Lines.LoadFromFile('000.txt', Enc);

    //保存
    //Unicodeで保存される
    RichEdit1.Lines.SaveToFile('003.txt', TEncoding.Unicode);
  finally
    Enc.Free;
  end;
end;

//=============================================================================
//  テキストファイルを読込んで表示,その後保存
//  000.txtはUTF-8(BOMなし)のテキストファイル
//
//  GetEncodingを使用した場合はFreeが必要
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  AStream : TMemoryStream;
  Enc     : TEncoding;
begin
  AStream := TMemoryStream.Create;
  try
    AStream.LoadFromFile('000.txt');
    //UTF-8 --> Unicode(UTF-16)変換
    //AStreamがUTF-8の文字コードのバイナリであると解釈させる
    Enc := TEncoding.GetEncoding(CP_UTF8);
    RichEdit1.Lines.LoadFromStream(AStream, Enc);

    //保存
    //Unicodeで保存される
    AStream.Clear;
    Enc.Free;
    Enc := TEncoding.Unicode;
    RichEdit1.Lines.SaveToStream(AStream, Enc);
    AStream.SaveToFile('004.txt');
  finally
    AStream.Free;
  end;
end;

end.




04_ユニコード ( Unicode ) の恩恵  -  中国簡体字を表示


ユニコード化の意義はいろいろありますが,その 1 つは多くの漢字が扱えるようになったことです.よく引き合いに出されるのが,人名用漢字の例としての明治の文豪「森鴎外」です.正式な字体は図の右の文字です.このような漢字も外字登録することなく扱えるようになりました.

古文書や古代文字の研究家にとっても,また人名を扱うにもまだまだ不十分ですが,Unicode の世界は広がっていき,当たり前の時代がくることでしょう.将来的には世界中の文字が普通に扱える時代がくるかも知れません.すでにそのような機能を持つ「超漢字」というソフトも存在しています.


図6
扱い可能になった漢字の例
  • 森鴎外と森鷗外


いろいろな国の文字が表示可能な例として,中国簡体字を表示してみます.プロジェクトには中国簡体字の 2 つのテキストファイルを添付しています.
たとえ文字のコードページが正しく選択されても,文字化けや表示できない文字があることもあります.日本語フォントの場合,Shift-JIS の文字範囲はほとんどのフォントセットで表示可能ですが,全てのフォントがユニコードの文字をサポートしているとは限りせん.フォントの Charset やフォント名も考慮する必要なことがあります.
このテストの文章は,http://zh.wikipedia.org/wiki/Delphi の一部を利用しました.


図7
設計時画面
図8
テスト用のテキストファイル

  • 中国簡体字 _BG2312.txt のコードは 936
  • 中国簡体字 _Unicode.txt は Unicode
図9
中国簡体字 _Unicode.txt を表示

  • Unicode なので文字化けしない
  • これは文字化けではなく,中国の簡体字であって,日本の漢字ではない
図10
中国簡体字 _GB2312.txt を表示

  • 文字化けしている
図11
エンコードすれば文字化けしない
  • これは文字化けではない.中国の簡体字.日本の漢字ではない

リスト4
中国簡体字を表示してみる
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, ComCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    OpenDialog1: TOpenDialog;
    RichEdit1: TRichEdit;
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    GetStream : TMemoryStream;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses RichEdit;

{$R *.dfm}

//=============================================================================
//  GetStreamは読込んだテキストを格納するメモリストリーム
//  TParaFormat2の使用にはusesにRichEditが必要
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
var
  ParaFormat2 : TParaFormat2;
begin
  GetStream := TMemoryStream.Create;

  //漢字を表示すると行間が詰まってしまうので,標準の行間にする
  //TMemoでは調整不可なのでTRichEditを使用
  ParaFormat2.cbSize := SizeOf(ParaFormat2);
  ParaFormat2.dwMask := PFM_LINESPACING;
  ParaFormat2.bLineSpacingRule := 0;
  SendMessage(RichEdit1.Handle, EM_SETPARAFORMAT, 0, Longint(@ParaFormat2));
end;

//=============================================================================
//  GetStreamを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  GetStream.Free;
end;

//=============================================================================
//  テキストファイルを読込んで表示
//
//  テストプロジェクトに以下のテキストファイルを用意してある
//  中国簡体字_GB2312.txt  GB2312(コードページは936)の簡体字のテキスト
//  中国簡体字_Unicode.txt ユニコードの簡体字のテキスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  OpenDialog1.Filter := 'テキスト|*.txt';
  OpenDialog1.InitialDir := ExtractFileDir(ParamStr(0));

  if OpenDialog1.Execute then begin
    Screen.Cursor := crHourGlass;
    RichEdit1.Clear;
    RichEdit1.Update;
    RichEdit1.Lines.BeginUpdate;
    RichEdit1.Font.Charset := DEFAULT_CHARSET;

    GetStream.Clear;
    try
      //テキストをGetMemoryに読込む
      GetStream.LoadFromFile(OpenDialog1.FileName);

      //それを表示
      GetStream.Position := 0;
      RichEdit1.Lines.LoadFromStream(GetStream);
    except
      RichEdit1.Lines.Add('読出しに失敗しました.' );
    end;
    RichEdit1.Lines.EndUpdate;
    Screen.Cursor := crDefault;
  end;
end;

//=============================================================================
//  エンコードして表示
//
//  中国の文字コードページ
//  936   gb2312  簡体字中国語 (GB2312)
//  950   big5  繁体字中国語 (Big5)
//  10008 x-mac-chinesesimp   簡体字中国語 (Mac)
//  20000 x-Chinese-CNS   繁体字中国語 (CNS)
//  51936 EUC-CN  簡体字中国語 (EUC)
//  52936 hz-gb-2312  簡体字中国語 (HZ)
//  54936 GB18030   簡体字中国語 (GB18030)
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  Enc : TEncoding;
begin
  if GetStream.Size = 0 then Exit;

  Screen.Cursor := crHourGlass;
  RichEdit1.Clear;
  RichEdit1.Update;
  RichEdit1.Lines.BeginUpdate;
  //中国語の場合CHINESEBIG5_CHARSETかGB2312_CHARSETにした方が字がきれいになる
  RichEdit1.Font.Charset := CHINESEBIG5_CHARSET;

  try
    //936(簡体字中国語-GB2312)からエンコード(Unicodeに)して表示
    Enc := TEncoding.GetEncoding(936);
    GetStream.Position := 0;
    RichEdit1.Lines.LoadFromStream(GetStream, Enc);
    Enc.Free;
  except
    on e: Exception do
    begin
      RichEdit1.Lines.Add('失敗しました.' + e.Message);
    end;
  end;

  RichEdit1.Lines.EndUpdate;
  Screen.Cursor := crDefault;
end;

end.




05_TIdHTTP でウェブページの HTML コードを取得して表示


いろいろな文字コードをテストすると言っても,中々手に入るものではありません.そこで掲示板等でよく質問がある,ネット上のページの HTML コードを取得して表示してみることにします.
本サンプルでは,コンボボックスで選択した URL の HTML ソースコードを取得して TMemo に表示します.文字化けした場合は,文字コードの選択コンボボックスから文字コードを選択して再表示します.選択する文字コードは,HTMLソースの Charset の記述が参考となりますが,必ずしもそれで文字化けが解消されるとは限りません.

HTML ではないテキストファイルの表示もそうですが,この手の文字化けは Delphi が Unicode 化されたから,Indy のコンポーネントを使用しているから発生するわけではありません.
このテストプログラムを Delphi 2009 よりも前のバージョンでコンパイルして実行してみればそのことを確認できます.


図12
設計時画面
図13
HTML コードを表示

  • Title も文字化けしている
図14
コードページ変更
  • 日本語 (EUC-JP) を選択

下図は,URL が http://www.delphi.gr/ の場合です.
HTML コードの Content-Style-Type の Charset が windows-1253 となっています.筆者の環境には,この Charset はインストールされていません.そこで,ギリシャ語のどれかを選択して再表示してみましたが,文字化けしてしまいます.
UTF-8 にすると文字化けしなくなりました.


図15
charset は windows1253
図16
文字化けしている
図17
UTF-8 ならOK

リスト5
コンボボックスで選択したサイトの HTML コードを取得して表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
  IdTCPClient, IdHTTP, Buttons;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    ComboBox1: TComboBox;
    IdHTTP1: TIdHTTP;
    SpeedButton1: TSpeedButton;
    ComboBox2: TComboBox;
    Label1: TLabel;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ComboBox2Click(Sender: TObject);
  private
    { Private 宣言 }
    GetStream : TMemoryStream;
    CodePage  : Integer;
    procedure GetCodeAndDistributeList(slCode, slCharsetName,
      slDistribute : TStringList);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI,
  Registry,
  MultiLanguage_TLB,
  ComObj;

{$R *.dfm}

//=============================================================================
//  メモリストリームを破棄
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  GetStream.Free;
end;

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
var
  CodeList       : TStringList;
  CharsetName    : TStringList;
  DistributeList : TStringList;
  i              : Integer;
  StrText        : String;
begin
  //HTMLソースを格納するメモリストリーム
  GetStream := TMemoryStream.Create;
  CodePage  := -1;

  //テスト用のURLをセット
  ComboBox1.Items.Clear;
  ComboBox1.Items.Add('http://mrxray.on.coocan.jp/index.htm');
  ComboBox1.Items.Add('http://www.videonews.com/');
  ComboBox1.Items.Add('http://www.yomiuri.co.jp/');
  ComboBox1.Items.Add('http://docwiki.embarcadero.com/RADStudio/ja/Main_Page');
  ComboBox1.Items.Add('http://www.upload.co.il/stage/studio/category.asp?id=8');
  ComboBox1.Items.Add('http://www.delphi.gr/');
  ComboBox1.Items.Add('http://ru.wikipedia.org/wiki/Delphi');
  ComboBox1.Items.Add('http://zh.wikipedia.org/wiki/Delphi');
  ComboBox1.Items.Add('http://ko.wikipedia.org/wiki/%EC%9D%BC%EB%B3%B8');

  ComboBox2.Style := csDropDown;
  ComboBox2.Clear;
  ComboBox2.Items.BeginUpdate;

  //文字エンコーディングに使用するコードページのリスト
  //CodePageが-1の時は文字コード変換しないようにするためにセット
  ComboBox2.Items.AddObject('指定なし', TObject(-1));
  //日本の場合のUnicodeのデフォルト値
  ComboBox2.Items.AddObject('shift_jis        日本語 (シフト JIS)', TObject(0));

  CodeList       := TStringList.Create;
  CharsetName    := TStringList.Create;
  DistributeList := TStringList.Create;
  try
    GetCodeAndDistributeList(CodeList, CharsetName, DistributeList);

    for i := 1 to CodeList.Count - 1 do begin
      StrText := Format('%-16s ', [CharsetName[i]]) + DistributeList[i];
      ComboBox2.Items.AddObject(StrText, TObject(StrToInt(CodeList[i])));
      Application.ProcessMessages;
    end;
  finally
    FreeAndNil(CodeList);
    FreeAndNil(CharsetName);
    FreeAndNil(DistributeList);
  end;

  //エンコーディングの初期値は[指定しない]
  ComboBox2.ItemIndex := 0;
  ComboBox2.Items.EndUpdate;
end;

//=============================================================================
//  文字コードと文字コード名のリストを作成
//  slCodeList    : 文字コードの格納リスト
//  slCharsetName : 文字コードのセット名のリスト
//  slDistribute  : 文字コード名の格納リスト
//
//  usesに  Registry,MultiLanguage_TLB,ComObjが必要
//=============================================================================
procedure TForm1.GetCodeAndDistributeList(slCode, slCharsetName,
  slDistribute: TStringList);
var
  Registry       : TRegistry;
  RegKey         : String;
  slGCodePage    : TStringList;
  slCharset      : String;
  MLang          : IMultiLanguage;
  MIMECPInfo     : TMIMECPINFO;
  i              : Integer;
  strDescription : String;
begin
  slGCodePage := TStringList.Create;
  slCode.Clear;
  slCharsetName.Clear;
  slDistribute.Clear;

  //レジストリから文字コードの情報を取得してリストに格納
  Registry := TRegistry.Create(KEY_READ);
  try
    Registry.RootKey := HKEY_CLASSES_ROOT;

    //レジストリから文字コードの数値のリストを取得して配列に格納
    RegKey := '\mime\database\codepage\';
    Registry.OpenKey(RegKey, False);
    Registry.GetKeyNames(slGCodePage);
    Registry.CloseKey;

    //mlangのオブジェクトを生成
    MLang := CoCMultiLanguage.Create;

    //レジストリから取得した文字コードのリストの説明文字を順番に取得
    for i := 0 to slGCodePage.Count - 1 do begin
      Registry.OpenKey(RegKey + slGCodePage[i], False);

      MLang.GetCodePageInfo(StrToInt(slGCodePage[i]), MIMECPInfo);
      strDescription := MIMECPInfo.wszDescription;
      slCharset      := Registry.ReadString('BodyCharset');
      Registry.CloseKey;

      slCode.Add(slGCodePage[i]);
      slCharsetName.Add(slCharset);
      slDistribute.Add(strDescription);
      Application.ProcessMessages;
    end;
  finally
    Registry.Free;
    FreeAndNil(slGCodePage);
    MLang := nil;
  end;
end;

//=============================================================================
//  [HTML GET]ボタンクリック
//  コードページ指定なしでComboBox1で表示中のURLのHTMLソースを取得して表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AURL : String;
begin
  Screen.Cursor := crHourGlass;
  ComboBox2.ItemIndex := 0;

  GetStream.Clear;
  //コンボボックスで選択中のURL文字列 
  AURL := Trim(ComboBox1.Text);

  try
    IdHTTP1.HandleRedirects := True;
    //URLのソースをGetStreamに取得
    IdHTTP1.Get(AURL, GetStream);
    //それを表示
    GetStream.Position := 0;
    Memo1.Lines.LoadFromStream(GetStream);
  except
    on e: EIdHTTPProtocolException do
    begin
      Memo1.Lines.Add('取得に失敗しました.' + e.Message);
    end;
    on e: Exception do
    begin
      Memo1.Lines.Add('失敗しました.' + e.Message);
    end;
  end;
  Memo1.SetFocus;
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  どんなサイトか確認するためにコンボボックスで選択したサイトを表示
//  ShellExecuteの使用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  AURL : string;
begin
  Screen.Cursor := crHourGlass;

  AURL := Trim(ComboBox1.Text);
  ShellExecute(Handle,'',PChar(AURL), '', nil, SW_SHOW);

  Screen.Cursor := crDefault;
end;

//=============================================================================
//  ComboBox2のItemをクリックしたらエンコーディングしてHTMLソースを再表示
//
//  エンコードが正しく行われても,必ずしもすべての文字が正常に表示されるとは限
//  らない.該当する国の文字がそのフォントには含まれていない場合がある.含まれ
//  ていても体裁が良くない場合もある.
//  したがって,フォントのCharsetやNameの変更が必要なことがある.
//=============================================================================
procedure TForm1.ComboBox2Click(Sender: TObject);
var
  Enc : TEncoding;
begin
  if GetStream.Size = 0 then exit;

  Screen.Cursor := crHourGlass;
  Memo1.Clear;
  Memo1.Update;
  Memo1.Lines.BeginUpdate;

  //選択Itemからコードページの数値を取り出す
  CodePage := Integer(ComboBox2.Items.Objects[ComboBox2.ItemIndex]);
  try
    GetStream.Position := 0;
    if CodePage < 0 then begin
      Memo1.Lines.LoadFromStream(GetStream);
    end else begin
      //エンコードして表示
      if (CodePage = 0) or (CodePage = 1200) then begin
        Memo1.Lines.LoadFromStream(GetStream);
      end else begin
        if CodePage = 51932 then CodePage := 20932;

        Enc := TEncoding.GetEncoding(CodePage);
        Memo1.Lines.LoadFromStream(GetStream, Enc);
        Enc.Free;
      end;
    end;
  except
    on e: Exception do
    begin
      Memo1.Lines.Add('失敗しました.' + e.Message);
    end;
  end;

  Memo1.Lines.EndUpdate;
  Memo1.SetFocus;
  Screen.Cursor := crDefault;
end;

end.




06_文字コード ( コードページ ) の自動変換表示


MLang の機能を使用して,文字コードの自動変換をテストするサンプルです.
タイプライブラリの取り込みで作成した MultiLanguage_TLB.pas を使用します.文字コードの変換には,IMultiLanguage2 インターフェイスの DetectInputCodepage メソッドを使用しています.

上のテストコード同様,ギリシャ語のサイトでコードページの値がうまく取得できす,文字化けしています.IE や FireFox では文字化けするが,このサンプルプログラムでは文字化けしないサイトもあります.日本語のサイトでは今のところ問題なく変換できているようです.
IE 等のブラウザでは,この windows-XXXX や他のタグ情報も利用してコードページの判定を行っているのかも知れません.

ブラウザはともかく,テキスト文書の文字コードを自動判定して文字化けしないように表示し,適切なフォントを選択するのは,現段階では完全に行うのは難しそうです.言語や文字コード (コードページ) が既知であれば,その設定で表示するのが一番いいような気がします.
対象となる文書内の文字の種類,例えば英数字だけとか,または短い文書だと判定に失敗することがあるようです.


[備考 2]
下図の実行結果で,HTTPS への接続のため失敗している例があります.近年,サイトの HTTPS 化が進んでいます.そのサイトも,本サンプル掲載当時は HTTP のサイトでした.



図18
設計時画面
図19
自動変換成功
図20
自動変換に失敗したウェブページ
  • このページは https サーバにリダイレクトされている
  • したがって,正確には,自動変換に失敗したのではなく,元のファイルのダウンロードに失敗していることになる

リスト6
コンボボックスで選択したサイトの HTML コードの文字コードを自動変換して表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdHTTP;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    ComboBox1: TComboBox;
    IdHTTP1: TIdHTTP;
    SpeedButton1: TSpeedButton;
    Memo1: TMemo;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private 宣言 }
    function DetectCodepage(SrcStream: TMemoryStream; dwFlag: Cardinal): Integer;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI, MultiLanguage_TLB, ActiveX, ComObj;

const
  MLDETECTCP_NONE   =  0;  //デフォルト
  MLDETECTCP_7BIT   =  1;  //7bitデータ
  MLDETECTCP_8BIT   =  2;  //8bitデータ
  MLDETECTCP_DBCS   =  4;  //ダブルバイト
  MLDETECTCP_HTML   =  8;  //HTMLページ
  MLDETECTCP_NUMBER = 16;  //未実装

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  //テスト用のURLをセット
  with ComboBox1.Items do begin
    Clear;
    Add('http://mrxray.on.coocan.jp/index.htm');
    Add('http://www.videonews.com/');
    Add('http://www.yomiuri.co.jp/');
    Add('http://docwiki.embarcadero.com/RADStudio/ja/Main_Page');
    Add('http://www.upload.co.il/stage/studio/category.asp?id=8');
    Add('http://www.delphi.gr/');
    Add('http://ru.wikipedia.org/wiki/Delphi');
    Add('http://zh.wikipedia.org/wiki/Delphi');
    Add('http://ko.wikipedia.org/wiki/%EC%9D%BC%EB%B3%B8');
  end;
end;

//=============================================================================
//  [HTML GET]ボタンクリック
//  コードページを自動検出してComboBox1で表示中のURLのHTMLソースを表示
//
//  TEncodintではUnicode(同じページコードへのエンコードは失敗するようだ
//  EUC-JP(コードページ51932)を検出した場合,
//  20932(マイクロソフト版日本語EUCらしい)を使用しないと文字化けする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AURL      : String;
  GetStream : TMemoryStream;
  CodePage  : Integer;
  Enc       : TEncoding;
begin
  Screen.Cursor := crHourGlass;
  //HTMLソースを格納するメモリストリーム
  GetStream := TMemoryStream.Create;

  GetStream.Clear;
  Memo1.Clear;
  Memo1.Update;
  Label1.Caption := '';

  //コンボボックスで選択中のURL文字列 
  AURL := Trim(ComboBox1.Text);

  try
    try
      IdHTTP1.HandleRedirects := True;
      //URLのソースをGetStreamに取得
      IdHTTP1.Get(AURL, GetStream);
      //コードページを取得して表示
      //引数のMLDETECTCP_HTMLは対象をHTMLとするもの
      //一般のテキストファイル等は他の値がいいかも知れない
      GetStream.Position := 0;
      CodePage :=  DetectCodepage(GetStream, MLDETECTCP_HTML);
      Label1.Caption := IntToStr(CodePage);
      if (CodePage = 0) or (CodePage = 1200) then begin
        Memo1.Lines.LoadFromStream(GetStream);
      end else begin
        if CodePage = 51932 then CodePage := 20932;

        Enc := TEncoding.GetEncoding(CodePage);
        GetStream.Position := 0;
        Memo1.Lines.LoadFromStream(GetStream, Enc);
        Enc.Free;
      end;
    except
      on e: EIdHTTPProtocolException do
      begin
        Memo1.Lines.Add('取得に失敗しました.' + e.Message);
      end;
      on e: Exception do
      begin
        Memo1.Lines.Add('失敗しました.' + e.Message);
      end;
    end;
  finally
    GetStream.Free;
  end;
  Memo1.SetFocus;
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  どんなサイトか確認するためにコンボボックスで選択したサイトを表示
//  ShellExecuteの使用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  AURL : string;
begin
  Screen.Cursor := crHourGlass;

  AURL := Trim(ComboBox1.Text);
  ShellExecute(Handle,'',PChar(AURL), '', nil, SW_SHOW);

  Screen.Cursor := crDefault;
end;

//=============================================================================
//  引数のSrcStreamに格納されている文字コードを判定する関数
//  サンプルプログラム集[886]で解説しているMLang.dllの機能を使用
//  戻り値がコードページの数値
//
//  SYSINT(実際にはInteger)の使用にはusesにActiveXが必要
//=============================================================================
function TForm1.DetectCodepage(SrcStream: TMemoryStream;
  dwFlag: Cardinal): Integer;
var
  hr         : HRESULT;
  MultiLang2 : IMultiLanguage2;
  SrcArray   : array of Char;
  SrcSize    : SYSINT;
  Encoding   : TDetectEncodingInfo;
  scores     : SYSINT;
begin
  //0だとShift-JIS(システムのデフォルト)も0で返すことがある
  Result := -1;

  //MultiLanguageオブジェクトの作成
  MultiLang2 := CreateComObject(CLASS_CMultiLanguage) as IMultiLanguage2;

  //使用するインターフェースをサポートしていれば実行
  if MultiLang2 <> nil then begin
    SrcSize := SrcStream.Size;
    SetLength(SrcArray, SrcSize);
    SrcStream.Position :=0;
    SrcStream.Read(SrcArray[0], Length(SrcArray));

    //取得したいコードページの候補数
    //ここでは最初の候補のみ(つまり1つだけ)検出
    scores := 1;
    hr := MultiLang2.DetectInputCodepage(dwFlag,
                                         0,
                                         SrcArray[0],
                                         SrcSize,
                                         Encoding,
                                         scores);
    if hr = S_OK then begin
      Result := Encoding.nCodePage;
    end;
    MultiLang2 := nil;
  end;
  SrcStream.Position := 0;
end;

end.




07_EncodeDetect, UCS4Encoding ユニットを使用した自動変換


Watercolor City でコードページの変換用のライブラリを提供しています.
これを使用したサンプルです.以下からファイルをダウンロードして,解凍したフォルダ内に含まれている EncodeDetect, UCS4Encoding の 2 つのユニットを利用します 以下がテストに使用したコードです.コードそのものは Watercolor City のサンプルとほとんど同じです.実行結果は MLang を使用した上のサンプルと同じです.
ただし,この結果は,今回テストに使用したウェブページの範囲内のことです.


リスト7
EncodeDetect, UCS4Encoding ユニットを使用したコードページの自動変換
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdHTTP;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    ComboBox1: TComboBox;
    IdHTTP1: TIdHTTP;
    SpeedButton1: TSpeedButton;
    Memo1: TMemo;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI, EncodeDetect, UCS4Encoding;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  //テスト用のURLをセット
  with ComboBox1.Items do begin
    Clear;
    Add('http://mrxray.on.coocan.jp/index.htm');
    Add('http://www.videonews.com/');
    Add('http://www.yomiuri.co.jp/');
    Add('http://docwiki.embarcadero.com/RADStudio/ja/Main_Page');
    Add('http://www.upload.co.il/stage/studio/category.asp?id=8');
    Add('http://www.delphi.gr/');
    Add('http://ru.wikipedia.org/wiki/Delphi');
    Add('http://zh.wikipedia.org/wiki/Delphi');
    Add('http://ko.wikipedia.org/wiki/%EC%9D%BC%EB%B3%B8');
  end;
end;

//=============================================================================
//  [HTML GET]ボタンクリック
//  コードページを自動検出してComboBox1で表示中のURLのHTMLソースを表示
//  EncodeDetect, UCS4Encoding ユニットを使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  Watercolor City で提供しているサンプルコードのコピペ
  //---------------------------------------------------------------------------
  function GetEncodingInstance(ENC: Integer): TEncoding;
  begin
    //判定結果に応じたTEncodingインスタンスの生成
    case ENC of
      ENC_SJIS: Result := TMBCSEncoding.Create(932);
      ENC_EUC: Result := TMBCSEncoding.Create(20932);
      ENC_JIS: Result := TMBCSEncoding.Create(50220);
      ENC_UTF16LE: Result := TUnicodeEncoding.Create;
      ENC_UTF16BE: Result := TBigEndianUnicodeEncoding.Create;
      ENC_UTF8: Result := TMBCSEncoding.Create(65001);
      ENC_UTF8BOM: Result := TUTF8Encoding.Create;
      ENC_UTF32LE: Result := TUCS4Encoding.Create;//要UCS4Encodingユニット
      ENC_UTF32BE: Result := TUCS4Encoding.Create(True);//要UCS4Encodingユニット
      else Result := TMBCSEncoding.Create(0);
    end;
  end;
  //---------------------------------------------------------------------------

var
  AURL      : String;
  GetStream : TMemoryStream;
  CodePage  : Integer;
  Enc       : TEncoding;
begin
  Screen.Cursor := crHourGlass;
  //HTMLソースを格納するメモリストリーム
  GetStream := TMemoryStream.Create;

  GetStream.Clear;
  Memo1.Clear;
  Memo1.Update;
  Label1.Caption := '';

  //コンボボックスで選択中のURL文字列 
  AURL := Trim(ComboBox1.Text);

  try
    try
      IdHTTP1.HandleRedirects := True;
      //URLのソースをGetStreamに取得
      IdHTTP1.Get(AURL, GetStream);
      //コードページを取得して表示
      CodePage := EncodeDetect.CheckEncoding(GetStream);
      Enc      := GetEncodingInstance(CodePage);

      GetStream.Position := 0;
      Memo1.Lines.LoadFromStream(GetStream, Enc);
      Enc.Free;
    except
      on e: EIdHTTPProtocolException do
      begin
        Memo1.Lines.Add('取得に失敗しました.' + e.Message);
      end;
      on e: Exception do
      begin
        Memo1.Lines.Add('失敗しました.' + e.Message);
      end;
    end;
  finally
    GetStream.Free;
  end;
  Memo1.SetFocus;
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  どんなサイトか確認するためにコンボボックスで選択したサイトを表示
//  ShellExecuteの使用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  AURL : string;
begin
  Screen.Cursor := crHourGlass;

  AURL := Trim(ComboBox1.Text);
  ShellExecute(Handle,'',PChar(AURL), '', nil, SW_SHOW);

  Screen.Cursor := crDefault;
end;

end.




08_TIdHTTP の Get メソッドの文字コードページ自動判定機能


TIdHTTP コンポーネントの Get メソッドには多くのオーバライドな関数があります.
その中に,引数が URL だけで,戻り値が String 型の関数があります.この関数を使用すると,ウェブページの文字コードページを自動判定してくれます.ただし,全ての HTML コードに対して有効なわけではないようです.
下図は,http://www.upload.co.il/stage/studio/category.asp?id=8 を表示した結果です.このページも,UTF-8 を指定して表示すれば正常に表示できます.



図21
変換に失敗した例
  • UTF-8 にすれば正常表示可能
  • これはヘブライ語のページ

リスト8
TIdHTTP の Get メソッドを使用した文字コードページの自動変換
//=============================================================================
//  [HTML GET]ボタンクリック
//
//  TIdTHHPコンポのGet関数は,引数がURLだけの場合,文字コードを自動判定する
//  全てのHTMLコードの文字コードページを判定できるわけではない
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AURL : String;
begin
  Screen.Cursor := crHourGlass;

  Memo1.Clear;
  Memo1.Update;
  Label1.Caption := '';

  //コンボボックスで選択中のURL文字列 
  AURL := Trim(ComboBox1.Text);
  try
    IdHTTP1.HandleRedirects := True;
    //URLのソースを取得
    Memo1.Lines.Text := IdHTTP1.Get(AURL);
    Label1.Caption := IdHTTP1.Response.CharSet;
  except
    on e: EIdHTTPProtocolException do begin
      Memo1.Lines.Add('取得に失敗しました.' + e.Message);
    end;
    on e: Exception do begin
      Memo1.Lines.Add('失敗しました.' + e.Message);
    end;
  end;

  Memo1.SetFocus;
  Screen.Cursor := crDefault;
end;




09_universalchardet を使用したコードページの自動判別


universalchardet (Universal Character Set Detector C Library) を使用したコードページの自動判別のテストです.universalchardet については A7M さんのサイトの記事を参考にしてください.

A7M さんが提供している DLL はランタイムライブラリが必要になっているので,ライタイムライブラリが必要ない DLL を作成して添付しています.DLL の作成は,A7M さんが提供している DLL のプロジェクトを C++Builder 10.2 Tokyo Stater でコンパイルしました.

ここまでのサンプルと同じく,指定ウェブページの HTML コードを取得して表示します.MLang を使用した時に取得できなかったギリシャ語のページも,UTF-8 として検出され,文字化けなしに HTML コードが取得できます.
HTTPS のウェブページの HTML コードも取得できるように,Indy の TIdSSLIOHandlerSocketOpenSSL コンポーネントを使用してみました.

A7M さんが提供している universalchardet.pas を利用しています.このユニットには,DLL 内の関数類を使用するための定義があります.



[備考]
本サンプルのテストで使用した Indy のバージョンは 10.6.2 です.



図22
設計時の画面
  • TIdSSLIOHandlerSocketOpenSSL コンポーネントを配置
図23
実行結果の例
  • これはヘブライ語のページ

リスト9
universalchardet を使用したコードページの自動判別
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdHTTP, IdIOHandler, IdIOHandlerSocket,
  IdIOHandlerStack, IdSSL, IdSSLOpenSSL;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    ComboBox1: TComboBox;
    IdHTTP1: TIdHTTP;
    SpeedButton1: TSpeedButton;
    Memo1: TMemo;
    Label1: TLabel;
    IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShellAPI,
  universalchardet;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  //テスト用のURLをセット
  with ComboBox1.Items do begin
    Clear;
    Add('http://mrxray.on.coocan.jp/index.htm');
    Add('http://www.videonews.com/');
    Add('http://www.yomiuri.co.jp/');
    Add('http://docwiki.embarcadero.com/RADStudio/ja/Main_Page');
    Add('http://www.upload.co.il/stage/studio/category.asp?id=8');
    Add('http://www.delphi.gr/');
    Add('http://ru.wikipedia.org/wiki/Delphi');
    Add('http://zh.wikipedia.org/wiki/Delphi');
    Add('http://ko.wikipedia.org/wiki/%EC%9D%BC%EB%B3%B8');
  end;
end;

//=============================================================================
//  universalchardetを使用したコードページの自動判別
//  コードページを自動検出してComboBox1のURLのHTMLソースを表示
//  処理はA7M氏が提供しているコードと同じ
//
//  EUC-JP(コードページ51932)を検出した場合,
//  20932(マイクロソフト版日本語EUCらしい)を使用しないと文字化けする
//  この処理はしていない.他のサンプルを参照
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPageURL   : string;
  LGetStream : TMemoryStream;
  Ldetect    : chardet_t;
  LEncName   : array[0..CHARDET_MAX_ENCODING_NAME] of AnsiChar;
  LEnc       : TEncoding;
begin
  Screen.Cursor := crHourGlass;
  //HTMLソースを格納するメモリストリーム
  LGetStream := TMemoryStream.Create;

  LGetStream.Clear;
  Memo1.Clear;
  Memo1.Update;
  Label1.Caption := '';

  //コンボボックスで選択中のURL文字列 
  LPageURL := Trim(ComboBox1.Text);

  try
    try
      IdHTTP1.HandleRedirects := True;
      //URLのソースをメモリストリームに取得
      IdHTTP1.Get(LPageURL, LGetStream);

      //universalchardet.dllでエンコーディングの判別
      Ldetect := nil;
      chardet_create(Ldetect);
      chardet_handle_data(Ldetect, LGetStream.Memory, LGetStream.Size);
      chardet_data_end(Ldetect);

      //エンコーディング名の取得
      chardet_get_charset(Ldetect, LEncName, CHARDET_MAX_ENCODING_NAME);
      chardet_destroy(Ldetect);
      Label1.Caption := string(LEncName);

      //エンコーディング名からTEncodigを取得
      try
        LEnc := TEncoding.GetEncoding(string(LEncName));
      except
        on EEncodingError do begin
          LEnc := TEncoding.Default;
        end;
      end;

      //オフセットをストリームの先頭に
      LGetStream.Position := 0;
      Memo1.Lines.LoadFromStream(LGetStream, LEnc);
    except
      on e: EIdHTTPProtocolException do begin
        Memo1.Lines.Add('取得に失敗しました.' + e.Message);
      end;
      on e: Exception do begin
        Memo1.Lines.Add('失敗しました.' + e.Message);
      end;
    end;
  finally
    LGetStream.Free;
  end;
  Memo1.SetFocus;
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  どんなサイトか確認するためにコンボボックスで選択したサイトを表示
//  ShellExecuteの使用にはusesにShellAPIが必要
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  LURL : string;
begin
  Screen.Cursor := crHourGlass;

  LURL := Trim(ComboBox1.Text);
  ShellExecute(Handle,'',PChar(LURL), '', nil, SW_SHOW);

  Screen.Cursor := crDefault;
end;

end.




10_日本語を含む URL の扱い


テキストの文字コードの扱い例として,ウェブページの HTML ソースコードの取得をテストしました.この時,URL に日本語が含まれる場合,URL の文字列をエンコードしないと,別の URL と解釈されてしまいます.
本テストは,このことを確認するためのサンプルです.
ウェブブラウザで表示する場合は,ブラウザが URL の文字列をエンコードしてくれます.ブラウザによっては,エンコードしたアドレスを表示するものもあります.


図24
設計時画面
図25
URL をエンコードしない場合
  • タイトルが [Wikipedia] のページとなっている
  • これは目的の記事のページではない
図26
URL をエンコードした場合
  • タイトルが [日本語] のページになっている
  • これが目的の記事のページ

リスト10
日本語を含む URL の扱い.エンコードが必要
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdHTTP;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    ComboBox1: TComboBox;
    IdHTTP1: TIdHTTP;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  IdURI;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  //テスト用のURLをセット
  with ComboBox1.Items do begin
    Clear;
    Add('http://ja.wikipedia.org/wiki/日本語');
  end;
  ComboBox1.ItemIndex := 0;
end;

//=============================================================================
//  TIdHTTPでウエブページのHTMLコードをダウンロード
//  URLの文字列をエンコードしない場合
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AURL      : String;
  GetStream : TMemoryStream;
begin
  Screen.Cursor := crHourGlass;
  //HTMLソースを格納するメモリストリーム
  GetStream := TMemoryStream.Create;

  GetStream.Clear;
  Memo1.Clear;
  Memo1.Update;

  //コンボボックスで選択中のURL文字列 
  AURL := Trim(ComboBox1.Text);
  try
    try
      IdHTTP1.HandleRedirects := True;
      IdHTTP1.Get(AURL, GetStream);

      GetStream.Position := 0;
      Memo1.Lines.LoadFromStream(GetStream, TEncoding.UTF8);
      //確認保存
      GetStream.SaveToFile('test.html');
    except
      Memo1.Lines.Add('失敗しました.');
    end;
  finally
    GetStream.Free;
  end;
  Memo1.SetFocus;
  Screen.Cursor := crDefault;
end;

//=============================================================================
//  TIdHTTPでウエブページのHTMLコードをダウンロード
//  URLの文字列をエンコードする場合
//  usesにIdURIが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  AURL      : String;
  GetStream : TMemoryStream;
begin
  Screen.Cursor := crHourGlass;
  //HTMLソースを格納するメモリストリーム
  GetStream := TMemoryStream.Create;

  GetStream.Clear;
  Memo1.Clear;
  Memo1.Update;

  //コンボボックスで選択中のURL文字列 
  AURL := Trim(ComboBox1.Text);
  try
    try
      //URLの文字列をエンコード
      AURL := TIdURI.URLEncode(AURL);

      IdHTTP1.HandleRedirects := True;
      IdHTTP1.Get(AURL, GetStream);

      GetStream.Position := 0;
      Memo1.Lines.LoadFromStream(GetStream, TEncoding.UTF8);
      //確認保存
      GetStream.SaveToFile('test_URLEncode.html');
    except
      Memo1.Lines.Add('失敗しました.');
    end;
  finally
    GetStream.Free;
  end;
  Memo1.SetFocus;
  Screen.Cursor := crDefault;
end;

end.




11_TFileStream, TextFile ( Readln ) を使用する方法


UTF-8N のテキストファイルを読込んで表示するテストです.
TFileStream と TextFile を使用した例です.比較用に TStringList を使用したコードと,エンコードしない場合も追加しています.

テキストを行単位で読み込むには,TStreamReader を使用すると便利です.TStreamReader を使用する例は,参考リンクの記事にあります.当サイトにも TStreamWriter, TStreamReader を使用したサンプルがあります.TStreamWriter, TStreamReader 共に.Delphi 2009 以降で使用可能です.



図27
設計時画面
図28
Button1 クリック
図29
Button2 クリック
図30
Button3 クリック
  • Delphi XE でコンパイルして実行した結果
  • Delphi XE2 以降でコンパイルした EXE では文字化けする
  • AssignFile の Delphi XE2 以降用のコードは Button4 クリックのコード
図31
Button5 クリック
  • エンコードしていないので文字化けして表示される

リスト11
TFileStream, TextFile を使用して,UTF-8N のテキストを読み込んで表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    OpenDialog1: TOpenDialog;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  UTF-8のテキストファイルを読込んで表示
//  TStringListを使用した方法
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStringList : TStringList;
  LText       : string;
begin
  Memo1.Clear;

  LStringList := TStringList.Create;
  try
    LStringList.LoadFromFile('000.txt', TEncoding.UTF8);
    LText := LStringList.Text;
  finally
    LStringList.Free;
  end;
  Memo1.Text := LText;
end;

//=============================================================================
//  UTF-8のテキストファイルを読込んで表示
//  TFileStreamを使用した方法
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LFileStream : TFileStream;
begin
  Memo1.Clear;

  LFileStream := TFileStream.Create('000.txt', fmOpenRead);
  try
    LFileStream.Position := 0;
    Memo1.Lines.LoadFromStream(LFileStream, TEncoding.UTF8);
  finally
    LFileStream.Free;
  end;
end;

//=============================================================================
//  UTF-8のテキストファイルを読込んで表示
//  どうしてもTextFile,Readlnが使いたい場合
//  Delphi 2009~Delphi XE以前専用
//  Delphi XE2以降では文字化けする
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LTextFile : TextFile;
  LUTF8Text : UTF8String;
  LResText  : String;
begin
  Memo1.Clear;

  Assignfile(LTextFile, '000.txt');
  Reset(LTextFile);
  while not Eof(LTextFile) do begin
    Readln(LTextFile, LUTF8Text);

    //UTF-8→UTF-16なのでロスレス変換なのだけれども、
    //単純に代入するとコンパイラがワーニングを吐くのでキャスト
    LResText := UnicodeString(LUTF8Text);
    Memo1.Lines.Add(LResText);
  end;
  CloseFile(LTextFile);
end;

//=============================================================================
//  UTF-8のテキストファイルを読込んで表示
//  どうしてもTextFile,Readlnが使いたい場合
//  Delphi XE2以降のAssignFileではエンコードの指定ができるので,その動作確認用
//  Delphi XE2以降専用
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  LTextFile : TextFile;
  LText     : string;
  LEnc      : TEncoding;
begin
  Memo1.Clear;

  {$IF CompilerVersion >= 23.0}
  //Assignfile(LTextFile, '000.txt', TEncoding.UTF8.CodePage);  //でも可
  //GetEncodingを使用した場合は解放が必要
  LEnc := TEncoding.GetEncoding(CP_UTF8);
  try
    Assignfile(LTextFile, '000.txt', LEnc.CodePage);
    Reset(LTextFile);
    while not Eof(LTextFile) do begin
      Readln(LTextFile, LText);
      Memo1.Lines.Add(LText);
    end;
  finally
    FreeAndNil(LEnc);
  end;
  CloseFile(LTextFile);
  {$IFEND}
end;

//=============================================================================
//  UTF-8のテキストファイルを読込んで表示
//  エンコードしない場合の動作確認用
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
var
  LStringList : TStringList;
  LResText    : String;
begin
  Memo1.Clear;

  LStringList := TStringList.Create;
  try
    LStringList.LoadFromFile('000.txt');
    LResText := LStringList.Text;
  finally
    LStringList.Free;
  end;
  Memo1.Text := LResText;
end;

end.




12_IOUtils.TFile を使用する方法


Delphi 2010 以降では,IOUtils.TFile が使用できます.
以下のコードはテキストファイルの読み込みだけですが,書き込み,保存の際もエンコードしてコードページの指定ができます.



[備考]
IOUtils.TFile でテキストを UTF-8 で保存する時は,BOM なしになります.


図32
実行結果
  • 添付の UTF-8N のテキストファイルを読み込んで表示

リスト12
IOUtils.TFile を使用したテキストファイルの読み込み
implementation

uses
  IOUtils;

{$R *.dfm}

//=============================================================================
//  UTF-8のテキストファイルを読込んで表示
//  IOUtils.TFileを使用する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  Memo1.Clear;

  LText := TFile.ReadAllText('000.txt', TEncoding.UTF8);
  Memo1.Text := LText;
end;




13_TextFile オブジェクトを使用したテキストの保存と読み出し


上のサンプルで TextFile オブジェクトを使用しました.
参考として TextFile オブジェクトだけを使用したテキストの保存と読み出しの簡単なサンプルを掲載しておきます.エンコードについては,上のサンプルを参考にしてください.AssignFile 関数でコードページの指定が可能なのは Delphi XE2 以降です.



図33
[Button1] クリックで保存されたテキストファイルの内容
図34
[Button1] クリックの後 [Button2] クリックで保存されたテキストファイルの内容
図35
保存したテキストを 1 行だけ読み出して表示
図36
保存したテキストを全て読み出して表示

リスト13
TextFile を使用したテキストの保存と読み出しのサンプル
//=============================================================================
//  TextFileオブジェクトの使用例
//
//  テキストファイルに新規にテキスト(文字列)を保存する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFilePath : string;
  LTextFile : TextFile;
begin
  Memo1.Clear;

  //テキストファイルのパス名
  LFilePath := ExtractFilePath(Application.ExeName) + '000.txt';

  //テキストファイルをTextFileオブジェクトにアサイン
  Assignfile(LTextFile, LFilePath);

  //TextFileを書き込み用として開く
  //テキストファイルが存在しない場合は新規に作成される
  //存在する場合はテキストファイルの内容はクリアされる
  Rewrite(LTextFile);

  Writeln(LTextFile, 'テスト文字列です');
  Writeln(LTextFile, '2 行目の文字列です');
  Writeln(LTextFile, '3 行目以降の文字列です' + sLineBreak + '改行コード付きです');

  //TextFileオブジェクトを閉じる
  CloseFile(LTextFile);
end;

//=============================================================================
//  TextFileオブジェクトの使用例
//
//  テキストファイルにテキスト(文字列)を追加保存する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LFilePath : string;
  LTextFile : TextFile;
begin
  Memo1.Clear;

  //テキストファイルのパス名
  LFilePath := ExtractFilePath(Application.ExeName) + '000.txt';

  //ファイル存在を確認
  if FileExists(LFilePath) then begin
    //テキストファイルをTextFileオブジェクトにアサイン
    Assignfile(LTextFile, LFilePath);

    //追加モードでTextFileを開く
    Append(LTextFile);
    //1行だけ追加する
    Writeln(LTextFile, '追加の文字列です');

    //TextFileオブジェクトを閉じる
    CloseFile(LTextFile);
  end;
end;

//=============================================================================
//  TextFileオブジェクトの使用例
//
//  テキストファイルからテキスト(文字列)を1行だけ読み出す
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LFilePath : string;
  LTextFile : TextFile;
  LResText  : String;
begin
  Memo1.Clear;

  //テキストファイルのパス名
  LFilePath := ExtractFilePath(Application.ExeName) + '000.txt';

  //ファイル存在を確認
  if FileExists(LFilePath) then begin
    //テキストファイルをTextFileオブジェクトにアサイン
    Assignfile(LTextFile, LFilePath);

    //TextFileを読み出し用として開いて先頭に移動
    Reset(LTextFile);

    //1行だけ読み出す
    Readln(LTextFile, LResText);
    Memo1.Lines.Add(LResText);

    //TextFileオブジェクトを閉じる
    CloseFile(LTextFile);
  end;
end;

//=============================================================================
//  TextFileオブジェクトの使用例
//
//  テキストファイルから全てのテキスト(文字列)を読み出す
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  LFilePath : string;
  LTextFile : TextFile;
  LResText  : String;
begin
  Memo1.Clear;

  //テキストファイルのパス名
  LFilePath := ExtractFilePath(Application.ExeName) + '000.txt';

  //ファイル存在を確認
  if FileExists(LFilePath) then begin
    //テキストファイルをTextFileオブジェクトにアサイン
    Assignfile(LTextFile, LFilePath);

    //TextFileを読み出し用として開いて先頭に移動
    Reset(LTextFile);

    //ファイルの最後まで1行づつ読み出す
    while not Eof(LTextFile) do begin
      Readln(LTextFile, LResText);
      Memo1.Lines.Add(LResText);
    end;

    //TextFileオブジェクトを閉じる
    CloseFile(LTextFile);
  end;
end;




14_その他のテキストの書き込みと読み込み用のクラス


以下のようなクラスが実装されていますが,コードページ指定の機能はありません.



Delphi 10 では JSON 関係のクラスも実装されています.