あいつの日誌β

働きながら旅しています。

Microsoft Notepad に対応するための csv を出力する

あらすじ

基本 json で返す web api を csv 形式で出力することになりました。 フロント側で format を csv に変換する仕組みを導入したとろこ WindowsExcel で不具合が発生します。 ということで CSV という拡張子の TSV を UTF-16LE で返すエンドポイントを作成することになりました。

何が何だかわかりませんが言われた通りに作業する事にします。

開発環境

% python --version
Python 2.7.3

余談

Python2 では str 型と unicode 型が混在します。

# -*- coding: utf-8 -*-

print type('あいうえお')   # <type 'str'>
print type(u'あいうえお')  # <type 'unicode'>

そして str 型も unicode 型のいずれも encode, decode という関数が使えます。

# -*- coding: utf-8 -*-
  
print type('あいうえお'.decode('utf_8'))   # <type 'unicode'>
print type(u'あいうえお'.decode('utf_8'))  # <type 'unicode'>
print type('あいうえお'.encode('utf_8'))   # <type 'str'>
print type(u'あいうえお'.encode('utf_8'))  # <type 'str'>

本題

str 型も unicode 型にも encode, decode という関数が使えるのでを文字コードを変換する処理は以下のように書けます。

# -*- coding: utf-8 -*-

print type('あいうえお'.decode('utf_8'))  # <type 'unicode'>
print type('あいうえお'.encode('utf_8'))  # <type 'str'>

さて、ちょっとややこしいのですが今回必要な str 型は以下の条件を満たすものです。

UTF-16LE でエンコードされた str 型で文字列の先頭に UTF-8 の BOM を含む

http://docs.python.jp/2/library/codecs.html#unicode

Microsoft は Notepad プログラム用に UTF-8 の変種 (Python 2.5 では "utf-8-sig" と呼んでいます) を考案しました。まだ Unicode 文字がファイルに書き込まれない前に UTF-8エンコードした BOM (バイト列では 0xef, 0xbb, 0xbf のように見えます) を書き込んでしまいます。

というわけで Python2 でこの 先頭に UTF-8 BOM を含む UTF-16LE な文字列は以下のように書くことができます。

# -*- coding: utf-8 -*-
import codecs

text1 = (codecs.BOM_UTF8 + 'あいうえお').encode('utf_16_le')
text2 = 'あいうえお'.encode('utf_8_sig').encode('utf_16_le')
print text1 == text2

テストケースなどで上記BOM を削除したい場合は以下のようします。 ポイントは unicode ではなく byte の状態で先頭の BOM 以降を取り出すところです。

# -*- coding: utf-8 -*-
import codecs

text0 = 'あいうえお'
text1 = (codecs.BOM_UTF8 + 'あいうえお').encode('utf_16_le')

utf8_text = text1.decode('utf_16_le').encode('utf8')

# どちらでも好きなほうで、
print utf8_text[len(codecs.BOM_UTF8):] == text0
print utf8_text[len(bytearray([0xEF, 0XBB, 0XBF])):] == text0

Microsoft Crazy:(