Skip to content

Instantly share code, notes, and snippets.

@ecormaksin
Created April 10, 2026 07:31
Show Gist options
  • Select an option

  • Save ecormaksin/3f0ba73d14156dbc0fd2908279ec6784 to your computer and use it in GitHub Desktop.

Select an option

Save ecormaksin/3f0ba73d14156dbc0fd2908279ec6784 to your computer and use it in GitHub Desktop.
XMLファイルをプロパティのレベルまで並べ替えてフォーマットするPythonスクリプト
"""
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