Hatena::ブログ(Diary)

Life like a clown このページをアンテナに追加 RSSフィード Twitter

2009-06-01

zip の解凍

zip の解凍クラスが必要になったので作成.C/C++ で zip を扱う場合,zlib のサンプルコードの 1 つである minizip を使うのが一番楽そうなので,このライブラリを利用したラッパクラスを作成することを考えます.

zip は,複数のファイル/ディレクトリをひとまとめにしてアーカイブ化できる機能があるので,この複数のファイルへの操作方法が問題となってきます.試行錯誤した結果,zip アーカイブ内の各要素(ファイル/ディレクトリ)を順に走査できるような iterator を用意して,通常はこの iterator を通して各ファイルにアクセスするような実装としました.

サンプルプログラムは以下のようになります.unzip オブジェクトを生成した後は,begin() や find() メソッドを用いて zip アーカイブ内の処理を行いたいファイルへアクセスします.これらのメソッドが返す iterator は istream へのポインタ(正確には shared_ptr)なので,*演算子や->演算子を用いることで,通常の istream と同様の方法で対象となるファイルの内容を読み取ることができます.

#include <iostream>
#include <string>
#include "clx/unzip.h"

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << argv[0] << " filename [password]" << std::endl;
        std::exit(-1);
    }
    
    std::string path(argv[1]);
    std::string password;
    if (argc > 2) password = argv[2];
    clx::unzip uz(path, password);
    
    // zip アーカイブ内の各ファイル/ディレクトリへ順にアクセスする場合.
    for (clx::unzip::iterator pos = uz.begin(); pos != uz.end(); ++pos) {
        std::cout << pos->path() << std::endl;
        
        // 各ファイルの最初の 10 行を表示.
        std::string buf;
        for (size_t i = 0; i < 10; ++i) {
            if (!std::getline(*pos, buf)) break;
            std::cout << "\t" << buf << std::endl;
        }
        std::cout << std::endl;
    }
    
    // zip アーカイブ内のある 1 つのファイルにアクセスする場合.
    clx::unzip::iterator target = uz.find("document/index.html");
    if (target != uz.end()) {
        std::cout << target->path() << " is found." << std::endl;
    }
    
    return 0;
}
実行結果
$ g++ -I.. -Wall example_unzip.cpp -lz
$ ./a clx_doc_0.14.2.zip
document/

document/acceptor.html
	<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
	  "http://www.w3.org/TR/html4/loose.dtd">
	<html lang="ja">

・・・(中略)・・・

	<meta http-equiv="Content-Script-Type" content="text/javascript">
	
	<script type="text/javascript" src="script/sh_main.js"></script>

document/index.html is found.

ただし,zip アーカイブ内の複数のファイルへ同時にアクセスすることはできないので,この点に注意する必要があります(find() や iterator の ++演算子で別のファイルへアクセスした時点で,元のファイルは閉じられる).

上記のライブラリは,CLX C++ Libraries から取得することができます.ただし,上記のプログラムをコンパイルするには,それとは別に zlib が必要となります.Linux など既に zlib がインストールされている場合は,-lz オプションを付与して下さい.Windows など zlib がインストールされていない環境では,前述した Web サイトからソースコードをダウンロードしてビルドして下さい.

Declarations

unzip

※現在のところ,wchar_t には対応していません.

namespace clx {
    template <
        class CharT,
        class Traits = std::char_traits<CharT>
    >
    class basic_unzip {
    public:
        typedef size_t size_type;
        typedef CharT char_type;
        typedef unzFile handler_type;
        typedef std::basic_string<CharT, Traits> string_type;
        typedef basic_unzipstream<CharT, Traits> stream_type;
        typedef basic_unzipiterator<CharT, Traits> iterator;
        
        basic_unzip();
        basic_unzip(const basic_unzip& cp);
        basic_unzip& operator=(const basic_unzip& cp);
        
        explicit basic_unzip(const string_type& path);
        explicit basic_unzip(const string_type& path, const string_type& password);
        
        virtual ~basic_unzip() throw();
        
        // open/close
        bool open(const string_type& path);
        bool open(const string_type& path, const string_type& password);
        void close();
        bool is_open() const;
        
        // zip アーカイブ内の各ファイル/ディレクトリへの操作
        bool exist(const string_type& path) const;
        iterator begin();
        iterator end();
        iterator find(const string_type& path);
    };
    
    typedef basic_unzip<char> unzip;
}

unzip は,(操作したい zip アーカイブの)ファイル名を指定してオブジェクトを生成します.その後は,begin(),find() メソッドを通じて iterator を取得することによって zip アーカイブ内の各ファイルへアクセスします.

unzipstream

unzipstream は,通常 iterator (後述する unzipiterator)が新たなオブジェクトを生成する際に使用するクラスです.そのため,ユーザが unzipstream を用いて明示的にオブジェクトを生成する事はありません.

namespace clx {
    template <
        class CharT,
        class Traits = std::char_traits<CharT>
    >
    class basic_unzipstream : public std::basic_istream<CharT, Traits> {
    public:
        typedef unzFile handler_type;
        typedef CharT char_type;
        typedef std::basic_string<CharT, Traits> string_type;
        typedef basic_unzipstreambuf<CharT, Traits> streambuf_type;
        typedef typename streambuf_type::size_type size_type;
        
        enum { nbuf = 65536 };
        
        explicit basic_unzipstream(handler_type h, size_type n = nbuf);
        virtual ~basic_unzipstream() throw();
        
        const string_type& path() const;
    };
}

unzipstream 独自のメソッドは path() のみです.path() メソッドは,現在開いているファイル/ディレクトリの(zip アーカイブ内での)絶対パスを返します.

unzipiterator

unzipiterator は,unzip::begin(), unzip::find() などが返す iterator クラスです.unzipiterator は,input_iterator に属します.そのため,提供される操作は ++演算子によるもののみで,--演算子による前方向へのアクセスや,[]演算子によるランダムアクセスはできません.

namespace clx {
    template <
        class CharT,
        class Traits = std::char_traits<CharT>
    >
    class basic_unzipiterator : public std::iterator<std::input_iterator_tag, basic_unzipstream<CharT, Traits> > {
    public:
        typedef basic_unzipstream<CharT, Traits> stream_type;
        typedef shared_ptr<stream_type> stream_ptr;
        typedef unzFile handler_type;
        typedef std::basic_string<CharT, Traits> string_type;
        
        basic_unzipiterator();
        basic_unzipiterator(const basic_unzipiterator& cp);
        basic_unzipiterator& operator=(const basic_unzipiterator& cp);
        
        template <class Unzip>
        basic_unzipiterator(const Unzip& cp);
        
        stream_type& operator*();
        stream_ptr& operator->();
        basic_unzipiterator& operator++();
        basic_unzipiterator operator++(int);
        
        friend bool operator==(const basic_unzipiterator& lhs, const basic_unzipiterator& rhs);
        friend bool operator!=(const basic_unzipiterator& lhs, const basic_unzipiterator& rhs);
    };
}
References