zlibjsでファイル名が文字化けしてしまう問題の対応

# 始めに
JSでzipファイルを展開する際に以下の記事を参考にしていましたが、日本語ファイルを展開する際に文字化けをしてしまったのでその対応についてまとめました。

+ [zlibjsのUnzipを使用してブラウザ上でzipファイルを展開する](https://qiita.com/59naga/items/d8abcdc06248cb28cebd)
+ [[zlib.js] JavaScriptでZIP解凍する](https://qiita.com/yun_bow/items/1b9e86a2efe12432845d)

# 導入方法
こちらでも使い方について簡単に書きます。
まずは以下でパッケージをインストールします。

```
$ yarn add zlibjs
```

次に以下のようにしてzipファイルを展開します。今回はinput fileからのコールバックを使っています。

```javascript:input_fileからのコールバックでzipファイルを展開する
const { Unzip } = require('zlibjs/bin.unzip.min').Zlib;

function onUpload(event) {
  const reader = new FileReader();
  // 読み込み完了後
  reader.onloadend = () => {
    if (!reader.result) {
      return;
    }

    // 展開用インスタンスを生成する
    const unzip = new Unzip(new Uint8Array(reader.result));

    // zipファイルに入っていたファイル名リスト
    const fileNames = unzip.getFilenames();

    // zipファイルの中身を展開する
    const zipFileInfoList = fileNames.map((fileName) => {
      return {
        fileName,
        // 対象ファイル名のバイナリデータを展開する
        binaryData: unzip.decompress(fileName),
      };
    });
  };

  // ファイルの読み込み開始
  const file = event.currentTarget.files[0];
  reader.readAsArrayBuffer(file);
}
```

# 問題と解決方法
こちらで基本的には動作しますが、zipファイルの中に日本語のファイル名が入っていると文字化けする問題がありました（ファイルの中身はバイナリで扱っているので、中身は日本語テキストでも画像でも問題はありませんでした）。

これの原因は`unzip.getFilenames()`がマルチバイト文字列に対応していないためでした。バイナリデータとしては正しいのですが、文字列の解析の仕方が誤っていたということですね。
そこで一度バイナリデータに戻してデコードすることで日本語ファイルも正しく表示されるようになります。日本語の変換はUTF8からのみにしたいため、[encoding](https://github.com/polygonplanet/encoding.js)というライブラリを使ってUTF8に変換していてから文字列に変換します。npmからimportしてもいいですが、ファイルサイズが大きいのでここではCDNから読み込んでグローバルメソッドとして使っています。

```javascript:文字コードの変換
/**
 * UTF8文字列のbyte配列から文字列に変換する
 * @param array - UTF8文字列のbyte配列
 */
// https://tutorialmore.com/questions-1371393.htm
export function utf8ByteArrayToStr(array) {
  var out, i, len, c;
  var char2, char3, char4;
  out = "";
  len = array.length;
  i = 0;
  while (i < len) {
    c = array[i++];
    switch (c >> 4) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        // 0xxxxxxx
        out += String.fromCharCode(c);
        break;
      case 12:
      case 13:
        // 110x xxxx   10xx xxxx
        char2 = array[i++];
        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
        break;
      case 14:
        // 1110 xxxx  10xx xxxx  10xx xxxx
        char2 = array[i++];
        char3 = array[i++];
        out += String.fromCharCode(((c & 0x0F) << 12) |
          ((char2 & 0x3F) << 6) |
          ((char3 & 0x3F) << 0));
        break;
      case 15:
        // 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
        char2 = array[i++];
        char3 = array[i++];
        char4 = array[i++];
        out += String.fromCodePoint(((c & 0x07) << 18) | ((char2 & 0x3F) << 12) | ((char3 & 0x3F) << 6) | (char4 & 0x3F));
        break;
    }
  }
  return out;
}

const zipFileInfoList = fileNames.map((fileName) => {
  // バイナリデータに変換する
  const charCodes = fileName.split('').map((char) => char.charCodeAt(0));
  return {
    // Encodingを使ってUTF8に変換してから文字列に変換する
    fileName: utf8ByteArrayToStr(Encoding.convert(charCodes, 'UTF8')),
    binaryData: unzip.decompress(fileName),
  };
});
```

# 終わりに
以上がファイル名が文字化けしてしまったときの対応でした。この解決方法の記事が見当たらなかったので記事にしましたが、誰かのお役に立てれば幸いです。

また、zlibjsを使って実装したアプリをこちらで作っていますので、興味がある方は参考にしてみてください。（こちらはTypeScriptでしかもWorkerも使っているので解読が難しいかもしれませんが・・・）
https://github.com/wintyo/diff-zip-file