xml.parsers.expatのよくわからない挙動 - 解決

xml.parsers.expatのよくわからない挙動で取り上げた内容をPython issue Trackerリポートしてみたら、xml.parsers.expatのバグではなく私のコードのバグであることをkawaiさんという方に指摘していただいた。結局「CharacterDataHandler」は渡されたデータを一回で読み込むとは限らない(SAXの仕様)ので、単にデータを渡すようにすると2回目以降に渡したデータで1回目に渡したデータを上書きてしまうのが原因らしい。(リンク先「The ContentHandler.characters() callback is missing data!」参照。)これを避けるために「CharacterDataHandler」に文字列を渡すときは「+=」で追加していく様にし、「StartElementHandler」で"link"要素の始めに来たときにクリアするようにする。

# -*- coding: utf-8 -*-
from __future__ import with_statement
from xml.parsers.expat import ParserCreate

_cnt = ""
_name = ""
_data = ""
_dict = {}

def make(buf=False):
  """Make database which is accessible by Databese.dict"""
  global _cnt
  global _name
  global _data
  global _dict
  map_file = "/usr/share/icon-naming-utils/legacy-icon-mapping.xml"

  p = ParserCreate()
  p.buffer_text = buf
  p.StartElementHandler = start_element
  p.EndElementHandler = end_element
  p.CharacterDataHandler = char_data

  try:
    with open(map_file, 'rb') as f:
      p.ParseFile(f)
  except IOError,  err:
    print err

  return _dict

def start_element(name, attrs):
  global _cnt
  global _name
  global _data
  if name == 'context':
    _cnt = attrs["dir"]
  if name == 'icon':
    _name = attrs["name"]
    _dict[_name] = (_cnt, _name)
  if name == 'link':
    _data = ''   # _dataをクリアする

def end_element(name):
  global _cnt
  global _name
  global _data
  if name == 'link':
    _dict[_data] = (_cnt, _name)

def char_data(data):
  global _data
  _data += data    # _data に取得した文字列を追加する

kawaiさん有難うございました。


追記2009/01/27
"_data"のクリアをend_element()からstart_element()へ移動。さもないとスペースやタブ、改行が"_data"に入ってしまう。