#!/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'
    1. (.*?)
    2. ', 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'
    3. (.*?)
    4. ', 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()