#!/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()
MON_PAT = r'(?:January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'
# 1) End-anchored English month + 19XX/20XX year: "March 2019.", "October 2018"
m_end = re.search(rf'\b({MON_PAT})\.?\s*,?\s*((?:19|20)\d{{2}})\s*\.?\s*$', raw, re.I)
if m_end:
mo_str = m_end.group(1).lower().rstrip('.')
if mo_str in MONTH_MAP:
return f"{m_end.group(2)}-{MONTH_MAP[mo_str]}"
# 2) Korean "2026년 6월", "9월, 1999"
m_kor = re.search(r'((?:19|20)\d{2})\s*년\s*(\d{1,2})\s*월', raw)
if m_kor:
yr = int(m_kor.group(1))
mo = int(m_kor.group(2))
if 1 <= mo <= 12:
return f"{yr:04d}-{mo:02d}"
m_kor2 = re.search(r'(\d{1,2})\s*월\s*,?\s*((?:19|20)\d{2})', raw)
if m_kor2:
mo = int(m_kor2.group(1))
yr = int(m_kor2.group(2))
if 1 <= mo <= 12:
return f"{yr:04d}-{mo:02d}"
# 3) Last match of English Month + Year anywhere in string (bypasses preceding article/issue numbers like s19030620 or e2035)
ms = list(re.finditer(rf'\b({MON_PAT})\.?\s*,?\s*((?:19|20)\d{{2}})', raw, re.I))
if ms:
for m_txt in reversed(ms):
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]}"
# 4) ISO date: YYYY-MM-DD, YYYY.MM.DD (where YYYY is 19XX or 20XX)
m = re.search(r'((?:19|20)\d{2})[. /\\-]?(\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 1 <= mo <= 12 and 1 <= day <= 31:
return f"{yr:04d}-{mo:02d}-{day:02d}"
# 5) ISO month: YYYY-MM or YYYY.MM
m_ym = re.search(r'((?:19|20)\d{2})[. /\\-]?(\d{1,2})', raw)
if m_ym:
yr = int(m_ym.group(1))
mo = int(m_ym.group(2))
if 1 <= mo <= 12:
return f"{yr:04d}-{mo:02d}"
# 6) 19XX or 20XX year only
m_y = re.search(r'\b((?:19|20)\d{2})\b', raw)
if m_y:
return f"{m_y.group(1)}"
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 parse_projects():
bodies = parse_standards()
body_map = {b["org"]: b for b in bodies}
# Extract deliverables for CCIS (IEC TC100 - Configurable Car Infotainment Services WG)
ccis_docs = []
if "IEC TC100" in body_map:
for p in body_map["IEC TC100"]["projects"]:
if "Configurable Car Infotainment Services" in p["name"]:
for d in p["documents"]:
ccis_docs.append({
"title": d["title"],
"ref": d["ref"],
"status": d["status"]
})
# Extract deliverables for VLC-IoT (ITU-T SG20)
vlc_docs = []
if "ITU-T SG20" in body_map:
for p in body_map["ITU-T SG20"]["projects"]:
for d in p["documents"]:
vlc_docs.append({
"title": d["title"],
"ref": d["ref"],
"status": d["status"]
})
# Extract deliverables for IoT Standards (ISO/IEC JTC1/SC41)
iot_docs = []
if "ISO/IEC JTC1/SC41" in body_map:
for p in body_map["ISO/IEC JTC1/SC41"]["projects"]:
for d in p["documents"]:
iot_docs.append({
"title": d["title"],
"ref": d["ref"],
"status": d["status"]
})
projects = [
{
"slug": "iot-standards",
"title": "IoT Standards",
"abstract": "Internet of Things covers a huge range of industries and use cases that scale from a single constrained device up to massive cross-platform deployments of embedded technologies and cloud systems connecting in real-time.",
"keywords": ["Internet of Things", "embedded technologies", "cloud systems", "real-time"],
"standardsOrg": "ISO/IEC JTC1/SC41",
"period": body_map.get("ISO/IEC JTC1/SC41", {}).get("period", "2020 ~ Present"),
"deliverables": iot_docs,
},
{
"slug": "ccis",
"title": "CCIS",
"abstract": "In-Vehicle Infotainment (IVI) refers to vehicle systems that combine entertainment and information delivery to drivers and passengers. Configurable Car Infotainment Service (CCIS) is a service that helps users to more easily manage and control In-Vehicle infotainment devices and content.",
"keywords": ["In-Vehicle Infotainment", "CCIS", "infotainment devices"],
"standardsTrack": "IEC/TC 100/TA 17",
"standardsOrg": "IEC TC100",
"period": body_map.get("IEC TC100", {}).get("period", "2018 ~ Present"),
"deliverables": ccis_docs,
},
{
"slug": "vlc-iot",
"title": "VLC-IoT",
"abstract": "Visible-light communication (VLC) transmits data by intensity modulating optical sources, such as light-emitting diodes (LEDs) and laser diodes (LDs), faster than the persistence of the human eye. The goal of IoT-VLC is to design a framework of IoT services based on VLC.",
"keywords": ["Visible-light communication", "VLC", "light-emitting diodes", "laser diodes", "IoT services"],
"standardsTrack": "ITU-T SG20",
"standardsOrg": "ITU-T SG20",
"period": body_map.get("ITU-T SG20", {}).get("period", "2018 ~ 2020"),
"deliverables": vlc_docs,
}
]
return projects
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()
projects = parse_projects()
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')
with open(os.path.join(CONTENT_DIR, "projects.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 { ResearchProject } from "./types";\n\n')
f.write(f'export const researchProjects: ResearchProject[] = {json.dumps(clean_dict(projects), ensure_ascii=False, indent=2)};\n')
if __name__ == "__main__":
main()