# -*- coding: utf-8 -*- """隐藏项探测.py —— 投资月报源文件「隐藏工作表 / 隐藏行 / 隐藏列」只读探测 用法: python docs\投资\工具\隐藏项探测.py --month 2026-07 输出: \隐藏项探测_.md \隐藏项探测_.txt 原则:只读,不修改任何源文件;不判定业务口径。 """ import sys, os, io, argparse, datetime as _dt, glob _here = os.path.dirname(os.path.abspath(__file__)) _root = os.path.abspath(os.path.join(_here, '..', '..', '..')) for _base in (_here, _root, os.getcwd()): for _sub in ('_pylibs',): _lib = os.path.abspath(os.path.join(_base, _sub)) if os.path.isdir(_lib) and _lib not in sys.path: sys.path.insert(0, _lib) try: import xlrd except ImportError: xlrd = None try: import openpyxl except ImportError: openpyxl = None VIS = {0: '可见', 1: '隐藏', 2: '深度隐藏'} def rngs(rows): rows = sorted(rows) out = [] for r in rows: if out and r == out[-1][1] + 1: out[-1][1] = r else: out.append([r, r]) return ['R%d' % a if a == b else 'R%d-R%d' % (a, b) for a, b in out] def cell(v): s = str(v).strip() if v is not None else '' s = s.replace('\n', ' ').replace('|', '/') return s[:24] def scan_xls(path): bk = xlrd.open_workbook(path, formatting_info=True) # 只有 True 才有 rowinfo_map/colinfo_map(隐藏行/列) res = [] for sh in bk.sheets(): hid = sh.rowinfo_map and [r + 1 for r, ri in sh.rowinfo_map.items() if ri.hidden] or [] res.append({ 'name': sh.name, 'vis': VIS.get(sh.visibility, str(sh.visibility)), 'nrows': sh.nrows, 'ncols': sh.ncols, 'hidden_rows': sorted(hid), 'head': [[cell(sh.cell_value(r, c)) for c in range(min(sh.ncols, 8))] for r in range(min(sh.nrows, 6))], 'hidden_head': [[r + 1] + [cell(sh.cell_value(r, c)) for c in range(min(sh.ncols, 8))] for r in hid[:6]], 'hidden_tail': [[r + 1] + [cell(sh.cell_value(r, c)) for c in range(min(sh.ncols, 8))] for r in hid[-2:]], }) bk.release_resources() return res def scan_xlsx(path): wb = openpyxl.load_workbook(path, data_only=True) res = [] for ws in wb.worksheets: hid = [r for r, d in ws.row_dimensions.items() if d.hidden] hidden_cols = [c for c, d in ws.column_dimensions.items() if d.hidden] state = ws.sheet_state vis = {'visible': '可见', 'hidden': '隐藏', 'veryHidden': '深度隐藏'}.get(state, state) head = [] for r in range(1, min(ws.max_row, 6) + 1): head.append([cell(ws.cell(r, c).value) for c in range(1, min(ws.max_column, 8) + 1)]) hh = [] for r in sorted(hid)[:6]: hh.append([r] + [cell(ws.cell(r, c).value) for c in range(1, min(ws.max_column, 8) + 1)]) res.append({'name': ws.title, 'vis': vis, 'nrows': ws.max_row, 'ncols': ws.max_column, 'hidden_rows': sorted(hid), 'hidden_cols': hidden_cols, 'head': head, 'hidden_head': hh}) wb.close() return res def main(): ap = argparse.ArgumentParser() ap.add_argument('--month', default='2026-07') ap.add_argument('--inroot', default=None) a = ap.parse_args() root = a.inroot or os.path.join(_root, 'docs', '投资', '输入') ym = a.month.replace('-', '') mm = a.month.split('-')[1].lstrip('0') outdir = os.path.join(_root, 'docs', '投资', '输出') os.makedirs(outdir, exist_ok=True) L = [] L.append('# 隐藏项探测(只读) — %s' % a.month) L.append('') L.append('生成时间:%s' % _dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) L.append('说明:只读扫描,不改任何源文件。「隐藏」= Excel 里默认看不到,但程序读单元格能读到。') L.append('') targets = [('客运(客运站)', os.path.join(root, '客运(客运站)', '%s月' % mm)), ('物流(物流园区)', os.path.join(root, '物流(物流园区)', '%s月' % mm))] for body, d in targets: L.append('## %s (目录:%s)' % (body, d)) L.append('') if not os.path.isdir(d): L.append('目录不存在。') L.append('') continue files = sorted([f for f in os.listdir(d) if not f.startswith('~$')]) for fn in files: fp = os.path.join(d, fn) ext = os.path.splitext(fn)[1].lower() L.append('### %s' % fn) L.append('') if ext == '.et': L.append('- 格式:WPS `.et`(xlrd/openpyxl 都读不了,需 Excel COM / WPS 打开)') L.append('') continue try: info = scan_xls(fp) if ext == '.xls' else scan_xlsx(fp) except Exception as e: L.append('- 读取失败:%s' % e) L.append('') continue alln = len(info) vis = [s for s in info if s['vis'] == '可见'] hid = [s for s in info if s['vis'] != '可见'] L.append('- 工作表总数 %d :可见 %d,隐藏 %d' % (alln, len(vis), len(hid))) if hid: L.append('- 隐藏工作表:%s' % '、'.join('%s(%s)' % (s['name'], s['vis']) for s in hid[:8]) + (' …共%d张' % len(hid) if len(hid) > 8 else '')) for s in info: tag = [] if s['vis'] == '可见' else ['(%s)' % s['vis']] L.append(' - 表 `%s`%s 尺寸 %d行×%d列' % (s['name'], ''.join(tag), s['nrows'], s['ncols'])) hr = s['hidden_rows'] if hr: L.append(' - **隐藏行 %d 行:%s**' % (len(hr), '、'.join(rngs(hr)))) for row in s['hidden_head']: L.append(' - %s' % ' | '.join(['R%d' % row[0]] + row[1:])) if len(hr) > 6 and s.get('hidden_tail'): for row in s['hidden_tail']: L.append(' - %s' % ' | '.join(['R%d' % row[0]] + row[1:])) if s.get('hidden_cols'): L.append(' - 隐藏列:%s' % '、'.join(s['hidden_cols'])) L.append('') txt = '\n'.join(L) p_md = os.path.join(outdir, '隐藏项探测_%s.md' % a.month) p_txt = os.path.join(outdir, '隐藏项探测_%s.txt' % a.month) io.open(p_md, 'w', encoding='utf-8').write(txt + '\n') io.open(p_txt, 'w', encoding='utf-8').write(txt + '\n') print('OK -> %s' % p_md) if __name__ == '__main__': main()