Created
April 10, 2026 07:31
-
-
Save ecormaksin/3f0ba73d14156dbc0fd2908279ec6784 to your computer and use it in GitHub Desktop.
XMLファイルをプロパティのレベルまで並べ替えてフォーマットするPythonスクリプト
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| sort_xml_attrs.py | |
| XMLファイルの属性をアルファベット順にソートし、指定した出力フォルダーに | |
| 元と同じファイル名で保存するスクリプト | |
| 使い方: | |
| python sort_xml_attrs.py -o <出力フォルダー> <xmlファイル or フォルダー> ... | |
| 例: | |
| python sort_xml_attrs.py -o sorted/ file1.xml file2.xml | |
| python sort_xml_attrs.py -o sorted/ pptx_extracted/ | |
| python sort_xml_attrs.py -o sorted/ *.xml | |
| フォルダーを丸ごと指定した場合、サブフォルダーの構造を維持して出力します。 | |
| """ | |
| import sys | |
| import os | |
| import glob | |
| import argparse | |
| from lxml import etree | |
| def sort_attributes(element): | |
| """要素の属性をアルファベット順にソートし、子要素に再帰適用する""" | |
| attribs = sorted(element.attrib.items()) | |
| element.attrib.clear() | |
| for key, value in attribs: | |
| element.attrib[key] = value | |
| for child in element: | |
| sort_attributes(child) | |
| def process_file(input_path, output_dir, base_dir=None): | |
| """1つのXMLファイルを処理して出力フォルダーに同名で保存する""" | |
| # サブフォルダー構造を維持する場合 | |
| if base_dir: | |
| rel_path = os.path.relpath(input_path, base_dir) | |
| output_path = os.path.join(output_dir, rel_path) | |
| else: | |
| filename = os.path.basename(input_path) | |
| output_path = os.path.join(output_dir, filename) | |
| # 出力先フォルダーを作成 | |
| os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else output_dir, exist_ok=True) | |
| try: | |
| parser = etree.XMLParser(remove_blank_text=True) | |
| tree = etree.parse(input_path, parser) | |
| root = tree.getroot() | |
| sort_attributes(root) | |
| tree.write( | |
| output_path, | |
| pretty_print=True, | |
| xml_declaration=True, | |
| encoding=tree.docinfo.encoding or "UTF-8", | |
| ) | |
| print(f"OK: {input_path} -> {output_path}") | |
| except Exception as e: | |
| print(f"ERROR: {input_path} -> {e}") | |
| def collect_files(args): | |
| """引数からXMLファイルのリストを収集する(base_dirも返す)""" | |
| entries = [] # (input_path, base_dir) | |
| for arg in args: | |
| if os.path.isdir(arg): | |
| for path in glob.glob(os.path.join(arg, "**", "*.xml"), recursive=True): | |
| entries.append((path, arg)) | |
| elif "*" in arg or "?" in arg: | |
| for path in glob.glob(arg): | |
| entries.append((path, None)) | |
| else: | |
| entries.append((arg, None)) | |
| return entries | |
| def main(): | |
| # lxmlの存在確認 | |
| try: | |
| from lxml import etree # noqa: F401 | |
| except ImportError: | |
| print("lxmlが必要です。インストールしてください:") | |
| print(" pip install lxml") | |
| sys.exit(1) | |
| parser = argparse.ArgumentParser( | |
| description="XMLファイルの属性をアルファベット順にソートして別フォルダーに保存します" | |
| ) | |
| parser.add_argument( | |
| "-o", "--output", required=True, | |
| help="出力先フォルダー(存在しない場合は自動作成)" | |
| ) | |
| parser.add_argument( | |
| "inputs", nargs="+", | |
| help="処理するXMLファイルまたはフォルダー" | |
| ) | |
| args = parser.parse_args() | |
| entries = collect_files(args.inputs) | |
| if not entries: | |
| print("対象のXMLファイルが見つかりませんでした。") | |
| sys.exit(1) | |
| os.makedirs(args.output, exist_ok=True) | |
| for input_path, base_dir in entries: | |
| process_file(input_path, args.output, base_dir) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment