✍️blog

技術系のこととか

PythonでExcelを読んでXMLを吐きたい

テスト管理を行うツールにTestLinkというものがあります。

testlink.org

このツールに関する説明は他のサイトにお任せするとして、
TestLinkにテストを登録するには、手動で1件ずつ手作業で入力していくか、 XML形式のものをインポートするかいずれかの方法を使用します。

Excel(所謂テスト仕様書)からXMLに変換するツールは探せば割と出てくるのですが、
たいていマクロ付きのExcelです。

別にいいんですが、マクロ付きのExcelってメンテナンス性微妙だと思うんですよね。。。

ということで、Pythonで変換するツールを作成する中で知った内容をまとめます。

Excelの読み込みには「openpyxl(3.0.10) 」を使用しています。

openpyxl.readthedocs.io

Excelファイルの読み込み

openpyxl.load_workbook("path/to/excel_file", data_only=True, read_only=True)

data_only: Trueにするとセル内の式の結果を読み込み。Falseだと式そのものを読み込み となります。
read_only: 読み取り専用ですね。

他にもオプションがありましたけど、使ったのはこれぐらいですね。

すべてのシートの取得

book.worksheets

すべてのシートがリストで取得できます。

シートの属性取得

sheet = book.worksheets[0] 

print(f'シート名:{sheet.title}')
print(f'シートの最大行数:{sheet.max_row}')
print(f'シートの最大列数:{sheet.max_column}')

if sheet.sheet_state != 'visible':
    print('隠しシートだね')

シート名や最大行数、列数などが取得できます。
なお最大行数はExcelとしての最大行数ではなく、そのシートの一番端の値があるセル行番号です。
また、非表示のシートも取得できます。

セルの値の取得

row = 1
col = 10

sheet[row][col].value

これだけですw

そういえば、XMLについて書くのを忘れていました。。。

xml.etree.ElementTreeを使ったのですが、
これはPython標準なのでまぁググれば腐るほど情報は出てくるでしょうということで1つだけ情報を。

docs.python.org

xml.etree.ElementTreeでCDATAを作成したい

ElementTreeでは要素を以下のような感じで作成します。

ET.Element("tag")

そうすると以下のようなXMLになります。

<tag />

これに子要素を追加しながら組み立てていくのですが、CDATAをそのままでは作成できません。

CADATAとは以下のようなやつで、XMLで特別意味をもつ記号等をテキストとして設定したい際に
文字参照に変換せずに値を設定する方法です。

<tag><![CDATA[   「<html>」を表示したい  ]]></tag>

これは、ElementTreeでは実現できません。(たぶん)
上記のXMLをそのままElmentTreeで表現しようとすると以下のようになります。

tag = ET.Element("tag")
tag.text = "<![CDATA[   「<html>」を表示したい  ]]>"

これをXML文字列に変換すると以下のようなXMLになってしまいます。

<tag>&lt;![CDATA[   &#12300;&lt;html&gt;&#12301;&#12434;&#34920;&#31034;&#12375;&#12383;&#12356;  ]]&gt;</tag>

CDATAの開始部分が <![CDATA[から&lt;![CDATA[に代わってしまっています。

これでは、ダメなのでCDATAが作成できるようにElementTreeを拡張します。(強引にw)

かなり前に私と同じ壁に当たった方がいたようです。
zlalanneさんのGistに以下のスクリプトがあります。

gist.github.com

ただし、このスクリプトは少なくともPython3.9では動かないようなので以下のように調整してあげます。

def CDATA(text=None):
    element = ET.Element('![CDATA[')
    element.text = text
    return element

ET._original_serialize_xml = ET._serialize_xml


def _serialize_xml(write, elem, qnames, namespaces, short_empty_elements, **kwargs):
    if elem.tag == '![CDATA[':
        write("<%s%s]]>" % (elem.tag, elem.text))
        return
    return ET._original_serialize_xml(write, elem, qnames, namespaces, short_empty_elements, **kwargs)

ET._serialize_xml = ET._serialize['xml'] = _serialize_xml

標準の機能を汚染しているので、プロダクションに使用する場合には注意が必要ですね。

これで、以下のようにすることでCDATAを作成することができます。

tag = ET.Element("tag")
tag.append(CDATA("   「<html>」を表示したい  "))

以下のようなXMLになります。

<tag><![CDATA[   &#12300;<html>&#12301;&#12434;&#34920;&#31034;&#12375;&#12383;&#12356;  ]]></tag>

この手の地味なスクリプトって作り始める前は簡単だろうって思うんですけど、
実際に作り始めるとハマりポイントが割とあるんですよね。
まだまだ精進が足りませんねw

この記事書いてて思ったんですが、ExcelってPandasでも読めますよね
テスト仕様書ならフォーマットにもよるけど普通に読めるんじゃ...
pandasなら楽ができたかも。。。

via GIPHY