NKFtool : Julia プログラム内から漢字コードを変換する

Julia Advent Calendar 2020，16日目です．

Julia で，漢字コード変換を行うパッケージ `NKFtool` を紹介します．

# Julia は Unicode が大好き

Julia は Unicode が大好きです．変数名，関数名，演算子を，Unicode文字で表せます．（以前紹介記事を書きました → [UTF-8 表記の演算子たち](https://qiita.com/tenfu2tea/items/c3402e647c49e989ac2f)）．

Julia の文字・文字列は、UTF-8 符号です． 可変長の UTF-8 を，Julia はしなやかに扱います．
例えば，`eachindex(s)` は，文字列 `s` に入っている各文字の添字(何バイト目か)を順番に取り出すイテレータです． 以下の例では，絵文字が 4 バイト，漢字が 3 バイト，半角文字は 1 バイトで表されていることが分かります．（絵文字 🍣 をJuliaの REPLで入力するには `\:sushi:` と入力してからTABキーで変換します)． 

```julia
julia> collect(eachindex("s🍣寿ss"))
5-element Array{Int64,1}:
  1
  2
  6
  9
 10
```


# 漢字コードの話

Unicode が使われる以前から，複数の漢字コードが存在しています．
主な漢字コードは，JISコード，日本語EUC (Extended Unix Code)，Shift_JIS の三つです． 日頃 Windows 上で Microsoft Office を使う人は， Shift_JIS 文字(の拡張)を使っているはずです． 各種測定器が吐き出すファイルも Shift_JIS で書かれるものが多いです． 

さて，UTF-8 以外で符号化されたファイルをJuliaで扱うには，事前に UTF-8 に変換したファイルを用意しておけばよいのですが、手間ですし，ファイルが増えてしまいます． 今回紹介する `NKFtool` を使えば、必要なときに Julia プログラム中からコード変換して読めるので、この悩みを解消できます． 

# nkf の紹介

nkf (Network Kanji Filter)は，漢字コード変換のためのコマンドライン・ツールです．

多くのパッケージマネージャから `nkf`をインストールできます．例えば，Mac の Homebrew からインストールするには

```command
% brew install nkf

% nkf --version
Network Kanji Filter Version 2.1.5 (2018-12-15) 
Copyright (C) 1987, FUJITSU LTD. (I.Ichikawa).
Copyright (C) 1996-2018, The nkf Project.
```

（`nkf`は， Unicode や the Internet が広く普及する前から存在する由緒あるツールです）

文字コード変換の標準的なツールである `iconv` と比較して `nkf` は，以下の点で便利です．

- 入力コードを自動で推定するので，指定しなくてよい(ただし、推定が失敗する場合もある)
- 改行記号を変換できる (CR, LF, CR+LF)
- 独自拡張された漢字コード（の一部）も取り扱うことができる


# NKFtool の導入と起動

`NKFtool` パッケージは，Julia 内から `nkf` を呼ぶためのパッケージです．Juliaのパッケージマネージャからインストールできます．`nkf` は予め入れておいてください．

```julia
julia> # ] キーを押してパッケージモードに入る

pkg> add NKFtool

julia> # CTRL + C で REPL に戻る
```

`NKFtool.nkf_version()` は，`nkf --version` の出力を文字列として返します．

```julia
julia> nkf_version() |> println
Network Kanji Filter Version 2.1.5 (2018-12-15) 
Copyright (C) 1987, FUJITSU LTD. (I.Ichikawa).
Copyright (C) 1996-2018, The nkf Project.
```

`NKFtool.nkf_help()` は，`nkf --help` の出力を文字列として返します． `nkf` のオプション確認に便利です．

```julia
julia> nkf_help() |> println
Usage:  nkf -[flags] [--] [in file] .. [out file for -O flag]
 j/s/e/w  Specify output encoding ISO-2022-JP, Shift_JIS, EUC-JP
          UTF options is -w[8[0],{16,32}[{B,L}[0]]]
 J/S/E/W  Specify input encoding ISO-2022-JP, Shift_JIS, EUC-JP
          UTF option is -W[8,[16,32][B,L]]
# 以下省略
```

# 漢字コードを推定する

`NKFtool.nkf_guess(s)` は，漢字コードを推定するコマンド `nkf -g` の出力を文字列として返します． 入力 `s` には，文字列またはストリームを指定します．

```julia
julia> nkf_guess("おはよう")
"UTF-8"

julia> nkf_guess(IOBuffer(raw"おはよう"))
"UTF-8"
```

# 漢字コードを変換する

`NKFtool.nkf_convert(s, options)` は，`options` に従い漢字コードを変換した文字列を返します． 入力 `s` には，文字列またはストリームを指定します． `options` は `nkf` コマンドにそのまま渡されます （`nkf --help` で表示されるオプションの全てを指定できるわけではありません． 特に、入力ファイルを指定してしまうと、そこから文字列を読み込んでしまうでしょう）．

出力漢字コードを指定する主なオプションは，以下の通りです．(小文字で指定します)

- `-j` :  JIS漢字コード，
- `-e` : 日本語EUCコード，
- `-s` : SHIFT_JIS コード
- `-w` または `-w0` : UTF-8

`options` は省略可能です。既定値は `-w -m0` (出力コード UTF-8, no MIME encoding) です．

文字列を，色々な漢字コードに変換してみます． `nkf_guess()` を用いて，漢字コードが正しく推定されることを確かめましょう．

```julia
julia> nkf_convert( raw"おはよう", "-j") |> nkf_guess
"ISO-2022-JP"

julia> nkf_convert( raw"おはよう", "-e") |> nkf_guess
"EUC-JP"

julia> nkf_convert( raw"おはよう", "-s") |> nkf_guess
"Shift_JIS"

julia> nkf_convert( raw"おはよう", "-w") |> nkf_guess
"UTF-8"

julia> nkf_convert( raw"おはよう") |> nkf_guess
"UTF-8"
```

# 漢字コードを変えてファイルに書き込む

漢字コードを変えてファイルに書き込むには，`nkf_convert(s, options)` の結果を，出力ストリームに渡せばよいでしょう．

以下の例では Shift_JIS コードのファイルを作りました．

```julia
julia> open("morning_sjis.txt","w") do f
          print(f, nkf_convert(raw"おはよう\n", "-s"))
          print(f, nkf_convert(raw"おはよう\n", "-s"))
       end

julia> open("morning_sjis.txt") do f
         nkf_guess(f)
       end
"Shift_JIS"

julia> # 最初の1行で，コードを判定する
       readline("morning_sjis.txt") |> nkf_guess
"Shift_JIS"
```

# ファイルから読み込む

色々な漢字コードで書かれたファイルから読み込むには，入力ファイルのストリーム `f` を `nkf_convert(f, options)` に渡せばよいでしょう．

Julia文字列 として読み込む場合には `options` を省略してもよいです． （`options` の既定が `-w -m0` だから）

```julia
julia> morning_utf=open("morning_sjis.txt") do f
          nkf_convert(f)
       end
"おはよう\\nおはよう\\n"
```

入力コードは誤判定がありうるので，事前に分かっているなら指定するとよいでしょう．
入力コードを指定する主なオプションは，以下の通りです．(各々の出力コード指定を大文字にした文字です)

- `-J` : JIS漢字コード
- `-E` : 日本語EUCコード
- `-S` : SHIFT_JIS コード

```julia
julia> morning_utf=open("morning_sjis.txt") do f
          nkf_convert(f, "-S" )  # Shift_JIS コードを読む
       end
"おはよう\\nおはよう\\n"
```

Windows（の一部？）は，独自拡張した Shift_JIS コードを使用します（こちらの記事などを参照 → [本当は怖くないCP932](https://qiita.com/kasei-san/items/cfb993786153231e5413) や [nkfでCP932←→UTF-8の変換をする](https://qiita.com/aosho235/items/6976247eb057ccc604c5) ）． 例えば，CP932コードで書かれたテキスト・ファイルを読み込むなら `--cp932` オプションを指定しましょう (`--cp932` は， `nkf --help` には表示されない隠しオプションです)． 

```julia
julia> open("morning_cp932.txt","w") do f
          print(f, nkf_convert(raw"おはよう～\n", "--oc=cp932"))  # CP932 へ変換
       end

julia> open("morning_cp932.txt") do f
         nkf_guess(f)
       end
       # Shift_JIS として判定される
"Shift_JIS" 

julia> open("morning_cp932.txt") do f
          nkf_convert(f, "--cp932" )
       end
"おはよう～\\n"
```

# Shift_JIS で書かれた CSV ファイルを読んで DataFrame を作る

応用として， Shift_JIS で書かれた CSV ファイルから， DataFrame を作ってみましょう。
私の用いているデータロガーは，次のような CSV ファイルを吐き出します．
(Shift_JIS で表されています)

```AUTO0001.csv
"ファイル名","AUTO0001.CSV","V 1.11"
"タイトルコメント",""
"トリガ時刻","20-03-25 10:45:00"
"CH","Mode","Range","Comment","Scaling","Ratio","Offset"
"CH-1","電圧","100V","","OFF","-","-"
"CH-2","電圧","100V","","OFF","-","-"
"CH-3","熱電対","2000℃","","OFF","-","-"
"CH-4","熱電対","2000℃","","OFF","-","-"
"Time","CH-1[V]","CH-2[V]","CH-3[℃]","CH-4[℃]","Event",
 0.000000000E+00, 1.00000E-02, 1.50000E-02, 1.96000E+01, 1.94000E+01, 0,
 1.000000000E+01, 1.50000E-02, 1.00000E-02, 1.96000E+01, 1.93000E+01, 0,
 以下省略
```

これから `DataFrame` を作るには、以下の手順を踏みます． 
まず `NKFtool_convert()` で UTF-8文字列に変換します． これを `IOBuffer()` に入れるとよいでしょう． データのヘッダ行は "Time" で始まる行として求められます． `CSV.read(..., DataFrame)` を呼ぶと `DataFrame` に変換できます．

```julia
using NKFtool
using CSV, DataFrames

# ヘッダー行を求める
function get_headerlineno(input_io)
   header_lineno=0
   lineno=0
   while true
      text=readline(input_io)
      isempty(text) && break
      lineno += 1
      # @show lineno, text
      if startswith(text,"\"Time\"")
         header_lineno=lineno
         break
      end
   end
   header_lineno
end

# CSV を読んで DataFrames を作る
function read_CSV(inout_csv_path)
   input_csv_texts = open(inout_csv_path) do f
      nkf_convert(f)
   end
   header_lineno=get_headerlineno(IOBuffer(input_csv_texts))
   CSV.read(IOBuffer(input_csv_texts), header=header_lineno, DataFrame)
end
```

実行してみましょう． 期待したとおりに DataFrame を作成できました （MacOSXターミナル上で起動した Julia REPLでは `[℃]` が `[@@]` に化けてしまいましたが、Visual Studio Code 内の Terminal で起動した Julia REPL では化けずに表示できました）． 

```julia
julia> read_CSV("~/AUTO0001.CSV") 
4×7 DataFrame
 Row │ Time     CH-1[V]  CH-2[V]  CH-3[℃]  CH-4[℃]  Event  Column7 
     │ Float64  Float64  Float64  Float64   Float64   Int64  Missing 
─────┼───────────────────────────────────────────────────────────────
   1 │     0.0    0.01     0.015      19.6      19.4      0  missing 
   2 │    10.0    0.015    0.01       19.6      19.3      0  missing 
   3 │    20.0    0.015    0.01       19.5      19.4      0  missing 
   4 │    30.0    0.02     0.01       19.5      19.4      0  missing 
 以下省略
```

# 終わりに

以上、Juliaプログラム中で、漢字コードを変換できる `NKFtool` を紹介しました． `CSV` を経由して `DataFrame` を作れば、データ操作が早く楽になるはずです． どんどん活用しましょう． 

また、同パッケージのソースは、Julia内でコマンドライン・ツールを呼ぶ例として参考になります。 別のツールを呼び出すパッケージを作る際に眺めてみるとよいでしょう。