#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""patch_linkbox.py —— 修「正文容器外的附件区(div.linkbox)被漏掉」

根因：源站正文表格里「见附件」是纯文本，真附件在正文容器 div.trs_editor_view 之外的
      下载面板 <div class='linkbox'><a href="./P020xxx.docx">附件名</a></div>
      → extract_body 只取 trs_editor_view → 附件链接与 attachments 列全丢。
      （与颍泉 div.m-dtdownload 同类，修法照搬颍泉的 collect_extra_attachments）
"""
import ast
import io
import os
import re
import shutil
import subprocess
import sys

D = "/root/gov_crawler/"
UA = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
      "(KHTML, like Gecko) Chrome/124.0 Safari/537.36")

FUNC = '''

def collect_extra_attachments(soup, page_url):
    """站点附件区（在正文容器之外）：div.linkbox / div.atile / div.xxgk_fj 等下载面板
    ⚠️ 正文表格里那句「见附件」往往只是纯文本（中间夹 Word 书签 <a name=_GoBack>），
       真附件链接在这个面板里 → 只取 trs_editor_view 会全丢"""
    out, seen = [], set()
    for div in soup.select("div.linkbox, div.atile, div.xxgk_fj, div.attach_list, div.fjlist"):
        for a in div.find_all("a", href=True):
            href = (a.get("href") or "").strip()
            if not href or re.match(r"^(?:javascript:|mailto:|tel:|#)", href, re.I):
                continue
            abs_url = urllib.parse.urljoin(page_url, href)
            if abs_url in seen:
                continue
            seen.add(abs_url)
            name = clean_title(a.get_text(" ", strip=True) or a.get("title") or "") \\
                or os.path.basename(abs_url.split("?")[0]) or "附件"
            out.append((abs_url, name))
    return out

'''


def patch(f):
    p = D + f
    src = io.open(p, encoding="utf-8").read()
    if "collect_extra_attachments" in src:
        print("  %-28s 已有该函数，跳过" % f)
        return None
    n = 0
    # ① 插入函数（放在 parse_detail 前）
    anchor1 = "def parse_detail(html_text, page_url):"
    if src.count(anchor1) == 1:
        src = src.replace(anchor1, FUNC.strip() + "\n\n\n" + anchor1, 1)
        n += 1
    else:
        print("  ❌ 锚点① 命中 %d" % src.count(anchor1)); return None
    # ② parse_detail 返回值加 extra_atts
    a2 = '''    if not date:
        dm = re.search(r"(\\d{4})[-年/](\\d{1,2})[-月/](\\d{1,2})", html_text)
        if dm:
            date = "%s-%s-%s" % (dm.group(1), dm.group(2).zfill(2), dm.group(3).zfill(2))
    return title, date, content_html'''
    b2 = '''    if not date:
        dm = re.search(r"(\\d{4})[-年/](\\d{1,2})[-月/](\\d{1,2})", html_text)
        if dm:
            date = "%s-%s-%s" % (dm.group(1), dm.group(2).zfill(2), dm.group(3).zfill(2))
    # 正文容器之外的附件区（div.linkbox 等）—— 只取正文容器会漏掉附件
    extra_atts = []
    try:
        if BeautifulSoup:
            extra_atts = collect_extra_attachments(BeautifulSoup(html_text, "html.parser"), page_url)
    except Exception:
        extra_atts = []
    return title, date, content_html, extra_atts'''
    if src.count(a2) == 1:
        src = src.replace(a2, b2, 1); n += 1
    else:
        print("  ❌ 锚点② 命中 %d" % src.count(a2)); return None
    # ③ main 解包 + 合并
    a3 = "            dtitle, ddate, content_html = parse_detail(dhtml, abs_url)"
    b3 = "            dtitle, ddate, content_html, extra_atts = parse_detail(dhtml, abs_url)"
    if src.count(a3) == 1:
        src = src.replace(a3, b3, 1); n += 1
    else:
        print("  ❌ 锚点③ 命中 %d" % src.count(a3)); return None
    a4 = '''            content, has_table, atts = html_to_text(content_html, abs_url)
            plain_len = len(re.sub(r"<[^>]+>", "", content).strip())'''
    b4 = '''            content, has_table, atts = html_to_text(content_html, abs_url)
            # 合并正文容器之外的附件（独立成段，遵守「多附件独立成段」规范）
            _known = set(u for u, _ in atts)
            for _u, _nm in (extra_atts or []):
                if _u in _known:
                    continue
                _known.add(_u)
                atts.append((_u, _nm))
                content += '\\n\\n<p><a href="%s" target="_blank">%s</a></p>' % (_u, _nm)
            plain_len = len(re.sub(r"<[^>]+>", "", content).strip())'''
    if src.count(a4) == 1:
        src = src.replace(a4, b4, 1); n += 1
    else:
        print("  ❌ 锚点④ 命中 %d" % src.count(a4)); return None
    if n != 4:
        print("  ❌ 只命中 %d/4，未落盘" % n); return None
    try:
        ast.parse(src)
    except SyntaxError as e:
        print("  ❌ 语法错误，未落盘:", e); return None
    bak = D + "Archive/%s.bak_20260925_linkbox" % f
    if not os.path.exists(bak):
        shutil.copy2(p, bak)
    io.open(p, "w", encoding="utf-8").write(src)
    print("  ✅ %-28s 已修（备份 %s）" % (f, os.path.basename(bak)))
    return f


print("=== ① 打补丁 ===")
for f in ["crawl_binyang_hjsp.py", "crawl_dazu_glz_qtgw.py", "crawl_danzhai_hjzf.py"]:
    patch(f)

print("\n=== ② 验证：目标文章 + 三站各抽 1 篇 ===")
SAMPLE = {
    "crawl_binyang_hjsp.py": "http://www.binyang.gov.cn/gk/xxgkml/shgysyjslygk/hjbhly/jsxmhjyxpjsp/t6684215.html",
}
import importlib.util
for f in ["crawl_binyang_hjsp.py", "crawl_dazu_glz_qtgw.py", "crawl_danzhai_hjzf.py"]:
    spec = importlib.util.spec_from_file_location("m_" + f, D + f)
    m = importlib.util.module_from_spec(spec)
    try:
        spec.loader.exec_module(m)
    except SystemExit:
        print("  %-28s (需 --col 参数，跳过)" % f); continue
    url = SAMPLE.get(f)
    if not url:
        h0 = m.fetch(m.list_page_url(1))
        its = m.parse_list(h0)
        if not its:
            print("  %-28s 列表解析 0 条" % f); continue
        url = its[0][0]
    h = m.fetch(url)
    r = m.parse_detail(h, url)
    content_html = r[2]
    extra = r[3] if len(r) > 3 else []
    content, ht, atts = m.html_to_text(content_html, url)
    print("  %-28s 容器外附件=%d | 正文内附件=%d | %s" % (
        f, len(extra), len(atts), url[-34:]))
    for u, nm in (extra or [])[:3]:
        print("        ↳ %s | %s" % (nm[:40], u[-50:]))
