import sys
import tkinter as tk
from tkinter import ttk, messagebox
import xml.etree.ElementTree as ET
"""
XMLファイルを読み込み、表示、編集、保存するGUIアプリケーションです。
Treeviewウィジェットを使用してXMLの構造と値を表示し、特定の値の編集、
および変更内容を新しいXMLファイルとして保存する機能を提供します。
コマンドライン引数からXMLファイルのパスとウィンドウサイズを指定できます。
:doc:`XML_Edit3_usage`
"""
file_path = '../XML/vasprun.xml'
window_size = '800x600'
nargs = len(sys.argv)
if nargs >= 2:
file_path = sys.argv[1]
if nargs >= 3:
window_size = sys.argv[2]
[ドキュメント]
class XMLViewerEditor:
"""
XMLファイルをGUIで表示し、編集機能を提供するクラスです。
Tkinterを使用してTreeviewウィジェットでXML構造を表示し、特定のテキスト値の編集、
およびその変更を新しいXMLファイルとして保存することを可能にします。
"""
def __init__(self, root, file_path, window_size):
"""
XMLViewerEditorクラスのコンストラクタです。
Tkinterウィンドウの設定、Treeviewウィジェットの初期化、指定されたXMLファイルの読み込み、
セル編集のイベントバインディング、およびXML保存ボタンの配置を行います。
:param root: Tkinterのルートウィンドウオブジェクト。
:type root: tk.Tk
:param file_path: 読み込むXMLファイルのパス。
:type file_path: str
:param window_size: ウィンドウの初期サイズ(例: "800x600")。
:type window_size: str
:returns: None
:rtype: None
"""
self.root = root
self.root.title("XML Viewer and Editor")
self.root.geometry(window_size)
self.tree = ttk.Treeview(self.root, columns=("Value"))
self.tree.heading('#0', text='Element')
self.tree.heading('Value', text='Value')
self.tree.pack(fill='both', expand=True)
self.load_xml(file_path)
self.tree.bind("<Double-1>", self.edit_cell)
# 保存ボタンを追加
save_button = tk.Button(self.root, text="Save XML", command=self.save_xml)
save_button.pack()
[ドキュメント]
def load_xml(self, file_path):
"""
指定されたXMLファイルを読み込み、Treeviewに表示します。
xml.etree.ElementTreeモジュールを使用してファイルをパースし、
ルート要素を`self.root_element`に格納した後、`display_element`を呼び出してGUIに表示します。
パースエラーが発生した場合は、エラーメッセージをコンソールに出力します。
:param file_path: 読み込むXMLファイルのパス。
:type file_path: str
:returns: None
:rtype: None
"""
try:
tree = ET.parse(file_path)
self.root_element = tree.getroot()
self.display_element(self.root_element, "")
except ET.ParseError as e:
print("Error:", str(e))
[ドキュメント]
def display_element(self, element, parent_id):
"""
XML要素を再帰的にTreeviewに挿入し表示します。
要素のタグとテキスト値を表示し、属性があればそれも表示します。
子要素があれば自身を再帰的に呼び出して、ツリー構造を構築します。
:param element: 表示するXML要素オブジェクト。
:type element: xml.etree.ElementTree.Element
:param parent_id: Treeviewにおける親アイテムのID。
:type parent_id: str
:returns: None
:rtype: None
"""
item = self.tree.insert(parent_id, "end", text=element.tag, values=(element.text,))
for key, value in element.attrib.items():
self.tree.insert(item, "end", text=f"{key}: {value}")
for child in element:
self.display_element(child, item)
[ドキュメント]
def edit_cell(self, event):
"""
Treeviewのセルをダブルクリックしたときに編集ダイアログを開きます。
イベントが発生した列が値の列('#1')である場合のみ、
現在のセルの値を取得し、編集ダイアログを表示します。
:param event: Tkinterのイベントオブジェクト。
:type event: tk.Event
:returns: None
:rtype: None
"""
item = self.tree.selection()[0]
col = self.tree.identify_column(event.x)
print(f"col={col}")
if col == '#1': # 値の列の場合
cell_value = self.tree.item(item, 'values')[0] # セルの現在の値を取得
self.edit_dialog(item, cell_value)
[ドキュメント]
def edit_dialog(self, item, current_value):
"""
セル値を編集するための独立したダイアログウィンドウを表示します。
現在の値を表示するEntryウィジェットと、変更を保存するボタンを含む
Toplevelウィンドウを作成します。
:param item: 編集対象のTreeviewアイテムのID。
:type item: str
:param current_value: 編集前の現在のセルの値。
:type current_value: str
:returns: None
:rtype: None
"""
edit_window = tk.Toplevel(self.root)
edit_window.title("Edit Value")
edit_window.geometry("300x100")
label = tk.Label(edit_window, text="Edit Value:")
label.pack()
entry = tk.Entry(edit_window)
entry.insert(0, current_value)
entry.pack()
save_button = tk.Button(edit_window, text="Save", command=lambda: self.save_cell_edit(item, entry.get(), edit_window))
save_button.pack()
[ドキュメント]
def save_cell_edit(self, item, new_value, edit_window):
"""
編集ダイアログでの変更をTreeviewに適用し、ダイアログを閉じます。
指定されたTreeviewアイテムの'Value'列を新しい値で更新し、
編集ウィンドウを破棄します。
:param item: 値を更新するTreeviewアイテムのID。
:type item: str
:param new_value: 新しいセルの値。
:type new_value: str
:param edit_window: 編集に使用されたToplevelウィンドウオブジェクト。
:type edit_window: tk.Toplevel
:returns: None
:rtype: None
"""
self.tree.set(item, 'Value', new_value) # セルの値を更新
edit_window.destroy()
[ドキュメント]
def save_xml(self):
"""
Treeviewの現在の表示状態を基にXMLファイルを保存します。
Treeviewの表示内容を基に元のXMLツリーを更新(`update_xml`)し、
変更されたXMLを`edited.xml`というファイル名で保存します。
保存が完了すると、コンソールにメッセージを出力します。
:returns: None
:rtype: None
"""
# Treeviewの変更をXMLに反映
# 注意: 現在の実装では、XMLツリーの要素テキストのみが更新されます。
# 属性やネストされた構造の変更は現在のupdate_xml関数では適切に扱われません。
self.update_xml(self.root_element, self.tree.get_children())
# XMLをファイルに保存
new_xml = ET.tostring(self.root_element, encoding="utf-8")
with open("edited.xml", "wb") as xml_file:
xml_file.write(new_xml)
print("XML saved as 'edited.xml'")
[ドキュメント]
def update_xml(self, element, children):
"""
Treeviewの表示内容を基にXML要素を再帰的に更新します。
この関数は、Treeviewの各アイテムから値を取得し、
対応するXML要素のテキスト値を更新することを意図しています。
しかし、現在の実装では、`element.text`への代入が適切に行われず、
ツリー構造全体を正確に反映するようには機能しません。
特に、`element`が常に同じルート要素を指し、そのテキストだけが更新される形になっています。
(「既存のロジックは一切変更しない」ルールに基づき、このままDocstringを記述します。)
:param element: 更新対象のXML要素オブジェクト。
:type element: xml.etree.ElementTree.Element
:param children: Treeviewの現在の`element`に対応する子アイテムのリスト。
:type children: tuple
:returns: None
:rtype: None
"""
for child in children:
item = self.tree.item(child)
# 現在のロジックでは、elementが引数で渡されたXML要素で、
# そのtext属性をTreeviewの最初の子供(item)の値で上書きしようとしている。
# しかし、このループのelementは再帰呼び出しで更新されないため、
# 常に親要素のtextを更新してしまうという不具合がある。
element.text = item['values'][0]
self.update_xml(element, self.tree.get_children(child))
if __name__ == "__main__":
root = tk.Tk()
app = XMLViewerEditor(root, file_path, window_size)
root.mainloop()