HTMLのテンプレートだと、HTMLセーフな感じで出力するじゃないですか。あれのターミナル版が欲しいと思ったわけですよ。バイナリデータが入ってきたときにそのまま出してしまうとたまにおかしくなってしまったりするし。かといって、テキストなのに全部バイナリにしてしまうと可読性とか困りますよね。一般人は脳内UTF-8デコードとかできませんし。
で、Goの標準ライブラリにはunicode.IsPrint()関数があって、表示できるか判定ができるので、これを利用します。これの元ネタ?のC/C++のctype.hヘッダーのisprint()関数はASCIIの範囲のみで日本語とかは判定できませんが、Goの方はOKのようです。
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
func Printable(b []byte) string {
var result strings.Builder
for len(b) > 0 {
if utf8.RuneStart(b[0]) {
r, size := utf8.DecodeRune(b)
if unicode.IsPrint(r) {
result.WriteRune(r)
} else if r == 0x0a {
result.WriteString("\\n")
} else {
fmt.Fprintf(&result, "\\x%02x", r)
}
b = b[size:]
} else {
fmt.Fprintf(&result, "\\x%02x", b[0])
b = b[1:]
}
}
return result.String()
}
やっていることは、まずバイト列からRuneを取り出します。その後、 IsPrint()
を見て、表示可能ならそのまま文字列にし、だめな場合はエスケープします。ただし、改行だけはよく出てくるので \n
表記に特別扱いしています。
こんなかんじでいいんですかね?非可逆を狙うなら、 \
は \\
とかにするなど、さらにエスケープが必要な気がしますが。
unicode.IsPrint()
の存在を教えてくれた @moriyoshi に感謝です。
2/12修正
utf8.DecodeRune(b)
は不正なコードがあると、不正なコードを表すマーカーの文字である0xfffdに変換してしまうようです。stringのrangeループでruneを取り出すときも、この関数が使われます。
ということで、↑のコードを少し修正して、 utf8.RuneStart()
でutf-8として正常な文字でないかどうかの判定を追加しています。ただ、これでも、1バイト目は正しいのに2バイト目がダメ、なケースは救えないと思うので、まだ修正が必要です。