【新機能】組み込みの機能でファイルの圧縮と展開をしてみた

圧縮と展開

お久しぶりです。 エイタです。

2019 R3 日本語版がリリースされてしばらく経ちましたが、遅ればせながら今回はこのバージョンで追加された新機能についてご紹介します。

今回ピックアップするのはファイルの圧縮/展開をするための CompressorObjectExtractorObject という 2 つのオブジェクト。 これを実際に使用しながら、どんな機能なのかお伝えできればと思ってます。

私も初めて使うのでドキドキ・・・。 それでは一緒に見ていきましょう!


CompressorObject オブジェクトの概要

まずはファイルやフォルダーを圧縮するための CompressorObject オブジェクト。 対応しているファイル形式は ZIP、7Zip、gzip、そして無圧縮のアーカイブ形式である tar の 4 種類ですね。

また、複数ファイルの圧縮やパスワード (AES-256) の指定にも対応しているので一般的な利用としては必要十分な機能が備わっているんじゃないでしょうか?

マニュアルを見てみるとプロパティ Level で圧縮レベルの指定もできるようです。 レベルは下記の CompressionLevel カタログデータ型で指定するみたいですね。

CompressionLevel カタログデータ型

CompressionLevelStore!無圧縮
CompressionLevelFastest! デフォルトは Normal。 Fastest → Best の順で「高速/低圧縮」→「低速/高圧縮」だが、圧縮方式によってそれぞれ。
CompressionLevelFast!
CompressionLevelNormal!
CompressionLevelMaximum!
CompressionLevelBest!

このオブジェクト固有の関数は Comporess と Cancel のみ。 ちなみに Cancel は非同期インターフェイス用とのことなので、凝った作りをしない限りは必要ないかな? 時間があったら使ってみたいと思います。

さて、CompressorObject のメインである圧縮をおこなう Compress 関数の構文です。

構文 1 : 単一ファイル/フォルダーの圧縮

objectname.Compress ( string source, string dest {, ArchiveFormat format })

構文 2 : 複数ファイル/フォルダーの圧縮

objectname.Compress ( string source[], string dest {, ArchiveFormat format })

構文 3 : Blob 型を圧縮して Blob 型に

objectname.Compress ( blob source, ref blob dest {, ArchiveFormat format })

1 つめの引数 source が圧縮の対象となるファイルやフォルダー、2 つめの引数 dest が作成される圧縮ファイルのファイル名ですね。

単一もしくは複数のファイル/フォルダーの圧縮に加え、実ファイルではなく Blob 型から Blob 型への圧縮もできるようです。 データベースへの登録など色々なシーンで使えそうですね! なお、gzip に関しては単一ファイルのみに対応する圧縮方式なのでフォルダーや複数ファイルの指定はできません。

指定できる圧縮方式は冒頭でご紹介した通り ZIP、7Zip、gzip、tar の 4 種類で、2 つめのオプションの引数 format で指定します。 下記の ArchiveFormat カタログデータ型が利用できます。

ArchiveFormat カタログデータ型

ArchiveFormatZIP!ZIP (デフォルト)
ArchiveFormat7Zip!7Zip
ArchiveFormatGZip!gzip
ArchiveFormatTAR!tar

では、さっそく使ってみましょう!


ファイル圧縮の実装

例として、フォルダーも含めた複数のファイルを ZIP に圧縮してみます。 圧縮する対象はこんな感じ。

圧縮処理の対象
対象のプロパティ

temp フォルダーの中に 3 つのテキストファイルと sample-folder1 フォルダー。 sample-folder1 フォルダーの中には、さらに 2 つのテキストファイルと sample-folder2 フォルダーがあり、その中に 1 つのテキストファイルを配置しています。

下記が実装例です。 直下にある 2 つのテキストファイル sample1.txt と sample2.txt、それから sample-folder1 フォルダーを圧縮の対象に指定しています。

CompressorObject lnv_compress
string ls_files[], ls_target
integer li_res

// CompressorObject のインスタンスを作成
lnv_compress = create CompressorObject

// 圧縮設定
lnv_compress.level = CompressionLevelNormal! //圧縮レベル: 標準
lnv_compress.password = "password1234" //パスワード

// 圧縮する対象ファイル (複数) を指定
ls_files[1] = "C:\temp\sample1.txt" //テキストファイル
ls_files[2] = "C:\temp\sample2.txt" //テキストファイル
ls_files[3] = "C:\temp\sample-folder1" //フォルダー 

// 出力ファイル名を設定
ls_target = "C:\temp\sample_comp.zip"

// ZIP ファイルとして出力
li_res = lnv_compress.Compress(ls_files, ls_target, ArchiveFormatZIP!)

// 結果表示
IF li_res = 1 THEN
    MessageBox("結果", "圧縮に成功しました。")
ELSE
    MessageBox("エラー", "圧縮に失敗しました。~nエラーコード: " + String(li_res))
END IF

ここで注意しなければならないのが、圧縮の対象となるファイル名と出力されるファイル名はフルパスで指定しなければいけないということ。 ファイル名のみでは -6 (対象ファイルやフォルダーが存在しない) とか -10 (出力先のフォルダーが存在しない)、-12 (出力先のファイル名が不正) といったエラーが発生します。 私も最初は相対パスで指定していたのでエラーとなり「ファイルもフォルダーも存在するのに・・・?」と、ちょっと戸惑ってしまいました。

それでは改めてフルパスを指定し、圧縮!

・・・と意気込んではみたものの、あっさり完了してしまいました。

圧縮したファイルのプロパティ

できあがりはこちら。

パスの指定以外は特に引っ掛かるところもなく実装できました。 そういえば圧縮レベルの指定もできるんでしたね。 最も圧縮率の低い CompressionLevelFastest! と圧縮率の高い CompressionLevelBest! を Level プロパティに指定して試してみよう!

たとえば CompressionLevelFastest! に変更する場合はこうですね・・・。

// 圧縮設定
lnv_compress.level = CompressionLevelFastest! //圧縮レベル: 最速

・・・

// 出力ファイル名を設定
ls_target = "C:\temp\sample_comp_fastest.zip"

できたできた。 CompressionLevelFastest! を指定したものが sample_comp_fastest.zip (左)、CompressionLevelBest! を指定したものが sample_comp_best.zip (右) です。 どれくらいサイズの差が出たのかな・・・?

ファイルサイズが変わらない?

なん・・・だと・・・?

どちらもサイズが同じ? 1 byte も変わっていやしねぇ・・・。

なにか失敗した? マニュアルの記載を見落とした?

確かにマニュアルを見ると、ZIP 形式の “Normal, Maximum and Best” の説明には “Same as the default Normal level in 7zip” (7zip のデフォルト設定の Normal レベルと同じ) と書かれているので、まぁ Nomal と Best が同じというのは理解できますが、Fastest と Best も同じというのは・・・。

気になったのでいろいろ調べた結果・・・、

 

 

わかりませんでした!

 

 

すみません。急ぎ Appeon に詳細を確認します。 わかり次第このブログに追記させていただきます・・・。


ExtractorObject オブジェクトの概要

お次は圧縮されたファイルを展開するための ExtractorObject オブジェクトです。 対応しているファイル形式は ComporessorObject で利用可能な 4 種類に加えて、RAR、LZMA、LZMA86 にも対応しています。 展開の際に使用するパスワードは ComporessorObject と同様にプロパティ password で指定可能です。

圧縮ファイルの展開は Extract 関数ですね。 構文は次の通り。

構文 1 : 単一ファイルの展開

objectname.Extract ( string source, string target )

構文 2 : 圧縮ファイルから指定したファイル/フォルダーを抽出 (複数指定可)

objectname.Extract ( string source, string items[], string target )

構文 3 : 圧縮ファイルから指定したファイルを抽出し Blob 型の変数に格納

objectname.Extract ( string source, string item, ref blob target )

構文 4 : 圧縮された Blob 型のデータを展開し Blob 型の変数に格納

objectname.Extract ( blob source, ref blob target {, ArchiveFormat format })

基本は圧縮ファイルのフルパスを引数 source で指定し、target で指定したフォルダーもしくは Blob 型の変数に格納するという感じです。

構文 2 を使えば圧縮ファイルから個々のファイルやフォルダーを取り出すこともできるんですね。 圧縮ファイル内のファイルのリストは GetFilesList 関数を呼び出すことで取得できるようなので、そこから選択させることができそうです。

展開するファイルの形式によって指定する format は下記から選択します。

ArchiveFormat カタログデータ型

ArchiveFormatZIP!ZIP (デフォルト)
ArchiveFormat7Zip!7Zip
ArchiveFormatRAR!RAR
ArchiveFormatGZip!gzip
ArchiveFormatTAR!tar
ArchiveFormatLZMA!LZMA
ArchiveFormatLZMA86!LZMA86

あ・・・、CompressorObject の Compress と共用でしたね。 展開のみに使われる 3 種類が追加されてます。


ファイル展開の実装

まずは GetFilesList 関数がどのような形でリストを返すのか確認してみましょう。 先ほど作成した “sample_comp.zip” を指定して、ウィンドウに配置したリストボックスに取得したリストを表示してみます。

ExtractorObject lnv_extractor
string ls_target, ls_list[]
integer i

// ExtractorObject のインスタンスを作成
lnv_extractor = create extractorobject

// リストを取得する圧縮ファイル
ls_target = "C:\temp\sample_comp.zip"

// ファイルリストを取得
lnv_extractor.GetFilesList(ls_target, ls_list)

// ファイルリストをリストボックスに追加
FOR i = 1 TO UpperBound(ls_list)
    lb_1.AddItem(ls_list[i])
NEXT
GetFileList の結果

フォルダーが含まれる場合は “\” で区切られて、サブフォルダー以下も取得できるんですね。 ちなみにファイルリストの取得のみの場合はパスワードの指定は不要でした。

展開する際は Extract 関数でこの文字列をそのまま 構文 2 の items 引数に渡してあげれば、そのファイルだけが抽出できるのかな? これも試してみよう。

ExtractorObject lnv_extractor
string ls_target, ls_items[]
integer i, n = 0, li_res

// リストボックスで選択されたファイルを配列に格納
FOR i = 1 TO lb_1.TotalItems( )
    IF lb_1.state(i) = 1 THEN
        n++
        ls_items[n] = lb_1.Text(i)
    END IF
NEXT

// ExtractorObject のインスタンスを作成
lnv_extractor = create ExtractorObject

// 展開する対象の圧縮ファイル
ls_target = "C:\temp\sample_comp.zip"

// パスワードの指定
lnv_extractor.password = "password1234"

// 圧縮ファイルから指定したファイルを \exctract フォルダーに抽出
li_res = lnv_extractor.Extract(ls_target, ls_items, "C:\temp\extract")

// 結果表示
IF li_res = 1 THEN
    MessageBox("結果", "展開に成功しました。")
ELSE
    MessageBox("エラー", "展開に失敗しました。~nエラーコード: " + string(li_res))
END IF
展開するファイル/フォルダーを選択

3 つのファイルを選択してみました。 どのように出力されるか確認してみましょう。

展開した結果

圧縮したフォルダーの階層を保ったまま展開されていますね。 ただし、出力先として引数 target に指定したフォルダーが存在しない場合は自動的に作成されるわけではなく、エラー : -13 (展開されたファイルが保存されるディレクトリが存在しない) が発生しますので CreateDirectory 関数とかで予め作っておきましょう。

それから上書き確認はされません。 ファイル上書きの警告を出したい場合は FileExists 関数で同名のファイルが存在するか確認するといいですね。


まとめ

これまで PowerBuilder アプリケーションでは、圧縮/展開を実装する場合は外部のプログラムを使っていたかと思います (もしくは手作業とかね)。 しかし、組み込みの機能を使えるようになったことで外部プログラム導入の手間やバージョンの互換性を気にすることなく圧縮/展開ができるようになりました。

また今回はオブジェクトをそのまま利用していますが、ユーザーオブジェクトとしてカスタマイズすることで圧縮/展開の完了やエラーの発生といったイベントが拾えたり、進捗状況 (圧縮/展開済みのサイズ) を取得できたり、もっと便利に利用できるので試して損はないですよ!

ところで、この記事では圧縮されたファイルを復元する言葉として「展開」を使っていますが、「解凍」というほうが馴染みがある!という方も大勢いらっしゃるでしょう。

実は「解凍 or 展開のどちらを使うか」という過激な論争が各地で数年間にわたり繰り広げられていますが、未だに決着はついていない模様です。 PowerBuilder ユーザーはどちらを使っている人が多いのか? Twitter で集計するのもアリかもしれない。 そんなリクエストがあれば、本サイトの公式 Twitter にお寄せいただければアンケートが実現するかも・・・?

以上、エイタでした!

テクニカルブログ 一覧を見る
PowerBuilder マイグレーション
PowerBuilder 2019 R3 日本語版リリース 紹介動画