LoginSignup
9
8

More than 5 years have passed since last update.

【Python】ディレクトリ構造をXML形式に変換する

Last updated at Posted at 2018-06-10

概要

PythonでXMLを便利に扱えるElementTreeの使い方を紹介します。
あと少し自作ツールのお披露目です。

経緯

業務で使用したIDEが、プロジェクトにソースコードを1ファイルずつしか追加できないという衝撃的な仕様だったため、設定ファイルのXMLを直接作成しようということになりました。

ElementTree(通称ET)

PythonでXMLをツリー構造として操作できるライブラリです。

PythonのチュートリアルではETという略称が公式で使われています。

XMLの構造を保持する

ETでは、ツリー構造の一つの要素をElementクラスを使って保持します。
Elementクラスは、下記の属性を持っています。

  • tag
    • XMLのタグを表す属性
  • text
    • 要素のデータ
  • tail
    • アプリケーション固有の付加的なデータ
    • XMLの閉じタグの後に置かれるイメージ
  • attrib
    • 要素の属性
  • SubElement
    • 要素の子要素(Element型)

XMLのイメージでいうと、それぞれ下記の部分に該当します。

<tag attrib="attrib">
  text
  <SubElement>
    SubText
  <SubElement>
</tag>tail

XMLを構築する

ETを使って、XMLをツリー構造として構築していきます。
まずは、Element()メソッドでルートとなる要素を生成します。

import xml.etree.ElementTree as ET

root = ET.Element('root')   # rootタグを生成

データはtextに直接代入することで追加します。

root.text = 'data'

子要素はSubElement()で追加できます。
もしくは、Elementに直接append()を使って追加することもできます。

SubElement()は追加した要素を返してくれるので、要素の追加と取得を一度にできてお得です。

import xml.etree.ElementTree as ET

root = ET.Element('root')   # rootタグを生成
root.text = 'data'  # rootのデータとしてdataを設定

sub = ET.SubElement(root, 'sub')    # rootの子要素としてsubを追加
# root.append(ET.Element('sub'))    # こちらでも可能

要素の検索

ツリー構造の要素を検索するためにはElementクラスのfindall()メソッドもしくはfind()メソッドを利用します。
二つの違いは、findall()は指定したタグをすべて取得しますが、find()メソッドは最初に見つかった一つだけを取得します。

import xml.etree.ElementTree as ET

root = ET.Element('root')
sub1 = ET.SubElement(root, 'sub')
sub2 = ET.SubElement(root, 'sub')
sub3 = ET.SubElement(root, 'sub')

# findall()はリストが返ってくる
for ele in root.findall('sub'):
    ele.text = 'data'

# find()では最初の一つだけ
subEle = root.find('sub')
subEle.text = 'first'

ファイル入出力

ETはファイルからXMLの構造を読み込むこともできます。

import xml.etree.ElementTree as ET

ET.parse('example.xml') # example.xmlをツリー構造として取り込む

自分で作成したツリー構造をファイルに出力するためには、文字列に変換してからファイルに出力します。
文字列に変換するためにはtostring()メソッドを使います。

import xml.etree.ElementTree as ET

root = ET.Element('root')   # rootタグを生成
root.text = 'data'  # rootのデータとしてdataを設定

sub = ET.SubElement(root, 'sub')    # rootの子要素としてsubを追加
# root.append(ET.Element('sub'))    # こちらでも可能

fp = open('output.xml', 'w')
fp.write(ET.tostring(root, 'unicode'))  # 文字列に変換してからファイル出力

作成したツール

これまでの内容を使って、ディレクトリ構造をXML形式に変換するツールを作成しました。
業務で使っているIDEの環境に合わせて作っているので、あまり汎用的なツールになってはいませんが、参考になれば幸いです。

Dir2XML.py
# -*- coding: utf-8 -*-

import xml.etree.ElementTree as ET
import os
import sys
import re

# Define constants
tag_group='group'
tag_name='name'
tag_file='file'

# Main function
def main():
    if len(sys.argv) < 2:
        print('Please input the directory path which is as root: ')
        root_path = sys.stdin.readline().rstrip()
    else:
        root_path = sys.argv[1]

    root = ET.Element('root')

    print('Converting Directories to XML...')

    ele_curr = root
    for path, directories, files in os.walk(root_path):
        group_path = re.sub('^\\' + os.path.sep, '', path.replace(root_path, ''))
        if group_path != '':
            # the root path has no name, so skipped.
            ele_curr = find_named_element(root, group_path)
        for dir in directories:
            ele_curr.append(create_new_group(dir))
        for file in files:
            # This statement is specified for my tool.
            # $PROJ_DIR$ is context root for my tool.
            ele_curr.append(create_new_file(os.path.join('$PROJ_DIR$', group_path, file)))

    print('Convertion Completed!')

    output_file_name = os.path.join(root_path, 'result.xml')
    print('Output File : %s' %(output_file_name))
    output_to_file(root, output_file_name)

# Define functions
def find_named_element(ele, name):
    name_list = name.split(os.path.sep)
    ele_curr = ele
    for target in name_list:
        ele_pre = ele_curr
        for group in ele_curr.findall(tag_group):
            ele_name = group.find(tag_name)
            if ele_name.text == target:
                ele_curr = group
                break
        if ele_curr is ele_pre:
            ele_new = create_new_group(target)
            ele_curr.append(ele_new)
            ele_curr = ele_new

    return ele_curr

def create_new_element(name, tag):
    ele = ET.Element(tag)
    ele_name = ET.SubElement(ele, tag_name)
    ele_name.text = name

    return ele

def create_new_group(name):
    return create_new_element(name, tag_group)

def create_new_file(name):
    return create_new_element(name, tag_file)

def output_to_file(element, file_name):
    fp = open(file_name, 'w')
    fp.write(ET.tostring(element, 'unicode'))

main()

下記のディレクトリexampleをXML形式に変換してみます。

C:\EXAMPLE
│  file1.txt
│  file2.txt
│  
├─SubDir1
│      file1.txt
│      file2.txt
│      
└─SubDir2
    └─SubDir3
            file1.txt
            file2.txt

c:\>Dir2XML.py .\example
result.xml
<root>
  <group>
    <name>SubDir1</name>
    <file>
      <name>$PROJ_DIR$\SubDir1\file1.txt</name>
    </file>
    <file>
      <name>$PROJ_DIR$\SubDir1\file2.txt</name>
    </file>
  </group>
  <group>
    <name>SubDir2</name>
    <group>
      <name>SubDir3</name>
      <file>
        <name>$PROJ_DIR$\SubDir2\SubDir3\file1.txt</name>
      </file>
      <file>
        <name>$PROJ_DIR$\SubDir2\SubDir3\file2.txt</name>
      </file>
    </group>
  </group>
  <file>
    <name>$PROJ_DIR$\file1.txt</name>
  </file>
  <file>
    <name>$PROJ_DIR$\file2.txt</name>
  </file>
</root>

result.xmlは表示用に整形しています。実際は1行にすべて出力されています。

GitHubにも公開しています。
Dir2XML

9
8
2

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
9
8