#!/usr/bin/env python3 """Offline WordPress WXR preflight and optional static output URL coverage check.""" from __future__ import annotations import argparse import html import json import sys from pathlib import Path ROOT=Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path:sys.path.insert(0,str(ROOT)) from app.wp_complex_v820 import inspect_wxr, MAX_XML from app.builder import output_path def inspect_file(source:Path,dist:Path|None=None): if source.stat().st_size>MAX_XML:raise ValueError('WXR 超过 16 MB,需分割后分别预检') result=inspect_wxr(source.read_bytes()) if dist is not None: if not dist.is_dir():raise ValueError('静态构建输出目录不存在') # Every actual published URL must be matched. WXR preflight by design # never claims 100% coverage if it has invalid or duplicate routes. from defusedxml import ElementTree as ET root=ET.parse(source).getroot() routes=[] from app.core import route_path from urllib.parse import urlsplit for item in root.findall('./channel/item'): fields={c.tag.rsplit('}',1)[-1]:c.text or '' for c in item if c.tag.rsplit('}',1)[-1] in ('post_type','status','link')} if fields.get('post_type') in ('attachment','revision','nav_menu_item') or fields.get('status','publish')!='publish':continue p=urlsplit(fields.get('link','')) if p.scheme in ('http','https') and p.hostname and not p.query and not p.fragment and p.path: try:routes.append(route_path(p.path)) except ValueError:pass missing=[route for route in sorted(set(routes)) if not output_path(dist,route).is_file()] result['static_build_coverage']={'expected_routes':len(set(routes)),'missing_count':len(missing), 'missing_sample':missing[:100], 'passed':len(missing)==0 and result['blocking_issues']==0} return result def render_html(report): rows=''.join(''+html.escape(x['severity'])+''+html.escape(x['code'])+ ''+html.escape(x['route'])+''+html.escape(x['message'])+'' for x in report['issues']) metrics=' '.join(f'{html.escape(str(k))}: {html.escape(str(v))}' for k,v in report['counts'].items() if k in ('published_content','attachments','findings_blocker','findings_review')) return f'''SiteForge V8.20 WordPress 兼容预检

SiteForge V8.20 · WordPress 兼容预检

离线分析,不连接原站,不修改已有站点

{metrics}

待处理问题

{rows}
等级问题类型URL处理说明

本报告不能证明 1:1 迁移、媒体本地化、动态功能、视觉一致性或正式发布验收通过。

''' def main(argv=None): parser=argparse.ArgumentParser(description='离线分析 WordPress WXR 迁移风险与 URL 覆盖') parser.add_argument('wxr',type=Path) parser.add_argument('--dist',type=Path,help='可选:静态构建 dist 目录,检查对应 HTML 是否存在') parser.add_argument('--output',type=Path,default=Path('reports/v820-wxr-preflight.json')) args=parser.parse_args(argv) try:report=inspect_file(args.wxr,args.dist) except (OSError,ValueError,Exception) as exc: # A single fail-closed error message avoids a massive traceback for malformed input. print('无法完成 WXR 预检:'+str(exc)[:350],file=sys.stderr) return 2 args.output.parent.mkdir(parents=True,exist_ok=True) args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8') args.output.with_suffix('.html').write_text(render_html(report),encoding='utf-8') print(f"WXR 预检:{report['unique_published_routes']} 个静态路由,{report['blocking_issues']} 个阻断问题;报告:{args.output}") if report['blocking_issues'] or report.get('static_build_coverage',{}).get('passed') is False:return 1 return 0 if __name__=='__main__':raise SystemExit(main())