Genshiの文字化け

開発したWebアプリケーションで時折文字化けが起こるので調べてみたら原因が分かった。多分Genshiのバグだと思う。

  • HTMLParser.parse() in genshi/input.py:
        def _generate():
            try:
                bufsize = 4 * 1024 # 4K
                done = False
                while 1:
                    while not done and len(self._queue) == 0:
                        data = self.source.read(bufsize)

これだ!

検証コード

>>> from genshi.input import HTML
>>> unicode_text = u"あ" * 5000
>>> [(i, c) for i, c in enumerate(str(HTML(unicode_text)).decode('utf-8')) if c != u"あ"]
[]
>>> utf8_text = unicode_text.encode('utf-8')
>>> [(i, c) for i, c in enumerate(str(HTML(utf8_text)).decode('utf-8')) if c != u"あ"]
[(1365, u'\ufffd'), (1366, u'\ufffd'), (1367, u'\ufffd'), (2732, u'\ufffd'), (2733, u'\ufffd')]

utf-8を渡した場合、バッファ境界でマルチバイト文字の「泣き別れ」が起こっていることが分かる。

  • 1365*3=4095=4K
  • 2732*3=8196=8K

補足

文字化けはToscaWidgetsを使った一部の編集画面でのみ起こっていて、どうやら

  • WidgetRepeater.update_params() in toscawidgets/core.py
        outputs = [
            w.render(v_f(w), **a_f(w)) for w in d['children'][:d.repetitions]
            ]
        d["output"] = '\n'.join(o for o in outputs if o)    

ここでutf-8文字列化しているために上記のGenshiのバグをピンポイントで突いてしまったようだ。

とりあえずの回避策:

from toscawidgets.widgets.forms import *

_FormFieldRepeater = FormFieldRepeater
class FormFieldRepeater(_FormFieldRepeater):
    def update_params(self, d):
        super(_FormFieldRepeater, self).update_params(d)
        # Genshi の潜在的なバグを回避するために, 強制的に unicode に変換する
        d["output"] = d["output"].decode('utf-8')