突然 Planet で収集・再構築したフィードが XML の構文エラーでパースできない事態が発生。
問題の行を1文字ずつダンプして調べてみたところ、0x03
(ETX/テキスト終了) が紛れ込んでいました。UTF-8 としては ETX を含んでいても問題無いが、well-formed な XML としてはダメらしいのですね。
そこで TAB,CR,LF 以外の制御文字を削除するように planet/cache.py を修正。(差分には EUC/SJIS 対応も含んでいます)
ETX を含んでいたフィードのキャッシュを削除した後、planet.py を再実行すると、問題なくパースできるようになりました。
diff -ur planet.orig/planet/cache.py planet/planet/cache.py --- planet.orig/planet/cache.py +++ planet/planet/cache.py @@ -19,6 +19,7 @@ re_slash = re.compile(r'[?/]+') re_initial_cruft = re.compile(r'^[,.]*') re_final_cruft = re.compile(r'[,.]*$') +re_ctrl_chars = re.compile(r'[\x00-\x08\x0b\x0c\x0e-\x1f]') class CachedInfo: @@ -295,12 +296,18 @@ def utf8(value): """Return the value as a UTF-8 string.""" if type(value) == type(u''): - return value.encode("utf-8") + return re_ctrl_chars.sub("", value.encode("utf-8")) else: try: - return unicode(value, "utf-8").encode("utf-8") + return re_ctrl_chars.sub("", unicode(value, "utf-8").encode("utf-8")) except UnicodeError: try: - return unicode(value, "iso-8859-1").encode("utf-8") + return re_ctrl_chars.sub("", unicode(value, "euc-jp").encode("utf-8")) except UnicodeError: - return unicode(value, "ascii", "replace").encode("utf-8") + try: + return re_ctrl_chars.sub("", unicode(value, "cp932").encode("utf-8")) + except UnicodeError: + try: + return re_ctrl_chars.sub("", unicode(value, "iso-8859-1").encode("utf-8")) + except UnicodeError: + return re_ctrl_chars.sub("", unicode(value, "ascii", "replace").encode("utf-8"))
あと、planet の設定とフィード一覧の分離は以下のような、安直な方法で。
list.ini を編集する Web インターフェースも PHP で作っていたりするのですが、それはまた別の機会にでも。
diff -ur planet.orig/planet.py planet/planet.py --- planet.orig/planet.py +++ planet/planet.py @@ -22,10 +22,11 @@ import planet -from ConfigParser import ConfigParser +from ConfigParser import SafeConfigParser as ConfigParser # Default configuration file path CONFIG_FILE = "config.ini" +LIST_FILE = "list.ini" # Defaults for the [Planet] config section PLANET_NAME = "Unconfigured Planet" @@ -93,6 +94,15 @@ feed_timeout = config_get(config, "Planet", "feed_timeout", FEED_TIMEOUT) template_files = config_get(config, "Planet", "template_files", TEMPLATE_FILES).split(" ") + + # Read the feed list and append each item to the configuration + config2 = ConfigParser() + config2.read(LIST_FILE) + for site in config2.sections(): + if config2.has_option(site, "name"): + if not config.has_section(site): + config.add_section(site) + config.set(site, "name", config2.get(site, "name")) # Default feed to the first feed for which there is a template if not planet_feed: