my funeral week

少しでも日々の生活に変化を。

Pythonにおける「Xml」の扱いのメモ( ..)φ

PythonXmlを扱いたい!

もちろん色々方法はあるみたい。
有名なところで言うと、ElementTreeやMinidomなんかでしょうかね・・・?
とりあえず今回のメモではElementTreeの利用について以下の項目で簡単にまとめます!


  • Xmlを作成する
  • Xmlをファイルへ書き込む
  • Xmlを開く(Parse)


最初に・・・

なんのあてもなく作成するのは、なんか嫌なので・・・
以下のようなXmlデータを作成するという体裁で進めますね。

<Root>
      <VideoGame name = "Fallout" dev= "BethesdaGameStudio">
            <Description>post apocalyps action role-playing open world</Description>
      </VideoGame>
      <VideoGame name = "Darksouls" dev = "FromSoftware">
            <Description>hardcore action role-playing game</Description>
      </VideoGame>
      <VideoGame name = "Watchdogs" dev = "ubiSoft">
            <Description>hacking action adventure</Description>
      </VideoGame>
</Root>


内容は・・・
Rootという要素の以下にVideoGameという要素、そしてさらにその以下にDescriptionというVideoGameの内容を的確に説明する要素があります。

そして、VideoGame要素には、属性としてnameとdev(開発会社)を付与しています。以上。

Xmlデータ自体の仕様や記法についての説明は省かせていただきます。


Xmlの作成

なにはともあれ、まずはモジュールのインポートからです。

import xml.etree.ElementTree as Et

ElementTreeのNamespaceは上記のようになっています。
今回は as による別名付けにて、Etとします。


では、さっそくXmlデータの作成。

root = Et.Element("Root") #Root要素の作成

videoGame = Et.SubElement(root, "VideoGame", {"name" : "Fallout"}) #サブ要素の作成

videoGame.set("dev", "BethesdaGameStudio") #属性の付与

description = Et.SubElement(videoGame, "Description")

description.text = "post apocalyps action role-playing open world" #要素内のテキストを作成

とりあえず、Rootの作成とVideoGame要素の一つ目にあるFalloutの項目を作成を表したものになります。


各所の解説

root = Et.Element("Root")

Xmlデータ直下の要素の作成。基本的にはRootという最上位の要素を作成することが多いんじゃないかな・・・?


videoGame = Et.SubElement(root, "VideoGame", {"name" : "Fallout"}) #サブ要素の作成

SubElement(特定の要素の子供になる要素)を作成。引数に与えているのは

  • 親要素
  • 要素名
  • 属性(属性名をKeyに、内容をValueとしたDict型)


videoGame.set("dev", "BethesdaGameStudio") #属性の付与

要素作成時でなくとも後から属性を付与することも可能。


description.text = "post apocalyps action role-playing open world" #要素内のテキストを作成

要素内のテキストを設定。これはメソッドではなく、プロパティなのに注意。


最終形の作成

作成に関する基本的な機能はこれくらいにして、とりあえず最終形のデータの制作を行います。

さっきのコードをちょっと編集して・・・

class VideoGame(object):
    
    def __init__(self, name, dev, desc):
        
        self.name = name
        
        self.dev = dev
        
        self.description = desc
        
vg_Fallout = VideoGame(
    name = "Fallout",
    dev = "BethesdaGameStudio",
    desc = "post apocalyps action role-playing open world"
    )
    
vg_DarkSouls = VideoGame(
    name = "Darksouls",
    dev = "FromSoftware",
    desc = "hardcore action role-playing game"
    )
    
vg_Watchdogs = VideoGame(
    name = "Watchdogs",
    dev = "ubiSoft",
    desc = "hacking action adventure game"
    )

root = Et.Element("Root")

for i in [vg_Fallout, vg_DarkSouls, vg_Watchdogs]:
    
    attrib = {"name" : i.name, "dev" : i.dev}
    
    videoGame = Et.SubElement(root, "VideoGame", attrib)
    
    description = Et.SubElement(videoGame, "Description")
    
    description.text = i.description

Python上ではVideoGame要素自体をクラス(役割的には構造体的な使い方)として扱います。
より要素をスクリプト上で扱いやすくするための一手間だと思ってください。
これは後にXmlデータを開く(Parse)するときにも役に立つと思います。

クラス(構造体)として扱うことで、Xml的には一律のSubElementとしてバラバラの扱いを受けているデータを、メインとなる要素毎に分割して扱うことができます。
少々文章が長くなってしまいますが、後のコーディングが楽になると思えば・・・。


Xmlをファイルに書き込む

データはこれで完成したので、実際にXmlファイルとしてディレクトリ上に生成しましょう。

ファイルの生成には以下のコードを利用できます。

filePath = "適当なファイルパス"

data = Et.ElementTree(root) #Root要素を指定

data.write(filePath, encoding = "utf-8")


ただ、この方法で生成されたXml

<Root><VideoGame dev="BethesdaGameStudio" name="Fallout"><Description>post apocalyps action role-playing open world</Description>......

こんな感じでインデントやスペースもなく、ずらっと一行にまとめられてしまい・・・とても見づらいです。

これを解決するにも、きっと色々方法が存在するのでしょうが、
私はいつもファイルへの書き込みの際だけは、ElementTreeではなく、minidomを利用しています。

def writeXml(xmlRoot, path):
    
    import xml.dom.minidom as md
    
    encode = "utf-8"
    
    xmlFile = open(path, "w")
    
    document = md.parseString(Et.tostring(xmlRoot, encode))
    
    document.writexml(
        xmlFile,
        encoding = encode,
        newl = "\n",
        indent = "",
        addindent = "\t"
    )

こんな感じの関数を用意しています。

document = md.parseString(Et.tostring(xmlRoot, encode))

ここにElementTreeで作成したデータを文字列として代入し、

document.writexml(
        xmlFile,
        encoding = encode,
        newl = "\n",
        indent = "",
        addindent = "\t"
)

にて、改行やインデントを指定して書き込みを行います。

なんかもっとシンプルで良い方法があれば是非コメント欄にて教えてください・・・。


Xmlを開く(Parse)

さて、今度は逆にXmlファイルをPythonで読み込んでみたいと思います。

読み込む際のコードはこちら

xmlFile = Et.parse(filePath) #XmlFileのPathを指定

これだけでとりあえず、データは読み込めます。


続いて各データの取り出し

root = xmlFile.getroot() #Rootの抽出

videoGameList = list()

for i in root.findall("VideoGame"): #VideoGame要素をすべて抽出
    
    name = i.get("name")
    
    dev = i.get("dev")
    
    description = i.find("Description").text
    
    videoGame = VideoGame(name, dev, description) #VideoGameクラスとして保存
    
    videoGameList.append(videoGame)


各所の解説

root = xmlFile.getroot() #Rootの抽出

読み込んだXmlデータからトップの要素を取得。まずはこれを行うことが基本でしょう。

root.findall("VideoGame")

続いてfindallにて引数に与えられた要素名(tag:タグとも言う)のものを 配列 にて取得。
そして、Descriptionを取得している箇所のfindは、引数の内容で検索してヒットした最初の要素を 単一のデータ として返します。

ちなみに各要素はタグと属性(Dict型)を持ちます。各値を取得するには、

要素.tag
要素.attrib

で取得できます。

name = i.get("name") #属性の取得
dev = i.get("dev") #属性の取得
description = i.find("Description").text #値の取得

get で引数に与えた属性名の値を取得し、 text で要素の値を取得しています。


きちんと取得できているか確認

取得したデータを文字列出力して確認します。

for vg in videoGameList:
    
    print "\n----------------------"
    
    print "Title : ", vg.name
    
    print "Dev : ", vg.dev
    
    print "Description : ", vg.description


VideoGameクラスを利用して各ゲームの情報を出力すると・・・

----------------------
Title :  Fallout
Dev :  BethesdaGameStudio
Description :  post apocalyps action role-playing open world

----------------------
Title :  Darksouls
Dev :  FromSoftware
Description :  hardcore action role-playing game

----------------------
Title :  Watchdogs
Dev :  ubiSoft
Description :  hacking action adventure

こんな感じ。




めっちゃ長くなった上に読みづらい箇所があるかも知れません・・・。

今のところ私がXmlを扱う際はこういう流れで行っています。

私の本業はプログラマではなく、あくまで素人の意見ですのでご参考までに!