1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# -*- 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()