DB_JOB/작업파일/완료/공공기관/25.국방과학연구소/_add_tabexpand.py
hehihoho3 df16c98366 백업: DB수집 전체 스냅샷 (공공기관2 정리 전)
공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:15:40 +09:00

191 lines
7.0 KiB
Python

# -*- coding: utf-8 -*-
import openpyxl, re
from copy import copy
from openpyxl.utils import get_column_letter
from openpyxl.styles import PatternFill
FN='국방과학연구소.xlsx'
BLUE=PatternFill(fill_type='solid', fgColor='FFBDD7EE')
U='https://www.add.re.kr/menu?menuId='
# parent menuId -> list of (label, url, L, M, N, O, S)
def page(label, mid): return (label, U+mid, '페이지', 1, '어문', '미부착', None)
TABS = {
'MENU02790':[page('국제협력','MENU02773'),page('방산수출','MENU02774'),page('수출지원 절차','MENU02775')],
'MENU02776':[page('민·군기술협력','MENU02777'),page('국방기술이전·사업화','MENU02778')],
'MENU02188':[page('故 김용철 님','MENU02192'),page('이충희 님','MENU02193')],
'MENU02191':[page('제1회 수상자','MENU02194'),page('제2회 수상자','MENU02195'),page('제3회 수상자','MENU02565'),
page('제4회 수상자','MENU02803'),page('제5회 수상자','MENU02804'),page('제6회 수상자','MENU03106'),
page('제7회 수상자','MENU03107'),page('제8회 수상자','MENU03108'),page('제9회 수상자','MENU03109'),
page('제10회 수상자','MENU03110')],
'MENU02160':[page('전자정보원','MENU03095'),page('전자저널','MENU03096'),page('전자책','MENU03097'),
page('WEB-DB','MENU03098'),
('외부접속안내','https://www.add.re.kr/resources/files/information.zip','사이트',None,None,None,'외부링크')],
'MENU02213':[page('정규직','MENU02214'),page('전문계약직','MENU02215')],
'MENU03037':[page('복리후생','MENU03038'),page('성과포상','MENU03039'),page('교육제도','MENU03040')],
}
DEL_MENU='MENU02024' # 인사말 duplicate -> delete
FILL_F={'MENU02769':'ADD 신문고'} # fill empty F
wb=openpyxl.load_workbook(FN)
ws=wb.active
MAXC=27
def midof(url):
if not url: return None
m=re.search(r'menuId=([A-Z0-9]+)', str(url))
return m.group(1) if m else None
# effective D,E via merges
eff={4:{},5:{}}
for col in (4,5):
for r in range(3, 123):
eff[col][r]=ws.cell(r,col).value
for mr in ws.merged_cells.ranges:
if mr.min_col<=col<=mr.max_col and mr.min_row>=3:
top=ws.cell(mr.min_row, col).value
for r in range(mr.min_row, mr.max_row+1):
eff[col][r]=top
# build records
recs=[]
for r in range(3,123):
rec={'src':r, 'new':False, 'mark':set()}
rec['D']=eff[4][r]; rec['E']=eff[5][r]; rec['F']=ws.cell(r,6).value
for c in range(7,MAXC+1):
rec[c]=ws.cell(r,c).value
rec['url']=ws.cell(r,11).value
hl=ws.cell(r,11).hyperlink
rec['hlink']=hl.target if hl else rec['url']
recs.append(rec)
# helper find rec by menuId
def findidx(mid):
for i,rc in enumerate(recs):
if midof(rc['url'])==mid: return i
return None
# Task3: delete 인사말 02024 (carry D to next handled by eff already on next rec)
di=findidx(DEL_MENU)
assert di is not None, 'del row not found'
# ensure following rec keeps D=ADD소개 (it does via eff). just drop.
del recs[di]
# Task2: fill F
for mid,val in FILL_F.items():
i=findidx(mid)
assert i is not None
recs[i]['F']=val; recs[i]['mark'].add(6)
# Task1: tab expansion (process; indices stable because we rebuild list)
out=[]
for rc in recs:
mid=midof(rc['url'])
if mid in TABS:
tabs=TABS[mid]
# reuse parent row for first tab
first=tabs[0]
rc[7]=first[0] # G label
rc['url']=first[1]; rc['hlink']=first[1]; rc[11]=first[1]
rc[12]=first[2]; rc[13]=first[3]; rc[14]=first[4]; rc[15]=first[5]; rc[19]=first[6]
rc['mark'].update([7,11,12,13,14,15] + ([19] if first[6] else []))
out.append(rc)
# children
for t in tabs[1:]:
ch={'src':rc['src'], 'new':True, 'mark':set(range(4,16))}
ch['D']=rc['D']; ch['E']=rc['E']; ch['F']=rc['F']
for c in range(7,MAXC+1): ch[c]=None
ch[3]=ws.cell(3,3).value # site name (col C handled separately below)
ch[7]=t[0]; ch['url']=t[1]; ch['hlink']=t[1]; ch[11]=t[1]
ch[12]=t[2]; ch[13]=t[3]; ch[14]=t[4]; ch[15]=t[5]; ch[19]=t[6]
if t[6]: ch['mark'].add(19)
out.append(ch)
else:
out.append(rc)
recs=out
print('final rows:', len(recs))
# snapshot source styles BEFORE any overwrite
stylesnap={}
for r in range(3,123):
for c in range(1,MAXC+1):
stylesnap[(r,c)]=copy(ws.cell(r,c)._style)
# ---- WRITE ----
# unmerge data merges (>=3)
for mr in list(ws.merged_cells.ranges):
if mr.min_row>=3:
ws.unmerge_cells(str(mr))
SITE='국방과학연구소'
n=len(recs)
for i,rc in enumerate(recs):
r=3+i
src=rc['src']
# style: copy from snapshot of src row for all cols
for c in range(1,MAXC+1):
ws.cell(r,c)._style=copy(stylesnap[(src,c)])
# values
ws.cell(r,1).value=None
ws.cell(r,2).value=i+1 # B 순번
ws.cell(r,3).value=SITE # C
ws.cell(r,4).value=rc['D']
ws.cell(r,5).value=rc['E']
ws.cell(r,6).value=rc['F']
for c in range(7,MAXC+1):
ws.cell(r,c).value=rc.get(c)
# K hyperlink
kc=ws.cell(r,11)
kc.value=rc['url']
kc.hyperlink=rc['hlink'] if rc['url'] else None
# blue marks
for c in rc['mark']:
ws.cell(r,c).fill=BLUE
ws.row_dimensions[r].height=15
# clear leftover rows
for r in range(3+n, 197):
for c in range(1,MAXC+1):
cell=ws.cell(r,c)
cell.value=None
cell.hyperlink=None
cell.fill=PatternFill(fill_type=None)
# ---- RE-MERGE D,E,F ----
def remerge(col, scope_cols):
r=3
while r < 3+n:
v=ws.cell(r,col).value
if v is None or v=='':
r+=1; continue
r2=r
while r2+1 < 3+n:
nv=ws.cell(r2+1,col).value
# next row belongs to same group only if its scope matches AND value equals
same=True
# value: for D compare own; for E/F the body rows were written with eff value too
if nv!=v: same=False
for sc in scope_cols:
if ws.cell(r2+1,sc).value!=ws.cell(r,sc).value: same=False; break
if not same: break
r2+=1
if r2>r:
# blank body values BEFORE merging (merged body cells are read-only)
for rr in range(r+1,r2+1):
ws.cell(rr,col).value=None
ws.merge_cells(start_row=r,start_column=col,end_row=r2,end_column=col)
r=r2+1
# For E/F merge we need body rows to have the eff value. Currently children wrote eff D/E/F.
# But parent/normal rows wrote raw F (=eff for them). Fill-down F across written rows for groups:
# Actually children F were set = parent F (eff). Non-group rows F = own raw. Good.
# But we must temporarily fill E down for rows where E blank? We wrote eff E on every rec, so fine.
# deepest first so scope columns still hold their values when shallower merges run
remerge(6, [4,5]) # F within same D,E
remerge(5, [4]) # E within same D
remerge(4, []) # D by contiguous equal
wb.save(FN)
print('saved')