CLOVER🍀

That was when it all began.

JavaScript/Node.jsでエンコーディング変換を扱うことができる、iconv-liteを試す

Node.jsといえば、UTF-8エンコーディングが基本で、Shift_JISEUC-JPなどを扱う場合にはどうするんだろう?と思って
いたのですが、「iconv-lite」というモジュールを使用するのがよいみたいです。

GitHub - ashtuchkin/iconv-lite: Convert character encodings in pure javascript.

これを見ると、変換表を実装している感じなんですねぇ、と…。
https://github.com/ashtuchkin/iconv-lite/tree/v0.4.19/encodings

サポートしているエンコーディングは、こちら。
Supported Encodings · ashtuchkin/iconv-lite Wiki · GitHub

とりあえず、使っていってみましょう。

環境

確認時の環境は、以下の通り。

$ node -v
v9.5.0

$ npm -v
5.6.0

準備

ドキュメントに沿って、npmでインストール。

$ npm install --save iconv-lite

package.jsonでの依存関係は、このようになりました。

  "dependencies": {
    "iconv-lite": "^0.4.19"
  },

動作自体は、テストコードで確認しましょう。

今回は、Jestを使うことにしました。

$ npm install --save-dev jest

package.jsonでの依存関係は、このように。

  "devDependencies": {
    "jest": "^22.2.2"
  }

「npm run test」で、Jestが起動するようにしておきました。

  "scripts": {
    "test": "jest"
  },

テストデータも用意しておきましょう。ファイルで、iconvを使って簡単に作ります。

$ echo -n "あいうえお" > data/utf8.txt

$ iconv -f UTF-8 -t Windows-31J -o data/sjis.txt data/utf8.txt
$ iconv -f UTF-8 -t EUCJP-MS -o data/eucjp.txt data/utf8.txt

こちらを使って確認していきましょう。

確認

まずは、requireから。
test/iconv-lite.test.js

const fs = require("fs");
const iconv = require("iconv-lite");

fsモジュールはファイル読み込みのためで、今回の主役はiconv-liteです。

decode

とりあえず、UTF-8のファイルは標準機能で読んでみます。

test("read standard utf8", done => {
    fs.readFile("data/utf8.txt", "utf-8", (err, data) => {
        expect(data).toEqual("あいうえお");
        done();
    });
});

これを、iconv-liteで。

test("read utf8, using iconv-lite", done => {
    fs.readFile("data/utf8.txt",  (err, data) => {
        const stringData = iconv.decode(data, "utf-8");
        expect(stringData).toEqual("あいうえお");
        done();
    });
});

iconv#decodeを使って、Bufferを変換します、と。

        const stringData = iconv.decode(data, "utf-8");

Shift_JISEUC-JPについても、同じ。

test("read shift-jis, using iconv-lite", done => {
    fs.readFile("data/sjis.txt",  (err, data) => {
        const stringData = iconv.decode(data, "windows-31j");
        expect(stringData).toEqual("あいうえお");
        done();
    });
});

test("read euc-jp, using iconv-lite", done => {
    fs.readFile("data/eucjp.txt",  (err, data) => {
        const stringData = iconv.decode(data, "eucjp");
        expect(stringData).toEqual("あいうえお");
        done();
    });
});

このあたりを見ると、「Windows-31J」や「CP932」は「Shift_JIS」のエイリアスっぽいです。
https://github.com/ashtuchkin/iconv-lite/blob/v0.4.19/encodings/dbcs-data.js#L41-L56

EUC-JPは、ひとつだけだったり。
https://github.com/ashtuchkin/iconv-lite/blob/v0.4.19/encodings/dbcs-data.js#L58-L62

encode

今度は、encode。単純に、Stringに対してencodeしてみます。

まずは、標準APIの範囲で。

test("encode standard utf-8", () => {
    const data = "あいうえお";
    const buffer = Buffer.from(data, "utf-8");
    expect(buffer).toEqual(Buffer.from([0xe3, 0x81, 0x82, 0xe3, 0x81, 0x84, 0xe3, 0x81, 0x86, 0xe3, 0x81, 0x88, 0xe3, 0x81, 0x8a]));
});

iconv-liteを使って、UTF-8Shift_JISEUC-JPでencode。

test("encode utf-8, using iconv-lite", () => {
    const data = "あいうえお";
    const buffer = iconv.encode(data, "utf-8");
    expect(buffer).toEqual(Buffer.from([0xe3, 0x81, 0x82, 0xe3, 0x81, 0x84, 0xe3, 0x81, 0x86, 0xe3, 0x81, 0x88, 0xe3, 0x81, 0x8a]));
});

test("encode shift-jis, using iconv-lite", () => {
    const data = "あいうえお";
    const buffer = iconv.encode(data, "windows-31j");
    expect(buffer).toEqual(Buffer.from([0x82, 0xa0, 0x82, 0xa2, 0x82, 0xa4, 0x82, 0xa6, 0x82, 0xa8]));
});

test("encode euc-jp, using iconv-lite", () => {
    const data = "あいうえお";
    const buffer = iconv.encode(data, "eucjp");
    expect(buffer).toEqual(Buffer.from([0xa4, 0xa2, 0xa4, 0xa4, 0xa4, 0xa6, 0xa4, 0xa8, 0xa4, 0xaa]));
});

こちらの場合は、iconv#encodeを使用してStringをBufferに変換します、と。

    const buffer = iconv.encode(data, "utf-8");
Streaming

ここまでは、iconv#encode/decodeとStringとBufferの変換をやっていましたが、Streming APIというものもあるようです。

Streaming API (Node v0.10+)

ここにある、stream.Readableと合わせて使うみたいですね。
Stream | Node.js v9.11.2 Documentation

fs#createReadStreamに対して、pipeでiconv#decodeStreamを指定。

test("read utf-8 stream, using iconv-lite", done => {
    fs
        .createReadStream("data/utf8.txt")
        .pipe(iconv.decodeStream("utf-8"))
        .collect((err, data) => {
            expect(data).toEqual("あいうえお");
            done();
        });
});

collectってstream.Readableにも載っていなくて、なんだろう?と思ったのですが、iconv-liteで作っているもののようです。
https://github.com/ashtuchkin/iconv-lite/blob/v0.4.19/lib/streams.js#L65-L73
https://github.com/ashtuchkin/iconv-lite/blob/v0.4.19/lib/streams.js#L112-L120

stream.Readableのイベントを使うなら、こんな感じでしょうか。

test("read utf-8 stream and event, using iconv-lite", done => {
    const stream =
              fs
              .createReadStream("data/utf8.txt")
              .pipe(iconv.decodeStream("utf-8"));

    stream.on("data", chunk => expect(chunk).toEqual("あいうえお"));
    stream.on("end", () => done());
});

stream.Readableから、stream.Writableにエンコーディングを変換して流し込み。

test("write shift-jis stream, using iconv-lite", done => {
    const stream =
              fs
              .createReadStream("data/eucjp.txt")
              .pipe(iconv.decodeStream("eucjp"))
              .pipe(iconv.encodeStream("windows-31j"))
              .pipe(fs.createWriteStream("data/sjis-iconv-lite.txt"));

    stream.on("finish", () => {
        fs.readFile("data/sjis-iconv-lite.txt",  (err, data) => {
            const stringData = iconv.decode(data, "windows-31j");
            expect(stringData).toEqual("あいうえお");
            done();
        });
    });
});

とりあえず、基本的な使い方とBuffer、StreamのAPIにちょこっと触れたところで、今回はおしまいです。