- 공공기관2/3 작업본 + 오늘 제출 17곳 D~J 카테고리 셀병합 정상화 - 한국지역난방공사 옵션2(고아셀 F98 수정)+전행 높이17 - 제출_프리랜서2_2026-06-21.zip 생성(17개 xlsx, 2,468행) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
110 lines
3.9 KiB
Python
110 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""랜딩셸 삭제(1-5e): menu?menuKey= 카테고리 인덱스(병합 첫행·다음깊이 빈칸·자식 보유) 제거.
|
|
풀리빌드(효과경로+스타일 보존 → 재기록 → 동일값런 재병합 → K링크 → B재부번)."""
|
|
import openpyxl, copy, re, io, sys
|
|
from openpyxl.utils import get_column_letter
|
|
from openpyxl.worksheet.hyperlink import Hyperlink
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
|
|
PATH = '한국청소년정책연구원.xlsx'
|
|
wb = openpyxl.load_workbook(PATH); ws = wb.active
|
|
merges = list(ws.merged_cells.ranges)
|
|
|
|
def eff(r, c):
|
|
v = ws.cell(r, c).value
|
|
if v is not None: return v
|
|
for m in merges:
|
|
if m.min_row <= r <= m.max_row and m.min_col <= c <= m.max_col:
|
|
return ws.cell(m.min_row, m.min_col).value
|
|
return None
|
|
def mtop(r, c):
|
|
for m in merges:
|
|
if m.min_row <= r <= m.max_row and m.min_col <= c <= m.max_col:
|
|
return (m.min_row, m.min_col)
|
|
return (r, c)
|
|
def deepest(path):
|
|
last = -1
|
|
for i in range(7):
|
|
if path[i] not in (None, ''): last = i
|
|
return last
|
|
|
|
COLS = range(2, 28)
|
|
DATA0 = ws.max_row
|
|
rows = []
|
|
for r in range(3, DATA0 + 1):
|
|
path = [eff(r, c) for c in range(4, 11)] # D..J
|
|
rows.append({'r': r, 'path': path,
|
|
'vals': {c: ws.cell(r, c).value for c in COLS},
|
|
'klink': (ws.cell(r, 11).hyperlink.target if ws.cell(r, 11).hyperlink else None),
|
|
'styles': {c: copy.copy(ws.cell(*mtop(r, c))._style) for c in COLS}})
|
|
|
|
MENU = re.compile(r'/menu\?menuKey=')
|
|
# 셸 판정: K가 menu-index AND 다음행이 같은 상위경로+더 깊은 깊이(자식 보유)
|
|
shells = set()
|
|
for i, rw in enumerate(rows):
|
|
k = rw['vals'].get(11) or ''
|
|
if not MENU.search(k): continue
|
|
c = deepest(rw['path'])
|
|
if c < 0: continue
|
|
# 자기 다음깊이(c+1)는 비어야 함(랜딩)
|
|
if c + 1 < 7 and rw['path'][c + 1] not in (None, ''):
|
|
continue
|
|
# 뒤쪽에 같은 [0..c] 경로 + c+1 채워진 자식이 있나
|
|
haschild = False
|
|
for j in range(i + 1, len(rows)):
|
|
op = rows[j]['path']
|
|
if op[:c + 1] != rw['path'][:c + 1]:
|
|
break # 상위경로 벗어남
|
|
if op[c + 1] not in (None, ''):
|
|
haschild = True; break
|
|
if haschild:
|
|
shells.add(rw['r'])
|
|
|
|
print('삭제 셸 행:', sorted(shells))
|
|
for r in sorted(shells):
|
|
rw = next(x for x in rows if x['r'] == r)
|
|
print(' r%d %s | %s' % (r, ' > '.join([p for p in rw['path'] if p]), rw['vals'].get(11)))
|
|
|
|
OUT = [rw for rw in rows if rw['r'] not in shells]
|
|
|
|
# 재기록
|
|
for m in list(ws.merged_cells.ranges):
|
|
if m.min_row >= 3: ws.unmerge_cells(str(m))
|
|
for r in range(3, DATA0 + 1):
|
|
for c in COLS:
|
|
ws.cell(r, c).value = None; ws.cell(r, c).hyperlink = None
|
|
for i, rw in enumerate(OUT):
|
|
rr = 3 + i
|
|
for k in range(7):
|
|
ws.cell(rr, 4 + k).value = rw['path'][k]
|
|
ws.cell(rr, 2).value = i + 1
|
|
for c in range(3, 28):
|
|
if c < 4 or c > 10:
|
|
ws.cell(rr, c).value = rw['vals'].get(c)
|
|
ws.cell(rr, 3).value = rw['vals'].get(3)
|
|
if rw['klink']:
|
|
ws.cell(rr, 11).hyperlink = Hyperlink(ref='K%d' % rr, target=rw['klink'])
|
|
for c in COLS:
|
|
ws.cell(rr, c)._style = copy.copy(rw['styles'][c])
|
|
|
|
NROW = 2 + len(OUT)
|
|
# 동일값런 재병합 (D..J)
|
|
for c in range(4, 11):
|
|
r = 3
|
|
while r <= NROW:
|
|
v = ws.cell(r, c).value
|
|
if v in (None, ''):
|
|
r += 1; continue
|
|
key = tuple(ws.cell(r, pc).value for pc in range(4, c + 1))
|
|
r2 = r
|
|
while r2 + 1 <= NROW and tuple(ws.cell(r2 + 1, pc).value for pc in range(4, c + 1)) == key:
|
|
r2 += 1
|
|
if r2 > r:
|
|
for rr in range(r + 1, r2 + 1):
|
|
ws.cell(rr, c).value = None
|
|
ws.merge_cells(start_row=r, start_column=c, end_row=r2, end_column=c)
|
|
r = r2 + 1
|
|
|
|
wb.save(PATH)
|
|
print('삭제 %d행 → 최종 %d행' % (len(shells), len(OUT)))
|