# -*- coding: utf-8 -*-
|
"""隐藏项探测.py —— 投资月报源文件「隐藏工作表 / 隐藏行 / 隐藏列」只读探测
|
|
用法:
|
python docs\投资\工具\隐藏项探测.py --month 2026-07
|
输出:
|
<out>\隐藏项探测_<YYYY-MM>.md
|
<out>\隐藏项探测_<YYYY-MM>.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('')
|
try:
|
# 注意:.et(WPS)实为 OLE2/BIFF8 复合文档,xlrd 可以直接读;只有 .xlsx 走 openpyxl
|
info = scan_xlsx(fp) if ext == '.xlsx' else scan_xls(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()
|