突然 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: