with-output-to-stringと文字コード
Common LispでWebアプリのお話。
POSTで日本語(UTF-8)を含んだ文字列を送信すると、文字化けしてしまったので、
原因を調べてみると、URLデコードを行っている関数が犯人のようでした。
(defun url-decode-string (s) (with-output-to-string (out) (do ((i 0 (1+ i))) ((>= i (length s)) out) (let ((c (char s i))) (case c (#\% (setf c (code-char (parse-integer s :start (+ i 1) :end (+ i 3) :radix 16 :junk-allowed t))) (unless (null c) (write-char c out)) (incf i 2)) (#\+ (write-char #\Space out)) (otherwise (write-char c out)))))))
outの文字コードがしていされていないのが原因だと思うんですけど、
with-output-to-stringでは:external-formatキーワードは指定できないようです。
ストリームを開いた後に文字コードをしている方法はないかと調べてみたら、
system::set-stream-external-formatなる関数があったのですが、
string-streamには使用できないみたいです。
色々苦心してみましたが結局解決策が見つからず、
一旦バイト列に入れてから文字列に変換することにしました。
;;; CLISP only (defun url-decode-string2 (s) (let ((arr (make-array (length s) :element-type '(mod 256)))) (do ((i 0 (1+ i)) (j 0 (1+ j))) ((>= i (length s)) (ext:convert-string-from-bytes arr charset:utf-8 :end j)) (let ((c (char s i))) (case c (#\% (setf c (parse-integer s :start (+ i 1) :end (+ i 3) :radix 16 :junk-allowed t)) (unless (null c) (setf (aref arr j) c)) (incf i 2)) (#\+ (setf (aref arr j) (char-code #\Space))) (otherwise (setf (aref arr j) (char-code c))))))))
ext:convert-string-from-bytesという関数がバイト列を文字列に変換してくれます。
バイト列に文字コードを指定して文字列を直す関数があるのは非常に便利ですね。
残念ながらCL標準ではないようですが、大抵の処理系にはこの手の関数があるようです。