本文書は、青空文庫で配布されているテキストファイルをUnicodeに変換する規則、またUnicodeで入力されたテキストファイルを青空文庫テキストファイル用のShift_JISのファイルに変換する規則を提案する。
青空文庫テキストファイルはJIS X 0208のShift_JISで記述されているが、Unicodeに相互変換したいというニーズがある。
- UTF-8で入力・出力したい
- UTF-8に対応したツールを使いたい
具体的な例としては、青空文庫テキストファイルの加工・修正にJavaScriptを使う例が挙げられる。JavaScriptでは内部で扱う文字列はUnicode文字列になる。そのためJavaScriptを使おうとすると、文字列をUnicodeとして扱わざるをえない。青空文庫テキストをJavaScript文字列に変換するための変換表を一意に定めなければ、ライブラリ間の互換性が維持できなくなる可能性がある。
なお、青空文庫内のデータベース(図書カードで表示される情報など)はUTF-8になっているが、テキストファイルはDB内には格納されておらず、Shift_JISのテキストファイルとして保管されている。
- 青空文庫で配布するテキストファイルの文字コードには適用しない(Shift_JIS / JIS X 0208のままとする)。
- とはいえXHTML版として配布されているファイルはUTF-8に変更してもよいかも。
- 現在の包摂規準は変更しない。外字注記辞書の包摂やデザイン差はそのまま採用する。
- 外字注記記法について、JIS X 0213の面区点番号やUnicodeのコードポイントが指定された注記についても、まずは変換しないで注記のままとする。
- そのうち変換オプションとして、注記をUnicode文字に変換するルールを別途定めてもよいと思われる。
- また、後述する通りUnicodeからShift_JISへの変換では複数のコードポイントを同一の文字に変換しているが、これを禁止してエラーとするか、外字注記記法に変換するオプションもあってもよいと思われる。
- 青空文庫では外字注記記法があり、直接UCSのコードポイントを指定して文字を記述することができる。
- その上で、現状の青空文庫は包摂規準としてJIS X 0208の包摂規準を採用しており、外字注記記法でUCSを指定する際にもこの包摂規準により包摂される文字は記述しないことになっている。
Shift_JISとUTF-8との変換表は様々なバリエーションがあるので、どの文字をどう変換するかを決めなければならない。
JIS X 0213でもShift_JISとUnicodeとの変換表が規定されているが、この変換表は0x5Cを円記号(U+00A5)に、0x815FをU+005Cに対応させている。これは、JIS X 0213でのShift_JISがJIS X 0201とJIS X 0208からなるもので、円記号はJIS X 0201のものが使われ、バックスラッシュ(REVERSE SOLIDUS)はJIS X 0208にしかないためである。しかし、例えば青空文庫でのくの字点「/\
」をUTF-8で入力する際、1文字目が/
(U+002F)ではなく/
(U+FF0F)を使うのであれば、2文字目も\
(U+005C)ではなく\
(U+FF3C)を使いたくなる。これはJISの変換表とは異なるため、JISの変換表をそのまま利用することはできず、独自の変換を考える必要がある。
が、円記号とバックスラッシュ以外は、JISの変換表を使って問題ないのではないかと思われる。
まとめると、方針は以下のようになる。
- Shift_JISの1バイト文字はUTF-8でもShift_JISでも違うデータに変換されず、同じバイト列になるようにする。
- Shift_JISの2バイト文字(JIS X 0208の文字)は、原則としてJIS X 0213で規定されているUnicodeとの対応表の通りの変換を行う。ただし、円記号とバックスラッシュは例外扱いとし、互換領域(Halfwidth and Fullwidth Forms)への対応とする。
なお、将来的に青空文庫のテキストファイルがJIS X 0213の包摂規準に対応する場合は、Unicodeへの変換表についても再検討する。
Shift_JIS中、1バイトで表現される文字(ASCII内の文字)については、Shift_JIS<->UTF-8間でバイト単位で保存される。
変換に揺れがある文字については以下に従う。
文字 | SJIS | 変換結果 | 参考:JIS X 0213 | 参考:CP932 |
---|---|---|---|---|
\ |
0x5C | U+005C | U+005C | |
~ |
0x7E | U+007E | U+007E |
Shift_JIS中、1バイトで表現される文字(JIS X 0208の文字)については、 JIS X 0213で規定されているマッピングに従う。
ただし、¥
(0x818F)についてはU+00A5ではなくU+FFE5に、\
(0x815F)はU+005CではなくU+FF3Cに、それぞれ変換する。
変換に揺れがある文字については以下に従う。
文字 | SJIS | 変換結果 | 参考:JIS X 0213 | 参考:CP932 |
---|---|---|---|---|
- |
0x815C | U+2014 | U+2014 | (U+2015) |
\ |
0x815F | U+FF3C | (U+005C) | U+FF3C |
~ |
0x8160 | U+301C | U+301C | (U+FF5E) |
‖ |
0x8161 | U+2016 | U+2016 | (U+2225) |
- |
0x817C | U+2212 | U+2212 | (U+FF0D) |
¥ |
0x818F | U+FFE5 | (U+FFE5 / U+00A5) | U+FFE5 |
¢ |
0x8191 | U+00A2 | U+00A2 | (U+FFE0) |
£ |
0x8192 | U+00A3 | U+00A3 | (U+FFE1) |
¬ |
0x81CA | U+00AC | U+00AC | (U+FFE2) |
前述の通り、バイト単位で保存される。
UCS→Shift_JISへの変換の場合、前述のShift_JIS→UCSの変換では使われない文字についても、Shift_JISに変換する場合には採用する。 つまり、UCSでは異なるコードポイントの文字が、Shift_JISでは同一の文字になる場合がある。
変換に揺れがある文字については以下に従う。
文字 | Unicode | 変換結果 |
---|---|---|
— |
U+2014 | 0x815C |
― |
U+2015 | 0x815C |
\ |
U+FF3C | 0x815F |
〜 |
U+301C | 0x8160 |
~ |
U+FF5E | 0x8160 |
‖ |
U+2016 | 0x8161 |
∥ |
U+2225 | 0x8161 |
− |
U+2212 | 0x817C |
- |
U+FF0D | 0x817C |
¥ |
U+FFE5 | 0x818F |
¥ |
U+00A5 | 0x818F |
¢ |
U+00A2 | 0x8191 |
¢ |
U+FFE0 | 0x8191 |
£ |
U+00A3 | 0x8192 |
£ |
U+FFE1 | 0x8192 |
¬ |
U+00AC | 0x81CA |
¬ |
U+FFE2 | 0x81CA |
上記の表から明らかな通り、複数の文字が同一の文字に変換されることがある。言い換えれば、Unicode→Shift_JIS→Unicodeのround trip conversionは保証されない。これは意図的なものである。
本案はあくまでShift_JISがマスターファイルの記述に使う基本エンコーディングとして考えている。そのため、Shift_JIS→Unicode→Shift_JISのround tripが成功することを期待しており、この変換ではこのround tripは実現されている。Unicodeはあくまで補助的・一時的なフォーマット用のエンコーディングであり、マスターとしての同一性が保持されていればよい。それよりも、Unicodeでの入力・加工時の利便性(例えばU+2014とU+2015のどちらでも入力できるようにする)を優先し、基本エンコーディングであるShift_JIS上にした際に揺れを吸収できていることが重要である。
表を使ってもよいが、以下のようにして一般的なCP932マッピングを部分的に使用することもできる。
- 範囲表を使って、Shift_JIS(!= Shift_JIS-2004)に含まれない文字を排除する
- CP932マッピングで変換する
- 以下の変換を行う
- U+2015→U+2014
- U+FF5E→U+301C
- U+2225→U+2016
- U+FF0D→U+2212
- U+FFE0→U+00A2
- U+FFE1→U+00A3
- U+FFE2→U+00AC
※Shift_JIS -> EUC-JIS-2004 -> UTF-8で変換すれば簡単そう?(要検証→余計面倒くさそうなので止めておく)
- 範囲表を使って、期待されるShift_JISに対応するUCSになっていない文字を排除する
- 以下の変換を行う
- U+2014→U+2015
- U+301C→U+FF5E
- U+2016→U+2225
- U+203E→U+007E
- U+2212→U+FF0D
- U+00A2→U+FFE0
- U+00A3→U+FFE1
- U+00A5→U+FFE5
- U+00AC→U+FFE2
- CP932マッピングで変換する
aozora-sjis2utf8.rb
#!/usr/bin/env ruby
if ARGV.size != 1
abort "usage: aozora-sjis2utf8.rb <filename>"
end
filename = ARGV[0]
s = File.read(filename, encoding: 'cp932')
u = s.encode("utf-8")
u.gsub!("\u2015", "\u2014")
u.gsub!("\uff5e", "\u301c")
u.gsub!("\u2225", "\u2016")
u.gsub!("\uff0d", "\u2212")
u.gsub!("\uffe0", "\u00a2")
u.gsub!("\uffe1", "\u00a3")
u.gsub!("\uffe2", "\u00ac")
print u
aozora-utf82sjis.rb
#!/usr/bin/env ruby
if ARGV.size != 1
abort "usage: aozora-utf82sjis.rb <filename>"
end
filename = ARGV[0]
u = File.read(filename, encoding: 'utf-8')
u.gsub!("\u2014"){ "\u2015" }
u.gsub!("\u301c"){ "\uff5e" }
u.gsub!("\u2016"){ "\u2225" }
u.gsub!("\u203e"){ "\u007e" }
u.gsub!("\u2212"){ "\uff0d" }
u.gsub!("\u00a2"){ "\uffe0" }
u.gsub!("\u00a3"){ "\uffe1" }
u.gsub!("\u00a5"){ "\uffe5" }
u.gsub!("\u00ac"){ "\uffe2" }
s = u.encode("cp932")
print s
- http://www.aozora.gr.jp/gaiji_chuki/
- http://www.asahi-net.or.jp/~wq6k-yn/code/enc-x0213.html
- http://x0213.org/codetable/
- http://www.unicode.org/charts/PDF/UFF00.pdf
- http://www.y-adagio.com/public/standards/tr_xml_jpf2/tutr.html
- http://www.asahi-net.or.jp/~wq6k-yn/code/jisucs.html
矢野啓介さんにコメントをいただきました。