nkfという今年で30周年になる古いソフトがあります。nkfでは文字コードを変換することができます。 nkfには様々な機能がありますが、次の3点を満たすものをGo言語で作ってみました。
- 文字コードを変換する(ISO-2022-JP、Shift_JIS、EUC-JP、UTF-8)
- 文字コードを判定する
- 改行コードを変換する(UNIXはLF、WindowsはCRLF、MacintoshはCR)
文字コードを変換する
文字コードの変換は以下の記事を参考にしました。というか丸コピ
文字コードを判定する
文字コードの判定はchardetを使用しました。chardetは文字コードを判定するためのライブラリです。 判定に失敗した時にはエラーを返します。
改行コードを変換する
改行コードの変換は正規表現を使用しました。
コード
コードは以下のようになりました。
main.go
package main import ( "flag" "fmt" "log" "os" ) var ( inJIS = flag.Bool("J", false, "Input JIS(ISO2022JP)") inEUC = flag.Bool("E", false, "Input EUCJP") inSJIS = flag.Bool("S", false, "Input Shift_JIS") inUTF8 = flag.Bool("W", false, "Input UTF-8") outJIS = flag.Bool("j", false, "Output JIS(ISO2022JP)") outEUC = flag.Bool("e", false, "Output EUCJP") outSJIS = flag.Bool("s", false, "Output Shift_JIS") outUTF8 = flag.Bool("w", false, "Output UTF-8") unix = flag.Bool("Lu", false, "LF(UNIX) Newline") windows = flag.Bool("Lw", false, "CRLF(Windows) Newline") macintosh = flag.Bool("Lm", false, "CR(Macintosh) Newline") override = flag.Bool("override", false, "Override") guess = flag.Bool("g", false, "Detect Charset") ) func setIn() string { switch { case *inJIS: return "ISO2022JP" case *inEUC: return "EUCJP" case *inSJIS: return "ShiftJIS" case *inUTF8: return "UTF8" default: return "" } } func setOut() string { switch { case *outJIS: return "ISO2022JP" case *outEUC: return "EUCJP" case *outSJIS: return "ShiftJIS" case *outUTF8: return "UTF8" default: return "ISO2022JP" } } func setNl() string { switch { case *unix: return "UNIX" case *windows: return "WINDOWS" case *macintosh: return "MACINTOSH" default: return "" } } func main() { var fp *os.File var err error flag.Parse() if len(flag.Args()) < 1 { fp = os.Stdin } else { fp, err = os.Open(flag.Args()[0]) if err != nil { log.Fatal(err) } defer fp.Close() } in := setIn() out := setOut() nl := setNl() if *guess { charset, _ := Guess(fp) fmt.Println(charset) } else { str, err := Convert(fp, in, out, nl) if err != nil { log.Fatal(err) } if *override && !(len(flag.Args()) < 1) { fp.Close() fp, err = os.OpenFile(flag.Args()[0], os.O_WRONLY, 0666) if err != nil { log.Fatal(err) } defer fp.Close() fmt.Fprint(fp, str) } else { fmt.Fprint(os.Stdout, str) } } }
convert.go
package main import ( "errors" "io/ioutil" "os" "regexp" "strings" "github.com/saintfish/chardet" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" ) var errDet = errors.New("Couldn't detect") func charDet(b []byte) (string, error) { d := chardet.NewTextDetector() res, err := d.DetectBest(b) if err != nil { return "", err } switch res.Charset { case "Shift_JIS": return "ShiftJIS", nil case "UTF-8": return "UTF8", nil case "EUC-JP": return "EUCJP", nil case "ISO-2022-JP": return "ISO2022JP", nil default: return res.Charset, errDet } } func toUtf8(str string, in string) (string, error) { var u8 []byte var err error switch in { case "ISO2022JP": u8, err = ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ISO2022JP.NewDecoder())) case "EUCJP": u8, err = ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.EUCJP.NewDecoder())) case "ShiftJIS": u8, err = ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ShiftJIS.NewDecoder())) case "UTF8": u8, err = []byte(str), nil default: u8, err = ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ShiftJIS.NewDecoder())) } if err != nil { return "", err } return string(u8), err } func toSjis(str string) (string, error) { sjis, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ShiftJIS.NewEncoder())) if err != nil { return "", err } return string(sjis), err } func toEuc(str string) (string, error) { euc, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.EUCJP.NewEncoder())) if err != nil { return "", err } return string(euc), err } func toJis(str string) (string, error) { jis, err := ioutil.ReadAll(transform.NewReader(strings.NewReader(str), japanese.ISO2022JP.NewEncoder())) if err != nil { return "", err } return string(jis), err } func nlRep(str string, nl string) string { rep := regexp.MustCompile(`\r\n|\r|\n`) switch nl { case "UNIX": return rep.ReplaceAllString(str, "\n") case "WINDOWS": return rep.ReplaceAllString(str, "\r\n") case "MACINTOSH": return rep.ReplaceAllString(str, "\r") } return str } /* Only detect Character encoding */ func Guess(file *os.File) (string, error) { input, err := ioutil.ReadAll(file) if err != nil { return "", err } det, err := charDet(input) return det, err } /* Convert Character encoding */ func Convert(file *os.File, in string, out string, nl string) (string, error) { input, err := ioutil.ReadAll(file) if err != nil { return "", err } if in == "" { in, err = charDet(input) if err != nil { return "", err } } u8, err := toUtf8(string(input), in) if err != nil { return "", err } if nl != "" { u8 = nlRep(u8, nl) } var output string switch out { case "ISO2022JP": output, err = toJis(u8) case "ShiftJIS": output, err = toSjis(u8) case "EUCJP": output, err = toEuc(u8) case "UTF8": output = u8 } return output, err }
まとめ
EUC-JPの判定はあまりうまくいかないのでEオプションをつけるといいと思います。
chardetはShift_JISとEUC-JPで誤判定が発生する可能性があるため、overrideオプションを使う時は入力オプションを使うことをおすすめします。
ここまで長くなるとコードをまるまる載せるのではなく、GitHubで公開したほうがいいと思いました。
GitHubに公開しました(2017/07/14)