# -*- 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()