#!/usr/bin/env python3
import os
import re
import json
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
ISL_DIR = os.path.join(BASE_DIR, "..", "ISL-2026")
CONTENT_DIR = os.path.join(BASE_DIR, "content")
MONTH_MAP = {
"january": "01", "jan": "01",
"february": "02", "feb": "02",
"march": "03", "mar": "03",
"april": "04", "apr": "04",
"may": "05",
"june": "06", "jun": "06",
"july": "07", "jul": "07",
"august": "08", "aug": "08",
"september": "09", "sep": "09", "sept": "09",
"october": "10", "oct": "10",
"november": "11", "nov": "11",
"december": "12", "dec": "12",
}
def to_iso(raw):
if not raw or "in progress" in raw.lower():
return None
raw = raw.strip()
# 1) YYYY-MM-DD or YYYY.MM.DD
m = re.search(r'(\d{4})[. /\\-]?(\d{1,2})[. /\\-]?(\d{1,2})', raw)
if m:
yr = int(m.group(1))
mo = int(m.group(2))
day = int(m.group(3))
if mo <= 12 and day <= 31:
return f"{yr:04d}-{mo:02d}-{day:02d}"
# 2) "May 2026", "Feb. 2008", "August 2018"
m_txt = re.search(r'([A-Za-z]+)\.?\s*(\d{4})', raw)
if m_txt:
mo_str = m_txt.group(1).lower().rstrip('.')
yr_str = m_txt.group(2)
if mo_str in MONTH_MAP:
return f"{yr_str}-{MONTH_MAP[mo_str]}"
# 3) "2019년 12월", "9월, 1999"
m_kor = re.search(r'(\d{4})년\s*(\d{1,2})월', raw)
if m_kor:
return f"{int(m_kor.group(1)):04d}-{int(m_kor.group(2)):02d}"
m_kor2 = re.search(r'(\d{1,2})월.*?,?\s*(\d{4})', raw)
if m_kor2:
return f"{int(m_kor2.group(2)):04d}-{int(m_kor2.group(1)):02d}"
# 4) YYYY-MM or YYYY.MM
m_ym = re.search(r'(\d{4})[. /\\-]?(\d{1,2})', raw)
if m_ym:
yr = int(m_ym.group(1))
mo = int(m_ym.group(2))
if mo <= 12:
return f"{yr:04d}-{mo:02d}"
# 5) YYYY only
m_y = re.search(r'(\d{4})', raw)
if m_y:
return f"{int(m_y.group(1)):04d}"
return raw
def read_file(filename):
path = os.path.join(ISL_DIR, filename)
with open(path, "r", encoding="utf-8-sig") as f:
return f.read()
def parse_members():
html = read_file("member.html")
parts = re.split(r'
\s*Alumni.*?
', html, flags=re.S | re.I)
active_html = parts[0]
alumni_html = parts[1] if len(parts) > 1 else ""
degree_map = {
"Professor": "Professor",
"Ph. D.": "PhD",
"Ph.D.": "PhD",
"Ph. D. Candidate": "PhDCandidate",
"Ph.D. Candidate": "PhDCandidate",
"Ph. D. Student": "PhDStudent",
"Ph.D. Student": "PhDStudent",
"M. S. Candidate": "MSCandidate",
"M.S. Candidate": "MSCandidate",
"M. S. Student": "MSStudent",
"M.S. Student": "MSStudent",
}
# Active members
active_members = []
active_items = re.findall(r'(.*?)
', active_html, flags=re.S | re.I)
for item in active_items:
clean_item = re.sub(r'.*?', '', item, flags=re.I)
clean_item = re.sub(r'<.*?>', '', clean_item)
clean_item = clean_item.strip().replace('\n', ' ')
if not clean_item:
continue
m = re.search(r'^(.*?)\s*\((.*?),\s*(.*?)\)(?:\s*with\s*(.*))?$', clean_item)
if m:
en = m.group(1).strip()
ko = m.group(2).strip()
deg_str = m.group(3).strip()
aff = m.group(4).strip() if m.group(4) else None
else:
# Case like Hoang Anh Dang (M. S. Student)
m2 = re.search(r'^(.*?)\s*\((.*?)\)(?:\s*with\s*(.*))?$', clean_item)
if m2:
en = m2.group(1).strip()
ko = en
deg_str = m2.group(2).strip()
aff = m2.group(3).strip() if m2.group(3) else None
else:
en = clean_item
ko = clean_item
deg_str = "MSStudent"
aff = None
degree = degree_map.get(deg_str, "MSStudent")
mem = {"ko": ko, "en": en, "degree": degree}
if aff:
mem["affiliation"] = aff
active_members.append(mem)
# Alumni
alumni = []
alumni_items = re.findall(r'(.*?)
', alumni_html, flags=re.S | re.I)
if len(alumni_items) < 60:
alumni_items = re.findall(r'(.*?)(?=||
|$)', alumni_html, flags=re.S | re.I)
month_map = {"January":"01","February":"02","March":"03","April":"04","May":"05","June":"06","July":"07","August":"08","September":"09","October":"10","November":"11","December":"12"}
for item in alumni_items:
clean = re.sub(r'<.*?>', '\n', item).strip()
lines = [l.strip() for l in clean.split('\n') if l.strip()]
if not lines:
continue
first_line = lines[0]
if "Alumni" in first_line:
continue
m = re.search(r'^(.*?)\s*\((?:(.*?),\s*)?(Ph\.\s*D\.|Ph\.D\.|M\.\s*S\.|M\.S\.)\s+([A-Za-z]+\s+\d{4})', first_line)
if not m:
m = re.search(r'^(.*?)\s*\((?:(.*?),\s*)?(Ph\.\s*D\.|Ph\.D\.|M\.\s*S\.|M\.S\.)', first_line)
if m:
en = m.group(1).strip()
ko = m.group(2).strip() if m.group(2) else en
deg_raw = m.group(3).strip()
date_raw = m.group(4).strip() if len(m.groups()) >= 4 and m.group(4) else "August 2026"
major = None
if " in " in first_line:
m_maj = re.search(r'\s+in\s+([A-Za-z\s]+)', first_line)
if m_maj: major = m_maj.group(1).strip()
degree = "PhD" if "Ph" in deg_raw else "MS"
parts = date_raw.split()
grad_date = f"{parts[1]}-{month_map.get(parts[0], '01')}" if len(parts)==2 else "2026-01"
cur_pos = " ".join(lines[1:]) if len(lines) > 1 else None
alum = {
"ko": ko,
"en": en,
"degree": degree,
"graduatedAt": grad_date,
}
if major: alum["major"] = major
if cur_pos: alum["currentPosition"] = cur_pos
alumni.append(alum)
return active_members, alumni
def parse_publications():
intl_html = read_file("paper-international.html")
dom_html = read_file("paper-domestic.html")
pat_html = read_file("patent.html")
intl_pubs = []
intl_items = re.findall(r'(.*?)', intl_html, flags=re.S | re.I)
for item in intl_items:
m_title = re.search(r'\s*"(.*?)"\s*
\s*', item, flags=re.S | re.I)
if not m_title:
m_title = re.search(r'\s*"(.*?)"\s*', item, flags=re.S | re.I)
title = m_title.group(1).replace('\n', ' ').strip() if m_title else ""
m_venue = re.search(r'\s*(.*?)\s*', item, flags=re.S | re.I)
venue = m_venue.group(1).replace('\n', ' ').strip() if m_venue else ""
rest = item[m_venue.end():] if m_venue else item
rest_clean = re.sub(r'<.*?>', '', rest).replace('\n', ' ').strip()
rest_clean = re.sub(r'^\s*,\s*', '', rest_clean)
pub_date = to_iso(rest_clean) or "2026-01"
intl_pubs.append({
"category": "intl-journal-conf",
"title": title,
"venue": venue,
"volume": rest_clean,
"publishedAt": pub_date
})
dom_pubs = []
dom_items = re.findall(r'(.*?)', dom_html, flags=re.S | re.I)
for item in dom_items:
m_title = re.search(r'\s*"(.*?)"\s*
\s*', item, flags=re.S | re.I)
if not m_title:
m_title = re.search(r'\s*"(.*?)"\s*', item, flags=re.S | re.I)
title = m_title.group(1).replace('\n', ' ').strip() if m_title else ""
m_venue = re.search(r'\s*(.*?)\s*', item, flags=re.S | re.I)
venue = m_venue.group(1).replace('\n', ' ').strip() if m_venue else ""
rest = item[m_venue.end():] if m_venue else item
rest_clean = re.sub(r'<.*?>', '', rest).replace('\n', ' ').strip()
rest_clean = re.sub(r'^\s*,\s*', '', rest_clean)
is_kci = "(KCI)" in item or "(KCI)" in rest_clean
pub_date = to_iso(rest_clean) or "2026-01"
dom_pubs.append({
"category": "domestic-journal-conf",
"title": title,
"venue": venue,
"volume": rest_clean,
"publishedAt": pub_date,
"kci": is_kci
})
patents = []
pat_items = re.findall(r'(.*?)', pat_html, flags=re.S | re.I)
for item in pat_items:
clean = re.sub(r'<.*?>', '\n', item)
lines = [l.strip() for l in clean.split('\n') if l.strip()]
if not lines:
continue
title = lines[0]
inventors = ""
app_no = ""
app_date = None
reg_no = None
reg_date = None
country = "한국"
for l in lines[1:]:
if l.startswith("발명자:"):
inventors = l.replace("발명자:", "").strip()
elif l.startswith("출원번호:"):
m = re.search(r'출원번호:\s*([\d-]+)\s*\((.*?),\s*([\d.]+)\)', l)
if m:
app_no = m.group(1)
country = m.group(2)
app_date = to_iso(m.group(3))
else:
app_no = l.replace("출원번호:", "").strip()
elif l.startswith("등록번호:"):
m = re.search(r'등록번호:\s*([\d-]+)\s*\((.*?),\s*([\d.]+)\)', l)
if m:
reg_no = m.group(1)
reg_date = to_iso(m.group(3))
else:
reg_no = l.replace("등록번호:", "").strip()
pub_date = app_date[:7] if app_date else "2026-01"
patents.append({
"category": "patent",
"title": title,
"inventors": inventors,
"applicationNo": app_no,
"applicationAt": app_date,
"registrationNo": reg_no,
"registrationAt": reg_date,
"country": country,
"publishedAt": pub_date
})
return intl_pubs, dom_pubs, patents
def parse_lectures():
html = read_file("lecture.html")
semesters = []
sections = re.split(r'(.*?)
', html, flags=re.I)
for i in range(1, len(sections), 2):
term_str = sections[i].strip()
body = sections[i+1]
parts = term_str.split()
if len(parts) != 2:
continue
term = parts[0]
year = int(parts[1])
courses = []
c_items = re.findall(r'(.*?)(?:
|
|$)', body, flags=re.S | re.I)
for item in c_items:
clean = re.sub(r'<.*?>', '', item).strip()
if not clean:
continue
if "No Class" in clean:
courses.append({"code": "SABBATICAL", "en": "No Class (Sabbatical)", "note": "Sabbatical"})
else:
m = re.search(r'^(.*?)\s*\((.*?)\)$', clean)
if m:
courses.append({"code": m.group(2).strip(), "en": m.group(1).strip()})
else:
courses.append({"code": "COMP", "en": clean})
semesters.append({
"term": term,
"year": year,
"courses": courses
})
return semesters
def parse_standards():
html = read_file("standard.html")
bodies = []
sections = re.split(r'(.*?)
', html, flags=re.I)
for i in range(1, len(sections), 2):
header = sections[i].strip()
body = sections[i+1]
m_head = re.search(r'^(.*?)\s*\((.*?)\):\s*(.*)$', header)
if not m_head:
continue
org = m_head.group(1).strip()
full = m_head.group(2).strip()
period = m_head.group(3).strip()
scope = "domestic" if "TTA" in org else "international"
role = "Convenor" if "Convenor" in header or "Convenor" in body else None
projects = []
if re.search(r'^\s*o\s+', body, flags=re.M):
proj_blocks = re.split(r'\s*o\s+', body)
for proj_b in proj_blocks:
if not proj_b.strip():
continue
lines = proj_b.strip().split('')
p_head = lines[0].replace('
', '').strip()
m_proj = re.search(r'^(.*?):\s*(.*)$', p_head)
wg = m_proj.group(1).strip() if m_proj else "WG"
p_name = m_proj.group(2).strip() if m_proj else p_head
docs = []
if len(lines) > 1:
doc_items = re.findall(r'- (.*?)
', lines[1], flags=re.S | re.I)
for d_item in doc_items:
d_clean = re.sub(r'<.*?>', '\n', d_item)
d_lines = [l.strip() for l in d_clean.split('\n') if l.strip()]
if not d_lines:
continue
d_title = d_lines[0].strip('" ')
d_rest = " ".join(d_lines[1:]) if len(d_lines) > 1 else ""
status = "IS"
if "In Progress" in d_rest: status = "InProgress"
elif "WDTR" in d_rest or "CDTR" in d_rest: status = "WDTR"
elif "CDV" in d_rest: status = "CDV"
elif "TR" in d_rest: status = "TR"
elif "TS" in d_rest: status = "TS"
m_date = re.search(r'\((.*?)\)', d_rest)
pub_date = to_iso(m_date.group(1)) if m_date else None
docs.append({
"title": d_title,
"ref": d_rest,
"status": status,
"publishedAt": pub_date
})
projects.append({
"wg": wg,
"name": p_name,
"documents": docs
})
else:
doc_items = re.findall(r'- (.*?)
', body, flags=re.S | re.I)
docs = []
for d_item in doc_items:
d_clean = re.sub(r'<.*?>', '\n', d_item)
d_lines = [l.strip() for l in d_clean.split('\n') if l.strip()]
if not d_lines:
continue
d_title = d_lines[0].strip('" ')
d_rest = " ".join(d_lines[1:]) if len(d_lines) > 1 else ""
status = "IS"
if "In Progress" in d_rest: status = "InProgress"
elif "WDTR" in d_rest or "CDTR" in d_rest: status = "WDTR"
elif "CDV" in d_rest: status = "CDV"
elif "TR" in d_rest: status = "TR"
elif "TS" in d_rest: status = "TS"
m_date = re.search(r'\((.*?)\)', d_rest)
pub_date = to_iso(m_date.group(1)) if m_date else None
docs.append({
"title": d_title,
"ref": d_rest,
"status": status,
"publishedAt": pub_date
})
projects.append({
"wg": "Main",
"name": full,
"documents": docs
})
bodies.append({
"org": org,
"full": full,
"scope": scope,
"period": period,
"role": role,
"projects": projects
})
return bodies
def clean_dict(obj):
if isinstance(obj, dict):
return {k: clean_dict(v) for k, v in obj.items() if v is not None}
elif isinstance(obj, list):
return [clean_dict(v) for v in obj if v is not None]
return obj
def main():
active_m, alumni_m = parse_members()
intl_p, dom_p, pat_p = parse_publications()
semesters = parse_lectures()
bodies = parse_standards()
os.makedirs(CONTENT_DIR, exist_ok=True)
with open(os.path.join(CONTENT_DIR, "members.ts"), "w", encoding="utf-8") as f:
f.write('// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND\n')
f.write('import { Member, Alumnus } from "./types";\n\n')
f.write(f'export const activeMembers: Member[] = {json.dumps(clean_dict(active_m), ensure_ascii=False, indent=2)};\n\n')
f.write(f'export const alumni: Alumnus[] = {json.dumps(clean_dict(alumni_m), ensure_ascii=False, indent=2)};\n')
with open(os.path.join(CONTENT_DIR, "publications.ts"), "w", encoding="utf-8") as f:
f.write('// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND\n')
f.write('import { Publication, Patent } from "./types";\n\n')
all_pubs = intl_p + dom_p
f.write(f'export const publications: Publication[] = {json.dumps(clean_dict(all_pubs), ensure_ascii=False, indent=2)};\n\n')
f.write(f'export const patents: Patent[] = {json.dumps(clean_dict(pat_p), ensure_ascii=False, indent=2)};\n')
with open(os.path.join(CONTENT_DIR, "lectures.ts"), "w", encoding="utf-8") as f:
f.write('// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND\n')
f.write('import { Semester } from "./types";\n\n')
f.write(f'export const semesters: Semester[] = {json.dumps(clean_dict(semesters), ensure_ascii=False, indent=2)};\n')
with open(os.path.join(CONTENT_DIR, "standards.ts"), "w", encoding="utf-8") as f:
f.write('// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND\n')
f.write('import { StandardsBody } from "./types";\n\n')
f.write(f'export const standardsBodies: StandardsBody[] = {json.dumps(clean_dict(bodies), ensure_ascii=False, indent=2)};\n')
if __name__ == "__main__":
main()