공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
191 lines
7.0 KiB
Python
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')
|