Zipファイルの中身のデータを操作するアプリケーションを作ったときに、注意しないといけなかった点のまとめ。
ググると結構出てくるネタだけど、自分なり解釈してまとめたもの。
- 日本語ファイル名の取り扱い
- 中身のデータを別のZipファイルにコピーする方法
上の2点に関して。
日本語ファイル名の取り扱い
ZIPファイル内のファイル名は次のようになっている、らしい。
- general purpose bit flagのビット11が1の場合、UTF-8。(以下「UTF-8ビット」と呼称)
- Windows11のシステムで日本語ファイル名を持つものをZip圧縮するとこれが建つ。
- 0の場合、IBM Code Page 437。
- 昔かアプリ等を使いWindowsでzip圧縮したものはこちらになる。
実際のエンコードはシフトJIS(Code Page 932)が使われる。 - Mac OSはUTF-8。UTF-8ビットが建ってるかどうかは不明。
- 昔かアプリ等を使いWindowsでzip圧縮したものはこちらになる。
以下参照ページ。stackoverflowは2012年のQAなので古いかな。
Windowsでの実際の処理方法
ZIPファイルのヘッダー部分を調べ、UTF-8ビットが1の場合はUTF-8エンコードを返し、0の場合はCode Page 932を返すような関数を用意、それ経由でエンコーダーを取得してZipファイルをオープンする。
Encoding? checkZipEncoding(String fileName)
{
Encoding? encoder = null;
if (File.Exists(fileName))
{
using (var fs = File.OpenRead(fileName))
{
byte[] bytes = new byte[8];
if (fs.Read(bytes, 0, bytes.Length) == bytes.Length)
{
try
{
if ((bytes.Last() & 0x08) != 0)
{
// UTF-8
encoder = Encoding.UTF8;
}
else
{
// シフトJIS
encoder = System.Text.CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
}
}
catch (Exception)
{
encoder = null;
}
}
}
}
return encoder;
}
foreach (var name in args)
{
var encode = checkZipEncoding(name);
if (encode != null)
{
using (ZipArchive archive = ZipFile.Open(name, ZipArchiveMode.Read, encode))
{
foreach (var e in archive.Entries)
{
Console.WriteLine(e.Name);
}
}
}
}
補足:文字列指定でのエンコーダー指定
UTF-8のような固定なのはEncoding.UTF8
を使う。
shift_jisなどのマルチバイトのものは2通り。
コードページ番号を指定して取得するか、文字列で検索する。
どちらもGetEncoding()メソッドを使う。
コードページ番号の場合
システムが用意されていればリターンが来るが、システムで用意されていなければ例外が発生する。
Windowsの場合シフトJISは932。Windowsであれば用意されている。
文字列の場合
システムで用意されているかどうかは、System.Text.CodePagesEncodingProvider.Instance
側のGetEncoding
を使う必要がある。
Encoding.GetEncoding
の場合は.Net Coreで用意されているデフォルトのものが検索対象になり、そちらにシフトJISは含まれていないので例外が発生する。
ただシステムで用意されていない文字列を指定しても例外が発生する。
こちら側にもちょっと書かれている。
指定可能な文字列は、GetEncodings
の集合内に入っているEncoding.WebName
が検索対象ということらしい。
一応、エイリアスは用意されているようだが、許容される文字がどこまでかのドキュメントがないので上のものを使用するのが良いと思われる。
エイリアスについてのQA。
中身のデータを別のZipファイルにコピーする方法
こちらは難しくなく、元ファイルをオープンしZipArchiveEntryを取得し、それを出力側にコピーするといった感じ。
using (var output = ZipFile.Open(zipFileName, ZipArchiveMode.Create, Encoding.GetEncoding(defaultCP)))
{
foreach (var data in archive.Entries)
{
// データをコピーする
var entry = output.CreateEntry(data.Name);
using (var writer = entry.Open())
{
using (var reader = data.Open())
{
reader.CopyTo(writer);
}
}
}
}
コメント