LoginSignup
5

More than 5 years have passed since last update.

posted at

XML中に実体参照していない & がある場合、ElementTree.parse で失敗するが、文字列置換で強引に修復

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 引数は無く、同等の方法がわかりませんでした。

そのため、正規表現で & -> &amp; に強引に置換して処理していますが、現状うまくいっています。

こんなコードで置換しました。

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">ほげ&amp;ふが (ホゲ&amp;フガ)</dc:title>'
    >>> xml_repair('<a>a&b&c&amp;d&quot;e</a>')
    '<a>a&amp;b&amp;c&amp;d&quot;e</a>'
    """
    def _replace(matcher):
        return re_replace.sub('&amp;', matcher.group(0))

    return re_entity.sub(_replace, xml_source)

(今回の件では特に必要ではなかったため) > と < に挟まれている部分のみ対象にしましたが、もしかしてその判定は行わずにxml すべてに _replace をかけてもいいかもしれません。

また、& ではなく ' などが入ってきたらやっぱり失敗するように思いますが(未検証)、これも今回は不要だったため考慮はしていません。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5