nptclのブログ

Common Lisp処理系nptの開発メモです。https://github.com/nptcl/npt

strjis: 日本語テキスト入出力ライブラリ

【追記】ISO-2022-JP-2004に対応しました。

strjisの紹介

Common Lispで日本語のテキストを読み書きするライブラリを作成しました。

strjis
https://github.com/nptcl/strjis

下記のエンコードを扱うことができます。

Common Lispにて日本語を読み書きする方法は、すでに色々と存在します。
わざわざこのライブラリを使用する必要はないかもしれません。

日本語を読み書きする一般的な方法は、 次の情報が参考になると思います。

LISPUSER Common Lisp と 日本語 と 文字コード
http://lispuser.net/commonlisp/japanese.html

逆引きCommon Lisp 処理系:日本語の扱い
https://lisphub.jp/common-lisp/cookbook/index.cgi?p=%bd%e8%cd%fd%b7%cf%3a%c6%fc%cb%dc%b8%ec%a4%ce%b0%b7%a4%a4

Babel
https://common-lisp.net/project/babel/

本ライブラリは次の利点があります。

欠点も色々とあります。

  • streamとして扱えない (streamへの入出力は可)
  • open:external-formatに指定できない
  • 規格に厳密に従っているわけではない

今回は標準ANSI Common Lispだけで作りたかったので、 例えば配列から配列へ変換するような作りになっています。 別の機会にでも、勉強してstreamに対応させたりしてみたいです。

UNICODEの変換表は、下記のサイトを参考にさせていただきました。

Unicode Consortium
http://www.unicode.org/
http://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS
http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt

JIS X 0213のコード対応表
http://x0213.org/codetable/
http://x0213.org/codetable/iso-2022-jp-2004-std.txt

JIS X 0213のコード対応表につきましては、「Copyright (c) 2006-2017 Project X0213」さんが 作成したものであり、自由に使用・配布・改変等してもよいとのことでしたので、 加工して変換テーブルの実装に利用させていただきました。 ありがとうございます。

ライブラリの使用方法は下記のファイルにまとめました。
https://github.com/nptcl/strjis/blob/master/docs/readme.ja

動作確認はsbcl, clisp, cclにて行っています。 ただ、これらは全てbase-charが21bit以上のコードを格納できる処理系です。 例えばAllegroCLLispWorksなんかは、一文字16bitで処理しているとのことで、 そうなるとどこまでうまく動くかどうかわかりません。

使用例を下記に示します。

strjisの使い方

簡単な使い方をいくつか載せます。

JISをUTF-8に変換

JISの文字1B 24 42 あいうを変換します。

(coerce-list
  '(#x1B #x24 #x42  #x24 #x22  #x24 #x24  #x24 #x26)
  :input 'jis :output 'utf8)
-> (227 129 130 227 129 132 227 129 134)

SHIFT-JISからUTF-8 BOMありに変換

あいうを変換します。

(coerce-list
  #(#x82 #xA0 #x82 #xA2 #x82 #xA4)
  :input 'shiftjis :output 'utf8bom)
-> (239 187 191 227 129 130 227 129 132 227 129 134)

リストではなく配列を返却したい

関数coerce-vectorを使用します。

(coerce-vector
  '(#x82 #xA0 #x82 #xA2 #x82 #xA4)
  :input 'shiftjis :output 'utf8bom)
-> #(239 187 191 227 129 130 227 129 132 227 129 134)

入力に文字列を指定したい

処理系がUnicode対応である必要があります。

(coerce-list "Hello" :output 'eucjis)
-> (72 101 108 108 111)

出力を文字列にしたい

処理系がUnicode対応である必要があります。

(coerce-string
  '(#x1B #x24 #x42  #x24 #x22  #x24 #x24  #x24 #x26)
  :input 'jis)
-> "あいう"

ファイルを変換したい

テキストファイルをJISに変換します。
処理系がUnicode対応である必要があります。

(with-open-file (input #p"input.txt" :direction :input)
  (with-open-file (output #p"output.txt" :direction :output
                          :if-exists :supersede
                          :if-does-not-exist :create
                          :element-type '(unsigned-byte 8))
    (coerce-stream input output :output 'jis)))

入力のファイル形式を指定して変換したい

EUC-JISのテキストファイルをUTF-16BE BOMなしに変換します。 入力も出力も(unsigned-byte 8)openしてください。

(with-open-file (input #p"input.txt" :direction :input
                       :element-type '(unsigned-byte 8))
  (with-open-file (output #p"output.txt" :direction :output
                          :if-exists :supersede
                          :if-does-not-exist :create
                          :element-type '(unsigned-byte 8))
    (coerce-stream input output :input 'eucjis :output 'utf16be)))

入力にbabelを使いたい

文字列あいうの読み込みをbabelに任せます。

(coerce-list
  (babel:string-to-octets "あいう" :encoding :utf-8)
  :input 'utf8 :output 'utf16le)
-> (66 48 68 48 70 48)

入力にopenexternal-formatを使いたい

sbclの実行例です。

(with-open-file (input #p"input.txt" :direction :input
                       :external-format :utf-8)
  (coerce-list input :output 'utf16le))
-> UTF-16LEのリスト

ISO-2022-JPを読み込みたい

【追記】ISO-2022-JP-2004に対応しました。

入力ISO-2022-JPからUTF-8に変換。

(coerce-string x :input 'iso2022jp :output 'utf8)
→ISO-2022-JPからUTF-8に変換

書き込みは、ISO-2022-JP-2004に対応しています。

(coerce-string x :input 'utf8 :output 'iso2022jp)
→UTF-8からISO-2022-JP-2004に変換

ISO-2022-JPの扱いについては別途説明します。
https://github.com/nptcl/strjis/blob/master/docs/readme.ja

関数の説明

変換に使用する関数は次の通り。

(defun coerce-list (x &key input output) ...)
(defun coerce-vector (x &key input output) ...)
(defun coerce-string (x &key input output) ...)
(defun coerce-stream (x output-stream &key input output) ...)

xは入力データです。 数値の配列、数値のリスト、文字列、streamのどれかを指定できます。

inputは入力エンコードタイプであり、下記のsymbolから選択します。

utf8 ascii jis eucjp eucjis shiftjis unicode
utf16 utf16v utf16be utf16le
utf32 utf32v utf32be utf32le

outputは出力エンコードタイプであり、下記のsymbolから選択します。

ascii jis eucjp eucjis shiftjis unicode
utf8 utf8bom utf8no
utf16 utf16v utf16be utf16le utf16bebom utf16lebom
utf32 utf32v utf32be utf32le utf32bebom utf32lebom

utf16vは、0から#xFFFFまでの数値を扱います。

utf32vは、0から#x10FFFFまでの数値を扱います。

ascii#x00#x7Fまでの文字を限定で扱います。

utf8noは、BOMを除去します。

utf16utf32は、BOMからbig-endianかlittle-endianを判定します。 BOMが無かったらbig-endianであるとみなします。

unicodeは、char-code-limitの値を見て、適切なタイプを判断します。
もしchar-code-limit255以下ならutf8です。
もしchar-code-limit65535以下ならutf16vです。
それ以外ならutf32vです。

その他

eucjpeucjisは同じです。

JISのエスケープシーケンスは、第1・第2水準漢字に相当するものが、 JIS C 6226-1978, JIS X 0208-1983, JIS X 0208-1990, JIS X 0213:2000 1面, JIS X 0213:2004 1面と様々ありますが、 全部同じものとして処理します。 JIS X 0213:2000 2面とJIS X 0212-1990補助漢字すら同じに扱います。

規約違反Unicode文字は全てエラーです。 例えばコードが#x110000を超えていたり、 Surrogate Code Pointを使ってみたり、 UTF-8で冗長な方法で表現していた場合は読み込みません。