tklib.tkxml のソースコード

"""
tkxml.py

概要: XMLデータの解析、変換、操作を支援するユーティリティ機能を提供します。

詳細説明:
このモジュールは、Pythonの標準ライブラリであるxml.etree.ElementTreeやxml.dom.minidomに加え、
外部ライブラリのxmltodict、dicttoxml、numpyを利用して、XMLファイルとPythonの辞書構造間の
相互変換、XML要素の再帰的な解析、特定のセクション情報の抽出、整形されたXMLの書き出しなど、
XML操作に関する幅広い機能を提供します。
ATLAS最適化XMLやVASP計算結果XML (vasprun.xml) などの特定フォーマットのXML解析例も含まれています。

関連リンク:
:doc:`tkxml_usage` (もし関連ドキュメントがあれば)
"""
import os
import sys
import xml.etree.ElementTree as ET
import xml.dom.minidom as md
import numpy as np

import xmltodict
import dicttoxml


infile = 'opt_2onl.xml'

nargs = len(sys.argv)
if nargs > 1:
    infile = sys.argv[1]


[ドキュメント] def get_elements_recursive(element): """ 概要: XML要素の子要素の情報を再帰的に取得します。 詳細説明: 入力されたXML要素が子要素を持たない場合、その要素のテキスト内容を返します。 子要素を持つ場合、各子要素のタグ、テキスト、属性を抽出し、さらにその子要素に対して 再帰的にこの関数を呼び出して情報を収集します。 収集された情報は、辞書のリストとして返されます。 :param element: 処理対象のxml.etree.ElementTree.Elementオブジェクト。 :returns: 子要素がない場合は要素のテキスト (str)、 子要素がある場合は子要素情報のリスト (list[dict])。 """ ne = len(element) if ne == 0 or element.text is None: return element.text inf = [] # if element has child elements for child_element in element: tag = child_element.tag ctext = child_element.text attrib = child_element.attrib child_inf = get_elements_recursive(child_element) if type(child_inf) is str: inf.append({"tag": tag, "text": ctext, "attrib": attrib}) else: inf.append({"tag": tag, "text": ctext, "attrib": attrib, "child": child_inf}) return inf
[ドキュメント] def get_unique_key(d, section, val = None): """ 概要: 辞書内でユニークなキー名を生成します。 詳細説明: 指定された`section`と`val`に基づいてキーを生成し、辞書`d`にそのキーが存在しない場合は そのまま返します。既に存在する場合は、`section[i]:val`または`section[i]`の形式で インデックスを付加し、ユニークなキーが見つかるまで試行します。 :param d: キーの存在を確認する辞書。 :param section: キーの基本となるセクション名。 :param val: (オプション) セクションに付加する値。デフォルトはNone。 :returns: 辞書内でユニークなキー文字列。 """ if val is None: key = section else: key = f"{section}:{val}" if d.get(key, None) is None: return key i = 0 while True: if val is None: key = f"{section}[{i}]" else: key = f"{section}[{i}]:{val}" if d.get(key, None) is None: return key i += 1
[ドキュメント] def get_section_inf(parent, sections = None, inf = None, params = None, ret_type = 'list', add_parent_params = True): """ 概要: 指定されたセクションパスに沿ってXML要素の情報を取得します。 詳細説明: `parent`要素から`sections`リストで指定されたパスを辿り、最後のセクションの子要素の情報を取得します。 `ret_type`が'list'の場合はリストで、'dict'の場合は辞書で情報を返します。 途中の親要素の属性も`params_child`として収集され、`add_parent_params`がTrueの場合、 最終的な戻り値に含められます。 :param parent: 検索を開始するxml.etree.ElementTree.Elementオブジェクト。 :param sections: (オプション) 検索する子要素のタグ名のリスト。デフォルトはNone。 :param inf: (オプション) 情報を追加する既存のリストまたは辞書。関数内でコピーして使用します。デフォルトはNone。 :param params: (オプション) 親要素から引き継がれるパラメータの辞書。デフォルトはNone。 :param ret_type: (オプション) 戻り値の型 ('list'または'dict')。デフォルトは'list'。 :param add_parent_params: (オプション) 親のパラメータを戻り値に含めるかどうか。デフォルトはTrue。 :returns: 子要素のタグ、テキスト、属性、または子要素の属性情報を含む辞書/リスト。 """ if inf is None: if ret_type == 'list': inf_child = [] else: inf_child = {} else: inf_child = inf.copy() if sections is None or len(sections) == 0: return inf_child ns = len(sections) if params is None: params_child = {} else: params_child = params.copy() section_list = parent for i in range(ns - 1): section_list = section_list.find(sections[i]) if section_list is None: return None for _key, _val in section_list.items(): key = get_unique_key(params_child, sections[i], _key) params_child[key] = _val sections_last = section_list.findall(sections[-1]) for parameter in sections_last: keys = parameter.keys() if len(keys) > 0: pkey = parameter.get(keys[0], None) _inf = {} for k, v in parameter.items(): _inf[k] = v else: pkey = "list" _inf = get_elements_recursive(parameter) if ret_type == 'list': inf_child.append(_inf) else: inf_child[pkey] = _inf if add_parent_params: if ret_type == 'list': inf_child.append(params_child) else: inf_child["params"] = params_child return inf_child
[ドキュメント] def get_section_inf_all(parent, sections = None, section_parent = 'root', inf = None, level = 0, params = None, pkey = None, ret_type = 'list', last_node_only = True): """ 概要: 指定されたセクションパスに沿ってXML要素の情報を再帰的に取得します。 詳細説明: `parent`要素から`sections`リストで指定されたパスを辿り、各階層の子要素の情報を取得します。 `last_node_only`がFalseの場合、中間ノードの情報も収集します。 `ret_type`が'list'の場合はリストで、'dict'の場合は辞書で情報を返します。 親要素の属性は`params_child`として引き継がれ、各ノードの情報に含まれます。 :param parent: 検索を開始するxml.etree.ElementTree.Elementオブジェクト。 :param sections: (オプション) 検索する子要素のタグ名のリスト。デフォルトはNone。 :param section_parent: (オプション) 現在の`parent`要素のタグ名(ユニークキー生成用)。デフォルトは'root'。 :param inf: (オプション) 情報を追加する既存のリストまたは辞書。デフォルトはNone。 :param level: (オプション) 現在の再帰レベル。デフォルトは0。 :param params: (オプション) 親要素から引き継がれるパラメータの辞書。デフォルトはNone。 :param pkey: (オプション) `ret_type`が'dict'の場合にキーとして使用する属性名。デフォルトはNone。 :param ret_type: (オプション) 戻り値の型 ('list'または'dict')。デフォルトは'list'。 :param last_node_only: (オプション) 最後のセクションのノードのみを収集するか、すべてのノードを収集するか。デフォルトはTrue。 :returns: 子要素のタグ、テキスト、属性、および親要素のパラメータを含む辞書/リスト。 """ # ret_type = 'list' if inf is None: if ret_type == 'list': inf = [] else: inf = {} if sections is None or len(sections) == 0: return inf ns = len(sections) if params is None: params_child = {} else: params_child = params.copy() for _key, _val in parent.items(): key = get_unique_key(params_child, section_parent, _key) params_child[key] = _val # print(f"level={level} sections={sections} sections[0]={sections[0]} param={params_child}") section_list = parent.findall(sections[0]) for i, section in enumerate(section_list): if len(sections) == 0: print(f" **return len(inf)={len(inf)}") return inf tag = section.tag ctext = section.text attrib = section.attrib keys = section.keys() if ret_type == 'dict': if len(keys) == 0: _pkey = 'list' else: if pkey is None: _pkey = keys[0] else: _pkey = pkey _pkey = get_unique_key(inf, section.get(_pkey, keys[0]), None) _inf = {"level": level, "tag": tag, "text": ctext, "attrib": attrib, "params": params_child} if not last_node_only or len(sections) == 1: if ret_type == 'list': inf.append(_inf) else: inf[_pkey] = _inf inf_ret = get_section_inf_all(section, section_parent = sections[0], sections = sections[1:], level = level + 1, inf = inf, params = params_child, pkey = pkey, ret_type = ret_type, last_node_only = last_node_only) # print(" in for: i=", i, " _inf=", _inf) return inf
[ドキュメント] def xml2dict(xml): """ 概要: XML文字列またはElementTree要素を辞書に変換します。 詳細説明: xmltodictライブラリを使用して、入力されたXMLデータをPythonの辞書構造に変換します。 :param xml: 変換するXML文字列。 :returns: 変換された辞書。 """ return xmltodict.parse(xml)
def _element_to_dict(element): """ 概要: xml.etree.ElementTreeのElementオブジェクトを再帰的に辞書に変換します。 詳細説明: 要素の属性は '@attributes' キーの下に、テキスト内容は '#text' キーの下に格納されます。 同じタグ名の子要素が複数ある場合はリストとして扱われます。 これはxmltodictの代替または補助的な役割を果たす内部関数です。 :param element: 変換するxml.etree.ElementTree.Elementオブジェクト。 :returns: 変換された辞書。 """ result = {} # 要素の属性を処理 if element.attrib: result['@attributes'] = element.attrib # 要素のテキスト内容を処理 if element.text and element.text.strip(): result['#text'] = element.text.strip() # 子要素を処理 for child in element: child_dict = _element_to_dict(child) if child.tag in result: # 同じタグ名の子要素が複数ある場合、リストに追加 if not isinstance(result[child.tag], list): result[child.tag] = [result[child.tag]] result[child.tag].append(child_dict) else: # 新しいタグ名の子要素の場合 result[child.tag] = child_dict return result
[ドキュメント] def file2dict(infile): """ 概要: 指定されたファイルパスのXMLファイルを読み込み、辞書に変換します。 詳細説明: 指定されたパスからXMLファイルを読み込み、その内容をxml2dict関数を使って辞書形式に変換します。 :param infile: 読み込むXMLファイルのパス。 :returns: 変換された辞書。 """ with open(infile) as fp: xml = fp.read() return xml2dict(xml)
[ドキュメント] def dict2xml(d, attr_type = True, root = True): """ 概要: 辞書をXMLインスタンスに変換します。 詳細説明: dicttoxmlライブラリを使用して、入力されたPythonの辞書をXMLインスタンスに変換します。 :param d: 変換する辞書。 :param attr_type: (オプション) 辞書のキーをXMLタグの属性として扱うかどうか。デフォルトはTrue。 :param root: (オプション) 生成されるXMLにルート要素を含めるかどうか。デフォルトはTrue。 :returns: 変換されたXMLインスタンス (bytes)。 """ return dicttoxml.dicttoxml(d, attr_type = attr_type, root = root)
[ドキュメント] def to_xml(outfile, element, encoding = 'utf-8', newl = '', indent = '', addindent = ' ', xml_declaration = True, use_minidom = False): """ 概要: XML要素をファイルに書き出します。 詳細説明: `use_minidom`がTrueの場合、`xml.dom.minidom`を使用してXMLを整形(インデントと改行を追加)して ファイルに書き出します。それ以外の場合は、`xml.etree.ElementTree`を使用して直接書き出します。 :param outfile: 書き込み先のファイルパス。 :param element: 書き出すルートのxml.etree.ElementTree.Elementオブジェクト。 :param encoding: (オプション) 出力ファイルのエンコーディング。デフォルトは'utf-8'。 :param newl: (オプション) `use_minidom`がTrueの場合の改行文字。デフォルトは''。 :param indent: (オプション) `use_minidom`がTrueの場合のインデント文字列。デフォルトは''。 :param addindent: (オプション) `use_minidom`がTrueの場合の追加インデント文字列。デフォルトは' '。 :param xml_declaration: (オプション) XML宣言 (<?xml version="...">) を含めるかどうか。デフォルトはTrue。 :param use_minidom: (オプション) `xml.dom.minidom`を使用してXMLを整形するかどうか。デフォルトはFalse。 :returns: None """ if not use_minidom: tree = ET.ElementTree(element) tree.write(outfile, encoding = encoding, xml_declaration = xml_declaration) else: doc = md.parseString(ET.tostring(element, encoding = encoding)) fp = open(outfile, 'w') doc.writexml(fp, encoding = encoding, newl = newl, indent = indent, addindent = addindent) fp.close()
[ドキュメント] def get_attrib(element): """ 概要: XML要素のタグ、テキスト、および属性を取得します。 :param element: 処理対象のxml.etree.ElementTree.Elementオブジェクト。 :returns: 要素のタグ (str)、テキスト内容 (str)、属性の辞書 (dict)。 """ return element.tag, element.text, element.attrib
[ドキュメント] def get_root(xml): """ 概要: XMLソースからルート要素を取得します。 詳細説明: 入力がファイルパスの場合はファイルを解析し、XML文字列の場合は文字列から解析して xml.etree.ElementTree.Element型のルート要素を返します。 :param xml: XMLファイルのパス (str) またはXML文字列 (str)。 :returns: xml.etree.ElementTree.Element型のXMLルート要素。 """ if os.path.isfile(xml): xml_tree = ET.parse(xml) xml_root = xml_tree.getroot() else: xml_root = ET.fromstring(xml) return xml_root
[ドキュメント] def main(): """ 概要: スクリプトのメイン実行関数です。 詳細説明: コマンドライン引数から入力ファイルを読み込み、そのXML構造に基づいて ATLAS最適化XMLまたはVASP計算結果XML(vasprun.xml)を解析し、 関連情報を標準出力に出力します。 :param: なし :returns: None """ print() print(f"infile: {infile}") # dict_xml = file2dict(infile) # print("dict:", dict_xml) # _xml = dict2xml(dict_xml) # print("xml:", _xml) xml_root = get_root(infile) # tag, text, attrib = get_attrib(xml_root) # print("tag=", tag) # print("text=", text) # print("attrib=", attrib) if xml_root.find('.//parameter-list'): print() print("ATLAS optimization XML:") parameter_inf = get_section_inf_all(xml_root, ['parameter-list', 'parameter'], ret_type = 'dict') # parameter_inf = get_section_inf(xml_root, ['parameter-list', 'parameter'], ret_type = 'dict') # parameter_inf = get_section_inf(xml_root.find('parameter-list'), ['parameter']) # parameter_inf = get_section_inf(xml_root, ['.//parameter']) # parameter_inf = get_section_inf(xml_root, ['.//parameter-list']) print("parameter_inf:") for key, val in parameter_inf.items(): print(f" {key}: {val}") setting_inf = get_section_inf_all(xml_root, ['settings', 'setting'], ret_type = 'dict') # setting_inf = get_section_inf(xml_root, ['settings', 'setting'], ret_type = 'dict') print("setting_inf:") for key, val in setting_inf.items(): print(f" {key}: {val}") target_inf = get_section_inf_all(xml_root, ['target-list', 'target'], ret_type = 'dict') # target_inf = get_section_inf(xml_root, ['target-list', 'target'], ret_type = 'dict') print("target_inf:") for key, val in target_inf.items(): print(f" {key}: {val}") elif xml_root.find('.//incar'): print() print("vasprun.xml:") # band_inf = get_section_inf(xml_root) # band_inf = get_section_inf(xml_root, [".//projected_kpoints_opt", ".//eigenvalues", './/set[@comment]']) # band_inf = get_section_inf(xml_root, [".//projected_kpoints_opt", ".//eigenvalues", './/set[@comment]', './/set[@comment]', './/r']) band_inf = get_section_inf_all(xml_root, [".//projected_kpoints_opt", ".//eigenvalues", './/set[@comment]', './/set[@comment]', './/r']) print("band_inf:") # for inf in band_inf: # print(f" {inf}") # incar_inf = get_section_inf(xml_root, [".//incar"], ret_type = 'dict') # print("incar_inf:", incar_inf) # incar_inf = get_section_inf_all(xml_root, [".//incar", "*"], ret_type = 'list', last_node_only = False) # print("incar_inf:") # for val in incar_inf: # print(f" {val}") incar_inf = get_section_inf_all(xml_root, [".//incar", "*"], pkey = 'name', ret_type = 'dict', last_node_only = False) for key, val in incar_inf.items(): print(f" {key}: {val}") else: print() print("Error: Invalid XML type") exit()
if __name__ == '__main__': main()