Python 置換対応表でHTMLを置換するツールを作成しました。

========================================
HTML文字列一括置換ツール 使用手順書
========================================

■ 概要
このツールは、指定したフォルダ内のHTMLファイルに含まれる文字列を、
「置換ルール表(replace_rules.xlsx または replace_rules.csv)」に基づいて一括で置換します。

すべてのHTMLを上書きするのではなく、
置換が行われたファイルのみを新しいフォルダに出力します。
また、置換前の状態もバックアップとして別フォルダに保存されます。
さらに、置換ルールの使用状況をCSVまたはExcelで確認できます。

----------------------------------------
■ フォルダ構成
----------------------------------------

作業フォルダの中に以下の4つのフォルダ/ファイルを配置します。

  ┣━━ replace_rules.xlsx または replace_rules.csv   ← 置換ルール表
  ┣━━ html_source/                              ← 置換対象のHTMLファイルを入れる
  ┣━━ html_replaced/                            ← 置換後のファイルが出力される
  ┣━━ html_backup/                              ← 置換前のファイル(バックアップ)
  ┗━━ script.py                                 ← このツールのPythonスクリプト本体

----------------------------------------
■ 置換ルール表の書式
----------------------------------------

「replace_rules.xlsx」または「replace_rules.csv」には以下の形式で置換ペアを記載します。
(1行目はヘッダー行として使用し、データは2行目以降に記載します)

  A列:置換前文字列(検索文字列)
  B列:置換後文字列(置換後の文字列)

※ A列・B列とも2行目から記載してください。
※ A列・B列のいずれかが空欄の行はスキップされます。

----------------------------------------
■ 実行方法
----------------------------------------

1. 作業フォルダに移動します。
   (例)cd "C:\Users\user\Documents\HTMLReplaceTool"

2. 次のコマンドを実行します。
   python script.py

3. スクリプトが実行されると、以下の処理が行われます。

   (1) replace_rules.xlsx または replace_rules.csv から置換ルールを読み込み
   (2) html_source フォルダ内のすべてのHTMLファイルをスキャン
   (3) 各ファイルに対して文字列置換を実施
   (4) 置換が発生したファイルのみを html_replaced に出力
   (5) 置換が発生したファイルの元データを html_backup に保存
   (6) 使用状況レポート(replace_usage_report.csv または .xlsx)を出力

----------------------------------------
■ 出力結果
----------------------------------------

・html_replaced/
 → 置換後のHTMLファイルが出力されます。

・html_backup/
 → 置換が行われる前のHTMLファイルがバックアップとして保存されます。

・replace_usage_report.csv または replace_usage_report.xlsx
 → 各置換ルールが実際に使用されたか(使用回数を含む)が記録されます。

----------------------------------------
■ 注意事項
----------------------------------------

・置換は完全一致検索(部分一致)で行われます。
・置換ルール表の文字列は大文字・小文字を区別します。
・html_source 内のファイルは上書きされません。
・置換ルール表のファイル名やフォルダ名を変更する場合は、スクリプト内の変数を修正してください。
Excel版の場合は openpyxl が必要です。
CSV版の場合は標準ライブラリのみで動作します。

----------------------------------------
■ 出力ログ
----------------------------------------

処理完了後、コンソールに以下のような情報が表示されます。

  - 置換ペア数
  - 処理したHTMLファイル数
  - 置換が行われたファイル数
  - 出力先フォルダとレポートファイルのパス

これで置換処理の内容を確認できます。

import os
import shutil
import openpyxl
import csv

# ===== 設定 =====
WORK_DIR = os.path.dirname(__file__)
SRC_DIR = os.path.join(WORK_DIR, "source_files")
DST_DIR = os.path.join(WORK_DIR, "updated_files")
BACKUP_DIR = os.path.join(WORK_DIR, "backup_files")
# REPLACE_TABLE = os.path.join(WORK_DIR, "replace_map.xlsx")
REPLACE_TABLE = os.path.join(WORK_DIR, "replace_map.csv")
# USAGE_REPORT = os.path.join(WORK_DIR, "usage.xlsx")
USAGE_REPORT = os.path.join(WORK_DIR, "usage.csv")

# ===== 置換ルールの読み込み (Excel版) =====
def load_replace_rules(file_path):
    import openpyxl
    wb = openpyxl.load_workbook(file_path)
    ws = wb.active
    rules = []
    # min_row=2 にして1行目のヘッダーをスキップ
    for row in ws.iter_rows(min_row=2, min_col=1, max_col=2, values_only=True):
        if row[0] and row[1]:
            rules.append({
                "before": str(row[0]),
                "after": str(row[1]),
                "used": False,
                "count": 0
            })
    wb.close()
    return rules

# ===== 置換ルールの読み込み (CSV版) =====
def load_replace_rules_csv(csv_path):
    import csv
    rules = []
    with open(csv_path, newline="", encoding="utf-8-sig") as f:
        reader = csv.reader(f)
        header = next(reader)  # 1行目のヘッダーを読み飛ばす
        for row in reader:
            if row[0] and row[1]:
                rules.append({
                    "before": row[0],
                    "after": row[1],
                    "used": False,
                    "count": 0
                })
    return rules

# ===== HTMLファイルの置換処理 =====
def replace_in_html(rules, src_dir, dst_dir, backup_dir):
    os.makedirs(dst_dir, exist_ok=True)
    os.makedirs(backup_dir, exist_ok=True)

    for root, _, files in os.walk(src_dir):
        for name in files:
            if not name.lower().endswith((".html", ".htm")):
                continue

            src_path = os.path.join(root, name)
            rel_path = os.path.relpath(src_path, src_dir)
            dst_path = os.path.join(dst_dir, rel_path)
            backup_path = os.path.join(backup_dir, rel_path)

            os.makedirs(os.path.dirname(dst_path), exist_ok=True)
            os.makedirs(os.path.dirname(backup_path), exist_ok=True)

            with open(src_path, "r", encoding="utf-8", errors="ignore") as f:
                original_text = f.read()

            new_text = original_text
            file_changed = False

            for rule in rules:
                if rule["before"] in new_text:
                    count = new_text.count(rule["before"])
                    rule["used"] = True
                    rule["count"] += count
                    new_text = new_text.replace(rule["before"], rule["after"])
                    file_changed = True

            if file_changed:
                # 元のファイルをバックアップ
                shutil.copy2(src_path, backup_path)
                # 置換後のファイルを保存
                with open(dst_path, "w", encoding="utf-8") as f:
                    f.write(new_text)
                print(f"置換あり: {rel_path}")

# ===== 使用結果の出力 =====
def export_usage_report(rules, output_path):
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Usage Report"
    ws.append(["置換前文字列", "置換後文字列", "使用されたか", "置換回数"])

    for r in rules:
        ws.append([
            r["before"],
            r["after"],
            "はい" if r["used"] else "いいえ",
            r["count"]
        ])

    wb.save(output_path)
    print(f"使用状況を出力しました: {output_path}")

# ===== csvで使用結果の出力 =====
def export_usage_report_csv(rules, output_path):
    """
    置換ルールの使用状況をCSVとして出力する
    :param rules: {"before": str, "after": str, "used": bool, "count": int} のリスト
    :param output_path: 出力先CSVファイルのパス
    """
    with open(output_path, mode="w", newline="", encoding="utf-8-sig") as f:
        writer = csv.writer(f)
        writer.writerow(["置換前文字列", "置換後文字列", "使用されたか", "置換回数"])
        for r in rules:
            writer.writerow([
                r["before"],
                r["after"],
                "はい" if r["used"] else "いいえ",
                r["count"]
            ])

# ===== メイン処理 =====
if __name__ == "__main__":
    # rules = load_replace_rules(REPLACE_TABLE)
    rules = load_replace_rules_csv(REPLACE_TABLE)
    replace_in_html(rules, SRC_DIR, DST_DIR, BACKUP_DIR)
    export_usage_report_csv(rules, USAGE_REPORT)
    print("✅ HTML置換処理が完了しました。")