LoginSignup
6
5

More than 5 years have passed since last update.

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

Posted at

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 をかけてもいいかもしれません。

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

6
5
0

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
  3. You can use dark theme
What you can do with signing up
6
5