공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
165 lines
6.0 KiB
Python
165 lines
6.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""335~443행 일반 다단계 탭 분해. _split_plan.json 의 29개 잡을 적용.
|
|
- 각 잡: 소스행의 가장 깊은 카테고리열(base)을 찾아, 자식을 base+1, 손자를 base+2에 배치
|
|
- 잡은 행번호 내림차순(아래→위)으로 처리해 미처리 잡 인덱스 불변
|
|
- 검증된 시프트 로직(값+_style+hyperlink, 병합 +delta 시프트, base열 신규 병합)
|
|
- 순번(B)은 마지막에 305~끝 일괄 재번호
|
|
--write 로 적용.
|
|
"""
|
|
import sys, json, shutil
|
|
from copy import copy
|
|
import openpyxl
|
|
from openpyxl.utils.cell import range_boundaries
|
|
|
|
XLSX = r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵\충청남도\2.공주시\충청남도_공주시.xlsx'
|
|
PLAN = r'D:\01.프로젝트\DB수집\_스크립트\_split_plan.json'
|
|
B_START_ROW = 305
|
|
B_START_VAL = 311
|
|
CATCOLS = [4, 5, 6, 7, 8, 9, 10] # D..J
|
|
|
|
|
|
def resolve_path(ws, row):
|
|
"""소스행의 카테고리 경로(병합 반영). {col: value}"""
|
|
path = {}
|
|
for c in CATCOLS:
|
|
v = ws.cell(row, c).value
|
|
if v is None:
|
|
# 병합 top-left 찾기
|
|
for mr in ws.merged_cells.ranges:
|
|
if mr.min_col <= c <= mr.max_col and mr.min_row <= row <= mr.max_row:
|
|
v = ws.cell(mr.min_row, mr.min_col).value
|
|
break
|
|
if v is not None:
|
|
path[c] = v
|
|
return path
|
|
|
|
|
|
def flatten_leaves(job):
|
|
"""잡 children 트리 -> 잎 리스트 [(lvl1name, lvl2name|None, url, L, M)]"""
|
|
out = []
|
|
for ch in job['children']:
|
|
if ch.get('children'):
|
|
for gc in ch['children']:
|
|
out.append((ch['name'], gc['name'], gc['url'], gc.get('L', '페이지'), gc.get('M', 1)))
|
|
else:
|
|
out.append((ch['name'], None, ch['url'], ch.get('L', '페이지'), ch.get('M', 1)))
|
|
return out
|
|
|
|
|
|
def apply_job(ws, source, leaves, tmpl, last_data):
|
|
N = len(leaves)
|
|
delta = N - 1
|
|
base = max(resolve_path(ws, source).keys()) # 가장 깊은 카테고리열
|
|
base_val = ws.cell(source, base).value
|
|
if base_val is None:
|
|
p = resolve_path(ws, source); base_val = p[base]
|
|
|
|
# 1) 병합 해제(전체) — 시프트 위해
|
|
old_merges = [str(mr) for mr in list(ws.merged_cells.ranges)]
|
|
for mr in old_merges:
|
|
ws.unmerge_cells(mr)
|
|
|
|
# 2) 시프트 source+1..last_data -> +delta (아래에서 위로)
|
|
if delta > 0:
|
|
for sr in range(last_data, source, -1):
|
|
dr = sr + delta
|
|
for c in range(1, 28):
|
|
s = ws.cell(sr, c); d = ws.cell(dr, c)
|
|
d.value = s.value
|
|
if s.has_style:
|
|
d._style = copy(s._style)
|
|
if s.hyperlink is not None:
|
|
d.hyperlink = copy(s.hyperlink); d.hyperlink.ref = d.coordinate
|
|
s.hyperlink = None
|
|
else:
|
|
d.hyperlink = None
|
|
|
|
# 3) source..source+N-1 클리어 + 템플릿 스타일
|
|
for r in range(source, source + N):
|
|
for c in range(1, 28):
|
|
cell = ws.cell(r, c)
|
|
cell.value = None; cell.hyperlink = None
|
|
if c in tmpl:
|
|
cell._style = copy(tmpl[c])
|
|
|
|
# 4) 잎 기입
|
|
l1col, l2col = base + 1, base + 2
|
|
prev_l1 = None
|
|
for i, (l1, l2, url, L, M) in enumerate(leaves):
|
|
r = source + i
|
|
if l1 != prev_l1:
|
|
ws.cell(r, l1col).value = l1
|
|
prev_l1 = l1
|
|
if l2:
|
|
ws.cell(r, l2col).value = l2
|
|
kc = ws.cell(r, 11); kc.value = url; kc.hyperlink = url
|
|
ws.cell(r, 12).value = L
|
|
ws.cell(r, 13).value = M
|
|
ws.cell(r, 14).value = '어문'
|
|
ws.cell(r, 15).value = '미부착'
|
|
# base 값 최상단
|
|
ws.cell(source, base).value = base_val
|
|
|
|
# 5) 병합 재생성: 기존(>=source+1행 +delta) + 신규
|
|
def shift(rr):
|
|
return rr + delta if rr >= source + 1 else rr
|
|
for rng in old_merges:
|
|
c1, r1, c2, r2 = range_boundaries(rng)
|
|
ws.merge_cells(start_row=shift(r1), start_column=c1, end_row=shift(r2), end_column=c2)
|
|
# base열 신규 병합(잡 전체)
|
|
if N > 1:
|
|
ws.merge_cells(start_row=source, start_column=base, end_row=source + N - 1, end_column=base)
|
|
# l1col 손자그룹 병합(같은 l1 연속 run)
|
|
i = 0
|
|
while i < N:
|
|
j = i
|
|
while j + 1 < N and leaves[j + 1][0] == leaves[i][0]:
|
|
j += 1
|
|
if j > i: # 2개 이상 -> 병합
|
|
ws.merge_cells(start_row=source + i, start_column=l1col, end_row=source + j, end_column=l1col)
|
|
i = j + 1
|
|
|
|
return last_data + delta
|
|
|
|
|
|
def main():
|
|
write = '--write' in sys.argv
|
|
jobs = json.load(open(PLAN, encoding='utf-8'))
|
|
jobs.sort(key=lambda j: j['row'], reverse=True) # 내림차순(아래→위)
|
|
|
|
wb = openpyxl.load_workbook(XLSX)
|
|
ws = wb.active
|
|
# 템플릿 스타일(클린 데이터행 305)
|
|
tmpl = {c: copy(ws.cell(305, c)._style) for c in range(1, 28) if ws.cell(305, c).has_style}
|
|
|
|
# 현재 마지막 데이터행
|
|
last_data = 3
|
|
for r in range(3, ws.max_row + 1):
|
|
if ws.cell(r, 11).value:
|
|
last_data = r
|
|
|
|
print('잡 %d개, 시작 last_data=%d' % (len(jobs), last_data))
|
|
if not write:
|
|
for j in jobs:
|
|
print(' r%d %s -> 잎 %d' % (j['row'], j['label'], len(flatten_leaves(j))))
|
|
print('(DRY)')
|
|
return
|
|
|
|
bak = XLSX.replace('.xlsx', '_backup_분야별분해전.xlsx')
|
|
shutil.copy(XLSX, bak)
|
|
for j in jobs:
|
|
leaves = flatten_leaves(j)
|
|
last_data = apply_job(ws, j['row'], leaves, tmpl, last_data)
|
|
print(' 적용 r%d %s (+%d) last_data=%d' % (j['row'], j['label'][:24], len(leaves) - 1, last_data))
|
|
|
|
# 순번 B 305~last 일괄 재번호
|
|
for r in range(B_START_ROW, last_data + 1):
|
|
ws.cell(r, 2).value = B_START_VAL + (r - B_START_ROW)
|
|
|
|
wb.save(XLSX)
|
|
print('저장 완료. 백업:', bak, '| 최종 데이터행', last_data)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|