utamaro’s blog

誰かの役に立つ情報を発信するブログ

lxmlを使ったxmlのパース方法

lxmlというライブラリを使ってxmlをパースしたときのメモを記事にしています。

lxmlはBeautifulSoupというスクレイピングのライブラリでも使われたりしなかったりします。

xmlファイルを取得する

pythonでファイルを読み込む方法はいろいろありますが、再帰的にファイルを取得しつつ、指定したディレクトリ以下のxmlファイルをすべて取得します。

from lxml import etree
from pathlib import Path

def get_xml_files(target):
    files = list(Path(target).glob('**/*.xml'))
    return files

get_xml_filesを実行すると、指定したディレクトリ以下のxmlファイルのパスがすべて取得できます。

このパスをlxmlで読み込んで、利用します。

lxmlでxmlをパースする

get_xml_filesを使って、ファイルまでのパスを取得後にパース処理をします。

xml_files = get_xml_files(xml_target)
for xml_file in xml_files:
    xml_file_name = str(xml_file)
    tree = etree.parse(xml_file_name)

xml_filePosixPothクラスのインスタンスなので、そのままetree.parseに渡せません。

一度strで文字列にしてからparseします。

コメントの削除方法

xmlのコメントが残っていると、forでループする際にcommentが引っかかります。

これを解消するために、removeしようと考えたのですが、remove系の関数がありませんでした。

理想ではtree.rm_comments()とかetree.rm_comments()があったらよかったです。

というわけで、対処方法が以下のプログラムです。

def remove_comments(tree):
    comments = tree.xpath('//comment()')
    for comment in comments:
        parent = comment.getparent()
        parent.remove(comment)

tagを判定する

以下のプログラムは、mybatisというjavaのormで使うxmlをパースする際に使った関数です。

xmlの中身はこのようなものです。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="jp.hoge.com">
    <select id="selectId" resultClass="Sample" parameterClass="string">
        SELECT
            id
        FROM
            sample
        WHERE
            id = 1
    </select>
</sqlMap>

このxmlをパースしてステートメントごとに配列にしています。

def get_statements(tree):
    statement_elements = []
    for element in tree.iter():
        if element.tag in ['select', 'insert', 'update', 'delete', 'procedure']:
            statement_elements.append(element)
    return statement_elements

属性を取得する際はelement.get('id')のようにします。

tree.iter()でelementを取得できますが、少し癖があります。

深さ優先探索のようにタグを取得していくので、書くのが難しかったです。

一度に欲しい情報を取得するのではなく、個別に取得する方法を採用しました。

例えば、以下のようなxmlがあった場合、sqlをdictに格納して、selectをパースしている最中にincludeを見つけたらdictを参照して、中身を置き換えます。

<sqlMap namespace="jp.hoge.com">
    <sql id="sampleInclude">
        where
            id = 2
    </sql>
    <select id="selectId" resultClass="Sample" parameterClass="string">
        SELECT
            id
        FROM
            sample
        <include refid="sampleInclude" />
    </select>
</sqlMap>