#!/usr/bin/env python3
"""
fix-richtext-rpr-order.py — 把 openpyxl CellRichText 写出的 <rPr> 子元素顺序
改成 OOXML 合规顺序 (rFont -> sz -> color)。

⚠️⚠️ 重要警告（2026-06-18 世茂实证）：
    本脚本只修 rPr 顺序，但实测 **修了顺序 Microsoft Excel 仍报"需要修复"**。
    openpyxl 生成的富文本表在 Excel 严格校验下还有其他不合规处（去掉富文本只留
    纯文本都仍报错）。所以本脚本 **不保证解决 Excel 报错**，仅作"试过什么"的记录。

    正解：不要用 CellRichText 做局部标红/加粗，改用纯文本前缀（【需客户核实】…）。
    详见 ../references/openpyxl-excel-richtext-pitfall.md

用法：
    python fix-richtext-rpr-order.py <文件.xlsx>            # 原地修复
    python fix-richtext-rpr-order.py <文件.xlsx> --check    # 只检查，不改
    python fix-richtext-rpr-order.py <文件.xlsx> --sheet sheet2.xml   # 指定 sheet
"""
import sys, re, zipfile, os


def analyze(xlsx, sheet_name="xl/worksheets/sheet1.xml"):
    z = zipfile.ZipFile(xlsx)
    if sheet_name not in z.namelist():
        sheets = [n for n in z.namelist() if re.match(r"xl/worksheets/sheet\d+\.xml$", n)]
        print(f"指定 sheet 不存在；可用：{sheets}")
        return None, None
    xml = z.read(sheet_name).decode("utf-8")
    rprs = re.findall(r"<rPr>(.*?)</rPr>", xml, flags=re.S)
    bad = 0
    for inner in rprs:
        p_sz = inner.find("<sz")
        p_color = inner.find("<color")
        if p_sz != -1 and p_color != -1 and p_sz > p_color:
            bad += 1  # color 在 sz 之前 = 错误顺序
    return xml, (len(rprs), bad)


def fix(xlsx, sheet_name="xl/worksheets/sheet1.xml"):
    xml, stats = analyze(xlsx, sheet_name)
    if xml is None:
        return
    total, bad = stats
    print(f"rPr 总数 {total}，错误顺序 {bad}")
    if bad == 0:
        print("无需修复（顺序已合规）。注意：顺序合规 ≠ Excel 不报错，见脚本顶部警告。")
        return

    def _fix(m):
        inner = m.group(1)
        rf = re.search(r"<rFont[^/]*/>", inner)
        cs = re.search(r"<charset[^/]*/>", inner)
        fam = re.search(r"<family[^/]*/>", inner)
        b = re.search(r"<b[^/]*/>", inner)
        i = re.search(r"<i[^/]*/>", inner)
        sz = re.search(r"<sz[^/]*/>", inner)
        co = re.search(r"<color[^/]*/>", inner)
        # OOXML 顺序: rFont, charset, family, b, i, ..., sz, color
        parts = [x.group(0) for x in (rf, cs, fam, b, i, sz, co) if x]
        return "<rPr>" + "".join(parts) + "</rPr>"

    fixed = re.sub(r"<rPr>(.*?)</rPr>", _fix, xml, flags=re.S)
    tmp = xlsx + ".tmp"
    with zipfile.ZipFile(xlsx, "r") as zin, zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as zo:
        for it in zin.namelist():
            zo.writestr(it, fixed.encode("utf-8") if it == sheet_name else zin.read(it))
    os.replace(tmp, xlsx)
    print(f"已修复并写回 {xlsx}")
    print("⚠️ 仍需用 Microsoft Excel 实际打开验证——本脚本不保证消除 Excel 报错。")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)
    path = sys.argv[1]
    sheet = "xl/worksheets/sheet1.xml"
    if "--sheet" in sys.argv:
        sheet = "xl/worksheets/" + sys.argv[sys.argv.index("--sheet") + 1]
    if "--check" in sys.argv:
        _, stats = analyze(path, sheet)
        if stats:
            print(f"rPr 总数 {stats[0]}，错误顺序 {stats[1]}")
    else:
        fix(path, sheet)
