(12/11/01~)

12/11/06

文字列プロパティエディタ
 以前、delfusa 氏がラベルの改行についての記事をブログに書いていました。とある調査の副産物で文字列プロパティエディタを作る事になってしまったので公開しておきます...何番煎じなんでしょうね?でも、QC に入れられるくらいだから、それなりに需要はあるのでしょう。

 アーカイブを解凍し、PkgCustomPropertyEditor.dpk を Delphi で開いてインストールすれば使えるようになります。オブジェクトインスペクタで string あるいは TCaption のプロパティをダブルクリックするか、[…] ボタンを押下するとプロパティエディタが開きます。

 画像のように、TLabel の Caption を改行する事ができます。ソースは長くないので、個別に解説してみます。

PkgCustomPropertyEditor.dpk
 プロパティエディタ登録専用のパッケージのユニットです。他の (設計時) パッケージにインストールするのであれば、このファイルは不要です。

{*******************************************************}
{                                                       }
{      カスタムプロパティエディタ登録用パッケージ       }
{                                                       }
{*******************************************************}
package PkgCustomPropertyEditor;

{$R *.res}
{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
{$DEBUGINFO ON}
{$EXTENDEDSYNTAX ON}
{$IMPORTEDDATA ON}
{$IOCHECKS ON}
{$LOCALSYMBOLS ON}
{$LONGSTRINGS ON}
{$OPENSTRINGS ON}
{$OPTIMIZATION OFF}
{$OVERFLOWCHECKS OFF}
{$RANGECHECKS OFF}
{$REFERENCEINFO ON}
{$SAFEDIVIDE OFF}
{$STACKFRAMES ON}
{$TYPEDADDRESS OFF}
{$VARSTRINGCHECKS ON}
{$WRITEABLECONST OFF}
{$MINENUMSIZE 1}
{$IMAGEBASE $400000}
{$DEFINE DEBUG}
{$ENDIF IMPLICITBUILDING}
{$DESCRIPTION 'Custom Property Editor'}
{$DESIGNONLY}
{$IMPLICITBUILD ON}

requires
  rtl,
  vcl,
  designide,
  xmlrtl,
  vclactnband,
  vclx;

contains
  frmuStringPropertyEditor in 'frmuStringPropertyEditor.pas' {frmStringPropertyEditor},
  uRegCustomPropertyeditor in 'uRegCustomPropertyEditor.pas';

end.

 {$DESIGNONLY} コンパイル指令が記述されている事でも解りますが、プロパティエディタのパッケージは設計時パッケージとなります。知ってるヒトには何を今更ですが、Delphi 6 で行われた "実行時パッケージと設計時パッケージの分離" とは、誤解を恐れずに言えば 「"コンポーネント" と "プロパティエディタ / コンポーネントエディタ" は別パッケージにしなさい」 という事です。実行時ライブラリにプロパティエディタ / コンポーネントエディタのコードが含まれるとアプリケーションのサイズが大きくなるだけで、メリットは何もありませんからね。

uRegCustomPropertyEditor.pas
 プロパティエディタを登録するためのユニットです。

{*******************************************************}
{                                                       }
{        カスタムプロパティエディタ登録用ユニット       }
{                                                       }
{*******************************************************}
unit uRegCustomPropertyEditor;

interface

uses
  Controls,
  DesignIntf;

procedure register;

implementation

uses
  frmuStringPropertyEditor; // 文字列用プロパティエディタ

procedure register;
begin
  // string 型のプロパティ (すべて) に関連付け
  RegisterPropertyEditor(TypeInfo(string), nil'', TCustomizedStringProperty);

  // TCaption 型のプロパティ (すべて) に関連付け
  RegisterPropertyEditor(TypeInfo(TCaption), nil'', TCustomizedStringProperty);
end;
end.

 TCustomizedStringProperty というプロパティエディタを、すべての string プロパティと TCaption プロパティに割り当てています。他に割り当てたい "string と互換性のある型" があれば (TFileName とか?) 追記してください。

frmuStringPropertyEditor.dfm
 DFM のコードを貼り付けても面白くないと思うので、ここは画像を。

 ボタン 2 個とメモ一つ、レイアウト用にパネルが一つあるだけの、何の変哲もないフォームです。キャプション類はすべて空にしてあります。

frmuStringPropertyEditor.pas
 プロパティエディタ本体と、プロパティエディタ用ダイアログのソースコードです。

{*******************************************************}
{                                                       }
{       文字列用プロパティエディタ / ダイアログ         }
{                                                       }
{*******************************************************}
unit frmuStringPropertyEditor;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Controls, Forms,
  Consts, Dialogs, StdCtrls, ExtCtrls, DesignIntf, DesignEditors;

type
  {TfrmStringPropertyEditor}
  TfrmStringPropertyEditor = class(TForm)
    pnlBottom: TPanel;
    mmString: TMemo;
    btnOK: TButton;
    btnCancel: TButton;
    procedure FormCreate(Sender: TObject);
    procedure mmStringKeyPress(Sender: TObject; var Key: Char);
  public
    function ShowModal(PropName: string): Integer; reintroduceoverload;
  end;

  { TCustomizedStringProperty }
  TCustomizedStringProperty = class (TStringProperty)
    public
      procedure Edit; override;
      function GetAttributes: TPropertyAttributes; override;
  end;

implementation

{$R *.dfm}

{TfrmStringPropertyEditor}
// -----------------------------------------------------------------------------
procedure TfrmStringPropertyEditor.FormCreate(Sender: TObject);
// フォーム作成時
begin
  btnOK.Caption := SOKButton;
  btnCancel.Caption := SCancelButton;
  mmString.Font.Size := mmString.Font.Size + 1// 好みで調整してください
end;

procedure TfrmStringPropertyEditor.mmStringKeyPress(Sender: TObject;
  var Key: Char);
// TMemo で 〔Ctrl〕+〔A〕による全選択を可能にする
begin
  if Key = ^A then
    begin
      (Sender as TMemo).SelectAll;
      Key := #$00;
    end;
end;

function TfrmStringPropertyEditor.ShowModal(PropName: string): Integer;
// ShowModal オーバーロードメソッド
begin
  Caption := Format('%s: %s', [SPropertiesVerb, PropName]);
  result := inherited ShowModal;
end;

{ TCustomizedStringProperty }
// -----------------------------------------------------------------------------
procedure TCustomizedStringProperty.Edit;
// プロパティ編集用メソッド
var
  StringPropertyEditor: TfrmStringPropertyEditor;
begin
  inherited;
  StringPropertyEditor := TfrmStringPropertyEditor.Create(nil);
  try
    StringPropertyEditor.mmString.Text := Self.GetStrValue;
    if StringPropertyEditor.ShowModal(Self.GetName) = mrOK then
      SetStrValue(StringPropertyEditor.mmString.Text);
  finally
    StringPropertyEditor.Free;
  end;
end;

function TCustomizedStringProperty.GetAttributes: TPropertyAttributes;
// プロパティエディタ属性取得メソッド
begin
  result := [paDialog, paMultiSelect]; // ダイアログ形式プロパティエディタ, 複数選択対応
end;

end.

 これまた大した事は何もやってありませんね。プロパティエディタは TStringProperty から派生しています。入力欄 (TMemo) を〔Ctrl〕+〔A〕で全選択できるようにしてありますが、"^ はべき乗の演算子ではない" というトピックに出てきたコントロールコードが使われています。

 ダイアログのキャプションやボタンのキャプションは文字列定数 (Consts.pas) から持ってきていますので、Delphi 2010 以降ならライブラリの言語を切り替えれば英語版が作れたりします。ソースコードのコメントは日本語ですが、ソースファイルは UTF-8 なのでガイジンサンでもそのままインストールできます。


12/11/07

Delphi で & は "ビット AND" の演算子ではないけれど?
 Delphi での & は "ビット AND" の演算子ではありません。「そもそも Delphi で & なんて使ったっけ?」 と思われるかもしれませんが、2 つの使用法があります。

 一つはインラインアセンブラの識別子のオーバーライド演算子としてです。

var
  Ch: Char;
begin  
  ...
  asm
    MOV     &CH,1
  end;
  ...
end;

 この場合の &CH は Char 型変数の CH を指します。

 もう一つは、拡張識別子 (Delphi 2005 以降で利用可能) としてです。例えば次のようなレコードがあったとすると、

type
  // TMyRecord
  TMyRecord = record
    Code: Integer;
    Type: Integer;
    Name: String;
  end;

 このままでは Type が予約語とカブっているのでエラーになりますが、次のようにするとこの問題を回避できます。

type
  // TMyRecord
  TMyRecord = record
    Code: Integer;
    &Type: Integer;
    Name: String;
  end;

 他言語のクラスの互換クラスを作ろうとした時や、構造体を作ろうとした時にメンバの名前が Delphi の予約語とカブってしまった時に使えます...まぁ、そんな時は大抵メンバの名前を変えるので拡張識別子のお世話になることはあまりないのですが。

 実用性は皆無に等しいですが、予約語とカブっていなくても拡張識別子を使える事を利用して、次のようなキモいコードを書く事もできます。

var
  &i: Integer;
  &s: string;
begin
  &s := '';
  for &i:=0 to 9 do
    &s := &s + IntToStr(&i);
  ShowMessage(&s);
end;

 それと、面白いことに演算子のオーバーロードで使われる In 演算子の定義部には & が必要ありません (IDE のコードエディタはたまに & を付けようとしますが)。 汎用ビットフィールド操作レコードの定義部を確認してみてください。


12/11/08

Delphi で { } は (* *) で置き換えられるけど?
 Delphi のブロックコメントは { } という特殊シンボルで記述しますが、(* *) で置き換える事もできます。

var
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
{
    SL.Add('ABC');
    SL.Add('DEF');
}
    SL.Add('GHI');
    SL.Add('JKL');
    ShowMessage(SL([0]));
  finally
    SL.Free;
  end;
end;

 こんな感じですね。

var
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
(*
    SL.Add('ABC');
    SL.Add('DEF');
*)
    SL.Add('GHI');
    SL.Add('JKL');
    ShowMessage(SL([0]));
  finally
    SL.Free;
  end;
end;

 { } は (* *) と (特殊シンボルとしては) 完全代替可能なので (*$IFDEF DEBUG*) なんてのも可能です。では次のコードはどうでしょう?

var
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
    SL.Add('ABC');
    SL.Add('DEF');
    SL.Add('GHI');
    SL.Add('JKL');
    ShowMessage(SL(.0.));
  finally
    SL.Free;
  end;
end;

 Delphi の [ ] は (. .) で完全代替可能です。(.0.) は [0] と同じなのですが、なんとなくカワイイですね。なお、コードエディタはこの特殊シンボルの代替の件を完全に忘れているので赤波線を引いてくれます (w


12/11/17

System.Zip.TZipFile を改変して Shift_JIS な Zip ファイルを扱えるようにする (XE2)
 経緯は Delphi Q&A の "ZipFileによるZip圧縮時の日本語ファイル名文字化け対処方法" を参照してください。僕の個人的な言い分は既に書いてありますのでこちらではあえて説明しません。

 Shift_JIS な Zip ファイルを扱うためには、継承クラスやクラスヘルパで対処する等の簡単な措置では無理です。現状、(恐らく) System.Zip を書き換えるしかないのですが、直接書き換えるのはオススメしません。

 それと、単に Shift_JIS な ZIP を扱うだけであれば、

 type                                    
   TOem437String = type AnsiString(932); // MOD

 TOem437String の定義を書き換え、ハードコードされている数値定数 437 を 932 に置換するだけでいいのですが、これでは根本的な解決には至りません (他のコードページの ZIP に対処できないので)。そこで現実的な実装を考えてみました。以下に System.Zip を改変したユニット System.ZipEx の作成方法を記します。

  1. System.Zip.pas を適当なプロジェクトフォルダへコピー
  2. コピーした System.Zip.pas を System.ZipEx.pas にリネーム
  3. System.ZipEx.pas をエディタで開く
  4. ユニット名を変更します。
    // unit System.Zip;
    unit System.ZipEx; // MOD
  5. オリジナルのクラスと混同しないようにするため、ソースコード中の TZipFile を TZipFileEx でリファクタリングで全置換 (この作業は必須ではありません)。
  6. 宣言部に定数 ZIP_NATIVE_CODEPAGE を追加
      ...
    uses
      System.SysUtils,
      System.IOUtils,
      System.Generics.Collections,
      System.Classes;
    
    const                        // ADD
      ZIP_NATIVE_CODEPAGE = 437// ADD
    
    type
      /// <summary> Zip Compression Method Enumeration </summary>
      TZipCompression = (
      ...
  7. TZipFileEx の Private フィールドに FCodePage を追加
      TZipFileEx = class
      ...
      private            
        FCodePage: Word; // ADD
      ...
  8. ハードコードされた 437 という数値定数を FCodePage で置換。ReadCentralHeader() メソッドと Read() メソッドの実装部にあります。
    procedure TZipFile.ReadCentralHeader;
    begin
      ...
        if LHeader.FileNameLength > 0 then
        begin
          ...
    //      SetCodepage(LHeader.FileName, 437, False);
            SetCodepage(LHeader.FileName, FCodepage, False); // MOD
          ...
        end;
        if LHeader.ExtraFieldLength > 0 then
        begin
          ...
        end;
        if LHeader.FileCommentLength > 0 then
        begin
          ...
    //      SetCodepage(LHeader.FileName, 437, False);
            SetCodepage(LHeader.FileName, FCodepage, False); // MOD
          ...
        end;
      ...
    end;

    procedure TZipFile.Read(Index: Integer; out Stream: TStream; out LocalHeader: TZipHeader);
    begin
      ...
    //  SetCodepage(LocalHeader.FileName, 437, False);
        SetCodepage(LocalHeader.FileName, FCodepage, False); // MOD
      ...
    end;
  9. 実装部の TOem437String をコメントアウト
    // type                                    // DEL
    //   TOem437String = type AnsiString(437); // DEL
  10. TZipFileEx.SetFileComment メソッドの実装部を書き換え
    procedure TZipFileEx.SetFileComment(Index: Integer; Value: string);
    begin
      ...
    {
      if UTF8Support then
        LFile.FileComment := UTF8Encode(Value)
      else
        LFile.FileComment := TOem437String(Value);
    }
      // MOD From Here
      LFile.FileComment := UTF8Encode(Value);
      if not UTF8Support then
        SetCodepage(LFile.FileComment, FCodePage);
      // MOD Until Here 
      ...
    end;
  11. TZipFileEx.Add メソッドの実装部を書き換え (オーバーロードされた 4 つの Add メソッドがあります。引数で判断して下さい)
    procedure TZipFileEx.Add(FileName: string; ArchiveFileName: string;
      Compression: TZipCompression);
    begin
      ...
    {
        if FUTF8Support then
        begin
          LHeader.Flag := LHeader.Flag or (1 SHL 11); // Language encoding flag, UTF8
          LHeader.FileName := UTF8Encode(ArchiveFileName);
        end
        else
          LHeader.FileName := TOem437String(ArchiveFileName);
    }
        // MOD From Here
        LHeader.FileName := UTF8Encode(ArchiveFileName);
        if FUTF8Support then
          LHeader.Flag := LHeader.Flag or (1 SHL 11)  // Language encoding flag, UTF8
        else
          SetCodePage(LHeader.FileName, FCodePage);
        // MOD Until Here 
      ...
    end;
  12. オーバーロードされた、もう一つの TZipFileEx.Add メソッドの実装部を書き換え (オーバーロードされた 4 つの Add メソッドがあります。引数で判断して下さい)
    procedure TZipFileEx.Add(Data: TStream; ArchiveFileName: string;
      Compression: TZipCompression);
    begin
      ...
    {
      if FUTF8Support then
      begin
        LHeader.Flag := LHeader.Flag or (1 SHL 11); // Language encoding flag, UTF8
        LHeader.FileName := UTF8Encode(ArchiveFileName);
      end
      else
        LHeader.FileName := TOem437String(ArchiveFileName);
    }
      // MOD From Here
      LHeader.FileName := UTF8Encode(ArchiveFileName);
      if FUTF8Support then
        LHeader.Flag := LHeader.Flag or (1 SHL 11)  // Language encoding flag, UTF8
      else
        SetCodepage(LHeader.FileName, FCodePage);
      // MOD Until Here
      ...
    end;
  13. TZipFileEx.Open メソッドの定義部と実装部を書き換え
    //  procedure Open(ZipFileName: string; OpenMode: TZipMode); overload;
        procedure Open(ZipFileName: string; OpenMode: TZipMode;                       // MOD
          IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE); overload;    // MOD

    // procedure TZipFileEx.Open(ZipFileName: string; OpenMode: TZipMode);
    procedure TZipFileEx.Open(ZipFileName: string; OpenMode: TZipMode; // MOD
      IsUTF8: Boolean; CodePage: word);                                // MOD
    begin
      ...
    //  Open(LFileStream, OpenMode);
        Open(LFileStream, OpenMode, IsUTF8, CodePage); // MOD
      ...  
    end;
  14. オーバーロードされた、もう一つの TZipFileEx.Open メソッドの定義部と実装部を書き換え
    //  procedure Open(ZipFileStream: TStream; OpenMode: TZipMode); overload;
        procedure Open(ZipFileStream: TStream; OpenMode: TZipMode;                 // MOD
          IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE); overload// MOD

    // procedure TZipFileEx.Open(ZipFileStream: TStream; OpenMode: TZipMode);
    procedure TZipFileEx.Open(ZipFileStream: TStream; OpenMode: TZipMode; IsUTF8: Boolean; CodePage: word); // MOD
    begin
      ...
      if OpenMode in [zmRead, zmReadWrite] then
      try
        // Read the Central Header to verify it's a valid zipfile
        FCodePage := CodePage; // ADD
        ReadCentralHeader;
      except
        // If it's an invalid zipfile, cleanup
        FStream := nil;
        raise;
      end;
      FMode := OpenMode;
    
      // ADD From Here
      if OpenMode in [zmWrite, zmReadWrite] then
        begin
          UTF8Support := IsUTF8;
          FCodePage := CodePage;
        end;
      // ADD Until Here
    end;
  15. クラスメソッド TZipFileEx.ExtractZipFile の定義部と実装部を書き換え
    //  class procedure ExtractZipFile(ZipFileName: string; Path: string); static;
        class procedure ExtractZipFile(ZipFileName: string; Path: string;         // MOD
           IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE); static// MOD

    // class procedure TZipFileEx.ExtractZipFile(ZipFileName: string; Path: string);
    class procedure TZipFileEx.ExtractZipFile(ZipFileName: string; Path: string// MOD
      IsUTF8: Boolean; CodePage: word);                                          // MOD
    var
      LZip: TZipFileEx;
    begin
      LZip := TZipFileEx.Create;
      try
    //  LZip.Open(ZipFileName, zmRead);
        LZip.Open(ZipFileName, zmRead, IsUTF8, CodePage); // MOD
        LZip.ExtractAll(Path);
        LZip.Close;
      finally
        LZip.Free;
      end;
    end;
  16. クラスメソッド TZipFileEx.ZipDirectoryContents の定義部と実装部を書き換え
    //  class procedure ZipDirectoryContents(ZipFileName: string; Path: string;
    //    Compression: TZipCompression = zcDeflate); static;
        class procedure ZipDirectoryContents(ZipFileName: string; Path: string// MOD
           IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE;        // MOD
           Compression: TZipCompression = zcDeflate); static;                   // MOD

    // class procedure TZipFileEx.ZipDirectoryContents(ZipFileName: string; Path: string;
    //   Compression: TZipCompression);
    class procedure TZipFileEx.ZipDirectoryContents(ZipFileName: string; Path: string// MOD
      IsUTF8: Boolean; CodePage: word; Compression: TZipCompression);                  // MOD
    begin
      ...
    //  LZipFile.Open(ZipFileName, zmWrite);
        LZipFile.Open(ZipFileName, zmWrite, IsUTF8, CodePage); // MOD
      ...
    end;
  17. コンストラクタの実装部にフィールド初期化を追加
    constructor TZipFileEx.Create;
    begin
      ...
      FUTF8Support := True;
      FCodePage := ZIP_NATIVE_CODEPAGE; // ADD
    end;
  18. TZipFileEx.ReadCentralHeader メソッドの実装部を書き換え - QC#104695 関連バグ修正 (2)
    procedure TZipFileEx.ReadCentralHeader;
    begin
      ...
        if LHeader.FileCommentLength > 0 then
        begin
          SetLength(LHeader.FileComment, LHeader.FileCommentLength);
          if (LHeader.Flag and (1 SHL 11)) <> 0 then
    //      SetCodepage(LHeader.FileName, 65001, False)
            SetCodepage(LHeader.FileComment, 65001, False) // MOD
          else
    //      SetCodepage(LHeader.FileName, 437, False);
            SetCodepage(LHeader.FileComment, FCodepage, False); // MOD
          VerifyRead(FStream, LHeader.FileComment[1], LHeader.FileCommentLength);
        end;
      ...  
    end;
  19. 引数が (Index, Path, CreateSubdirs) な TZipFileEx.Extract メソッドの実装部を書き換え (if 文の begin end が追加されています) - QC#104695 関連バグ修正 (3)
    procedure TZipFileEx.Extract(Index: Integer; Path: string; CreateSubdirs: Boolean);
    begin
      ...
          if (LHeader.Flag and (1 SHL 3)) = 0 then
    {
            if FFiles[Index].UncompressedSize > 0 then // Special case for empty files.
              LOutStream.CopyFrom(LInStream, FFiles[Index].UncompressedSize)
    }
            begin                                                                         // MOD
              if FFiles[Index].UncompressedSize > 0 then // Special case for empty files. // MOD
                LOutStream.CopyFrom(LInStream, FFiles[Index].UncompressedSize)            // MOD
            end                                                                          // MOD
            else
            begin
              //CRC, Uncompressed, and Compressed Size follow the compressed data.
      ...
    end;
 上記修正方法は XE2 用なので XE3 では多少異なるかもしれません...ですが、やる事は同じです。

System.ZipEx.TZipFileEx の使い方
 使い方と言っても従来と同じ書き方なら TZipFile と同じ動作になるので悩む事はないと思います。Shift_JIS なファイル / フォルダの ZIP を扱う場合など、TZipFile と異なる動作をさせたい場合にはメソッドに追加された引数に値を設定します。例えば Tips にあるユニットと同等のものは次のようになります。

unit ZipFiles;

interface

uses
  System.SysUtils, System.ZipEx;

  procedure CompressZipFile(const aFileName, aSrcDir: TFileName;
    IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE);
  procedure DecompressZipFile(const aFileName, aDstDir: TFileName;
    IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE);

implementation

procedure CompressZipFile(const aFileName, aSrcDir: TFileName;
  IsUTF8: Boolean; CodePage: word);
begin
  TZipFileEx.ZipDirectoryContents(aFileName, IncludeTrailingPathDelimiter(aSrcDir), IsUTF8, CodePage);
end;

procedure DecompressZipFile(const aFileName, aDstDir: TFileName;
  IsUTF8: Boolean; CodePage: word);
begin
  TZipFileEx.ExtractZipFile(aFileName, IncludeTrailingPathDelimiter(aDstDir), IsUTF8, CodePage);
end;

end.

 UTF-8 フォルダ / ファイル名を使う場合には従来と同じ記述になります。

procedure TForm1.Button1Click(Sender: TObject);
begin
  CompressZipFile('C:\test_UTF8.zip''C:\TEST');
end;

 規格通りな CodePage 437 のフォルダ / ファイル名を使う場合には第 3 引数を False にします。

procedure TForm1.Button1Click(Sender: TObject);
begin
  CompressZipFile('C:\test_ANSI.zip''C:\TEST', False); // Codepage 437
end;

 Shift_JIS なフォルダ / ファイル名を使う場合には第 3 引数を False にし、第 4 引数にコードページとして 932 を渡します。

procedure TForm1.Button1Click(Sender: TObject);
begin
  CompressZipFile('C:\test_SJIS.zip''C:\TEST', False, 932);
end;

 第 3 引数が False で、第 4 引数に何も指定しなかった場合には (PK)ZIP の仕様通り Codepage 437 なフォルダ / ファイル名になります。つまり、第 4 引数を指定した場合には規格外の ZIP ファイルとなる訳です。また、第 3 引数が True の場合、第 4 引数のコードページ指定は無視されます。

 TZipFileEx をインスタンス化して使う場合には、Open() メソッドで UTF-8 形式か否かと、コードページを指定できます。指定しなければ従来通りです。これにより、UTF8Support プロパティを単独で使う機会がほぼなくなったと思われますが、互換性のために残してあります。

 ZIP は ファイルフォーマットとして "UTF-8 か?それ以外 (Codepage 437) か?" というフラグしか持っていないため、IsUTF8 でそれを指定し、規格外の ZIP を扱う場合には CodePage でコードページを指定するような実装になっています。CodePage だけにして、指定されなかったら (65001 だったら) UTF-8、それ以外だったら指定したコードページという実装でもよかったのですが、これだと CodePage 437 の意味がなくなってしまう (規格のハズの CodePage 437 をわざわざ指定しなくてはならない) ので現状の実装になっています。

 System.ZipEx は System.Zip を丸パクリに近い状態で改変しますので、「改変がメンドイので出来上がった System.ZipEx.pas くれ!」 と仰られても差し上げる事はできません...ご了承ください。

System.Zip.TZipFile 関連情報
 関連情報を載せておきます。

 (派生クラスやクラスヘルパで対応できないという意味で) 修正が容易ではないのと、(パクリですから) 修正ファイルを配布できないため、個人的には QC の Vote をお願いしたいトコロです。


12/11/28

System.Zip.TZipFile を改変して Shift_JIS な Zip ファイルを扱えるようにする (XE3)
 仕事とデブキャン資料作りで手一杯でこちらにまで手が回らなかったのですが、一息ついたのでやってみました。

 XE3 の場合も単に Shift_JIS な ZIP を扱うだけであれば、ハードコードされている数値定数 437 を 932 に置換するだけでいいです。ただ、前回も書いたようにこれでは根本的な解決には至りません。以下に System.Zip を改変したユニット System.ZipEx の作成方法 (XE3 用) を記します。

  1. System.Zip.pas を適当なプロジェクトフォルダへコピー
  2. コピーした System.Zip.pas を System.ZipEx.pas にリネーム
  3. System.ZipEx.pas をエディタで開く
  4. ユニット名を変更します。
    // unit System.Zip;
    unit System.ZipEx; // MOD
  5. オリジナルのクラスと混同しないようにするため、ソースコード中の TZipFile を TZipFileEx でリファクタリングで全置換 (この作業は必須ではありません)。
  6. 宣言部に定数 ZIP_NATIVE_CODEPAGE を追加
      ...
    uses
      System.SysUtils,
      System.IOUtils,
      System.Generics.Collections,
      System.Classes;
    
    const                        // ADD
      ZIP_NATIVE_CODEPAGE = 437// ADD
    
    type
      /// <summary> Zip Compression Method Enumeration </summary>
      TZipCompression = (
      ...
  7. TZipFileEx の Private フィールドに FCodePage を追加
      TZipFileEx = class
      ...
      private            
        FCodePage: Word; // ADD
      ...
  8. ハードコードされた 437 という数値定数を FCodePage で置換。TBytesToString() メソッドと StringToTBytes() メソッドの実装部にあります。
    function TZipFile.TBytesToString(B: TBytes) : string;
    begin
      if FUTF8Support then
        E := TEncoding.GetEncoding(65001)
      else
    //  E := TEncoding.GetEncoding(437);
        E := TEncoding.GetEncoding(FCodePage); // MOD
        ...
    end;

    function TZipFile.StringToTBytes(S: string): TBytes;
    var
      E: TEncoding;
    begin
      if FUTF8Support then
        E := TEncoding.GetEncoding(65001)
      else
    //  E := TEncoding.GetEncoding(437);
        E := TEncoding.GetEncoding(FCodepage); // MOD
      ...  
    end;
  9. TZipFileEx.Open メソッドの定義部と実装部を書き換え
    //  procedure Open(ZipFileName: string; OpenMode: TZipMode); overload;
        procedure Open(ZipFileName: string; OpenMode: TZipMode;                       // MOD
          IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE); overload;    // MOD

    // procedure TZipFileEx.Open(ZipFileName: string; OpenMode: TZipMode);
    procedure TZipFileEx.Open(ZipFileName: string; OpenMode: TZipMode; // MOD
      IsUTF8: Boolean; CodePage: word);                                // MOD
    begin
      ...
    //  Open(LFileStream, OpenMode);
        Open(LFileStream, OpenMode, IsUTF8, CodePage); // MOD
      ...  
    end;
  10. オーバーロードされた、もう一つの TZipFileEx.Open メソッドの定義部と実装部を書き換え
    //  procedure Open(ZipFileStream: TStream; OpenMode: TZipMode); overload;
        procedure Open(ZipFileStream: TStream; OpenMode: TZipMode;                 // MOD
          IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE); overload// MOD

    // procedure TZipFileEx.Open(ZipFileStream: TStream; OpenMode: TZipMode);
    procedure TZipFileEx.Open(ZipFileStream: TStream; OpenMode: TZipMode; IsUTF8: Boolean; CodePage: word); // MOD
    begin
      ...
      if OpenMode in [zmRead, zmReadWrite] then
      try
        // Read the Central Header to verify it's a valid zipfile
        FCodePage := CodePage; // ADD
        ReadCentralHeader;
      except
        // If it's an invalid zipfile, cleanup
        FStream := nil;
        raise;
      end;
      FMode := OpenMode;
    
      // ADD From Here
      if OpenMode in [zmWrite, zmReadWrite] then
        begin
          UTF8Support := IsUTF8;
          FCodePage := CodePage;
        end;
      // ADD Until Here
    end;
  11. クラスメソッド TZipFileEx.ExtractZipFile の定義部と実装部を書き換え
    //  class procedure ExtractZipFile(ZipFileName: string; Path: string); static;
        class procedure ExtractZipFile(ZipFileName: string; Path: string;         // MOD
           IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE); static// MOD

    // class procedure TZipFileEx.ExtractZipFile(ZipFileName: string; Path: string);
    class procedure TZipFileEx.ExtractZipFile(ZipFileName: string; Path: string// MOD
      IsUTF8: Boolean; CodePage: word);                                          // MOD
    var
      LZip: TZipFileEx;
    begin
      LZip := TZipFileEx.Create;
      try
    //  LZip.Open(ZipFileName, zmRead);
        LZip.Open(ZipFileName, zmRead, IsUTF8, CodePage); // MOD
        LZip.ExtractAll(Path);
        LZip.Close;
      finally
        LZip.Free;
      end;
    end;
  12. クラスメソッド TZipFileEx.ZipDirectoryContents の定義部と実装部を書き換え
    //  class procedure ZipDirectoryContents(ZipFileName: string; Path: string;
    //    Compression: TZipCompression = zcDeflate); static;
        class procedure ZipDirectoryContents(ZipFileName: string; Path: string// MOD
           IsUTF8: Boolean = True; CodePage: word = ZIP_NATIVE_CODEPAGE;        // MOD
           Compression: TZipCompression = zcDeflate); static;                   // MOD

    // class procedure TZipFileEx.ZipDirectoryContents(ZipFileName: string; Path: string;
    //   Compression: TZipCompression);
    class procedure TZipFileEx.ZipDirectoryContents(ZipFileName: string; Path: string// MOD
      IsUTF8: Boolean; CodePage: word; Compression: TZipCompression);                  // MOD
    begin
      ...
    //  LZipFile.Open(ZipFileName, zmWrite);
        LZipFile.Open(ZipFileName, zmWrite, IsUTF8, CodePage); // MOD
      ...
    end;
  13. コンストラクタの実装部にフィールド初期化を追加
    constructor TZipFileEx.Create;
    begin
      ...
      FUTF8Support := True;
      FCodePage := ZIP_NATIVE_CODEPAGE; // ADD
    end;

 頑なに Codepage 437 がハードコードされていますが、修正箇所は減っています。QC#104695 の (2) と (3) の件は XE3 で修正されていますね。TZipFileEx の使い方は先日の記事と全く同じです。

 派生クラスやクラスヘルパで対応できない構造だと、このようにバージョン別にパッチしなくてはならないので困るのですよねぇ...ソースコードが付属しない Starter 版では打つ手がないし。XE3 も XE2 も持っているのなら、XE3 の System.Zip.pas を書き換えて XE2 へ (バックアップは取った上で) 書き戻すと手間が少なくて済みます。


 BACK   古いのを読む   新しいのを読む