Python3でXML をパースしようと、
from xml.etree import ElementTree
ElementTree.parse(xml_path)
した所、
xml.etree.ElementTree.ParseError: not well-formed (invalid token): line *, column *
の例外が出て、処理が失敗しました。
XML (今回のは とあるEPUB の content xml )を読んだ所、
<dc:title id="title">ほげほげ&ふがふが</dc:title>
となっており、この & が実体参照されていないため失敗した様子。
lxml を使っている場合は、
python - ParseError: not well-formed (invalid token) using cElementTree - Stack Overflow
ここにあるように、Parser クラスのコンストラクタに recover=True
をつけることでいけそうですが、Python3 の xml.etree.ElementTree.XMLParser
には recover
引数は無く、同等の方法がわかりませんでした。
そのため、正規表現で &
-> &
に強引に置換して処理していますが、現状うまくいっています。
こんなコードで置換しました。
re_entity = re.compile(r'(>[^<]*)(&)([^<]*<)')
re_replace = re.compile(r'&(?!\w*?;)')
def xml_repair(xml_source):
"""
>>> xml_repair('<dc:title id="title">ほげ&ふが (ホゲ&フガ)</dc:title>')
'<dc:title id="title">ほげ&ふが (ホゲ&フガ)</dc:title>'
>>> xml_repair('<a>a&b&c&d"e</a>')
'<a>a&b&c&d"e</a>'
"""
def _replace(matcher):
return re_replace.sub('&', matcher.group(0))
return re_entity.sub(_replace, xml_source)
(今回の件では特に必要ではなかったため) > と < に挟まれている部分のみ対象にしましたが、もしかしてその判定は行わずにxml すべてに _replace
をかけてもいいかもしれません。
また、& ではなく ' などが入ってきたらやっぱり失敗するように思いますが(未検証)、これも今回は不要だったため考慮はしていません。