Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions docs/publications.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
sidebar_label: 'Publications'
custom_edit_url: 'https://github.com/vernamlab/vernamlab.github.io/tree/dev/docs/publications'
---
import BibBaseEmbed from '@site/src/components/BibBaseEmbed';
import FacultyPublications from '@site/src/components/FacultyPublications';

{/* Start of JavaScript block to dynamically load publications */}
{(() => {
Expand Down Expand Up @@ -68,16 +68,16 @@ This page provides a comprehensive list of publications from Vernam Lab.

### Publications by Fatemeh Ganji

<BibBaseEmbed bibUrl="https://dblp.org/pid/137/6331.bib" />
<FacultyPublications faculty="Fatemeh Ganji" />

### Publications by Patrick Schaumont

<BibBaseEmbed bibUrl="https://dblp.org/pid/39/1269.bib" />
<FacultyPublications faculty="Patrick Schaumont" />

### Publications by Berk Sunar

<BibBaseEmbed bibUrl="https://dblp.org/pid/91/465.bib" />
<FacultyPublications faculty="Berk Sunar" />

### Publications by Shahin Tajik

<BibBaseEmbed bibUrl="https://dblp.org/pid/139/7378.bib" />
<FacultyPublications faculty="Shahin Tajik" />
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"version": "0.0.0",
"private": true,
"scripts": {
"generate-publications": "python3 scripts/generate-faculty-publications.py",
"verify-publications": "python3 scripts/verify-faculty-publications.py",
"prebuild": "npm run generate-publications && npm run verify-publications",
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
Expand Down Expand Up @@ -102,4 +105,4 @@
"js-yaml": "4.1.1"
}
}
}
}
104 changes: 104 additions & 0 deletions scripts/generate-faculty-publications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Generate the faculty publication data from the Scholar exports."""

import json
import re
from pathlib import Path
from xml.etree import ElementTree
from zipfile import ZipFile


ROOT = Path(__file__).resolve().parents[1]
OUTPUT = ROOT / "src" / "data" / "facultyPublications.json"
WORD_NS = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}

SOURCES = {
"Fatemeh Ganji": ROOT / "scholar-data" / "ganji.docx",
"Patrick Schaumont": ROOT / "scholar-data" / "schaumont_cleaned.bib",
"Berk Sunar": ROOT / "scholar-data" / "sunar_cleaned.bib",
"Shahin Tajik": ROOT / "scholar-data" / "tajik_cleaned.bib",
}


def docx_bibliography(path):
with ZipFile(path) as archive:
document = ElementTree.fromstring(archive.read("word/document.xml"))
paragraphs = []
for paragraph in document.findall(".//w:body/w:p", WORD_NS):
paragraphs.append("".join(node.text or "" for node in paragraph.findall(".//w:t", WORD_NS)))
return "\n".join(paragraphs)


def split_entries(bibliography):
starts = list(re.finditer(r"(?m)^@\w+\s*\{", bibliography))
return [bibliography[start.start() : starts[index + 1].start()].strip()
for index, start in enumerate(starts)
if index + 1 < len(starts)] + ([bibliography[starts[-1].start():].strip()] if starts else [])


def field(entry, name):
match = re.search(rf"(?mi)^\s*{name}\s*=\s*", entry)
if not match:
return ""
position = match.end()
if entry[position] in "{\"":
opening = entry[position]
closing = "}" if opening == "{" else '"'
depth = 0
value_start = position + 1
for index in range(value_start, len(entry)):
character = entry[index]
if opening == "{" and character == "{" and (index == 0 or entry[index - 1] != "\\"):
depth += 1
elif character == closing and (index == 0 or entry[index - 1] != "\\"):
if depth == 0:
return entry[value_start:index]
depth -= 1
return entry[position:].split(",", 1)[0].strip()


def clean(value):
replacements = {
r"\&": "&", r"\L": "Ł", r'\"a': "ä", r'\"o': "ö", r'\"u': "ü",
r"\ss": "ß", "{": "", "}": "", "~": " ",
}
for old, new in replacements.items():
value = value.replace(old, new)
return re.sub(r"\s+", " ", value).strip()


def publication(entry):
venue = field(entry, "journal") or field(entry, "booktitle") or field(entry, "howpublished")
if not venue:
venue = field(entry, "publisher") or field(entry, "note")
return {
"title": clean(field(entry, "title")),
"authors": clean(field(entry, "author")).replace(" and ", ", "),
"venue": clean(venue),
"year": int(field(entry, "year")) if field(entry, "year").isdigit() else None,
}


def title_key(title):
return re.sub(r"[^a-z0-9]", "", title.casefold())


def main():
output = {}
for faculty, path in SOURCES.items():
bibliography = docx_bibliography(path) if path.suffix == ".docx" else path.read_text(encoding="utf-8")
unique = {}
for entry in split_entries(bibliography):
item = publication(entry)
key = title_key(item["title"])
if key and key not in unique:
unique[key] = item
output[faculty] = sorted(unique.values(), key=lambda item: (-(item["year"] or 0), item["title"].casefold()))

OUTPUT.parent.mkdir(parents=True, exist_ok=True)
OUTPUT.write_text(json.dumps(output, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print("Generated " + ", ".join(f"{name}: {len(items)}" for name, items in output.items()))


if __name__ == "__main__":
main()
44 changes: 44 additions & 0 deletions scripts/verify-faculty-publications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Verify generated publication data before Docusaurus builds the site."""

import json
import re
from pathlib import Path


DATA = Path(__file__).resolve().parents[1] / "src" / "data" / "facultyPublications.json"
EXPECTED_FACULTY = (
"Fatemeh Ganji",
"Patrick Schaumont",
"Berk Sunar",
"Shahin Tajik",
)


def title_key(title):
return re.sub(r"[^a-z0-9]", "", title.casefold())


def main():
publications = json.loads(DATA.read_text(encoding="utf-8"))
if tuple(publications) != EXPECTED_FACULTY:
raise SystemExit("Generated data does not contain exactly the four expected faculty lists")

for faculty, entries in publications.items():
if not entries:
raise SystemExit(f"Generated publication list is empty for {faculty}")
titles = [title_key(entry["title"]) for entry in entries]
if len(titles) != len(set(titles)):
raise SystemExit(f"Generated publication list contains duplicate titles for {faculty}")
expected_order = sorted(
entries,
key=lambda entry: (-(entry["year"] or 0), entry["title"].casefold()),
)
if entries != expected_order:
raise SystemExit(f"Generated publication list is not sorted for {faculty}")

print(", ".join(f"{faculty}: {len(entries)}" for faculty, entries in publications.items()))


if __name__ == "__main__":
main()
49 changes: 0 additions & 49 deletions src/components/BibBaseEmbed.js

This file was deleted.

22 changes: 22 additions & 0 deletions src/components/FacultyPublications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import publications from '@site/src/data/facultyPublications.json';

export default function FacultyPublications({faculty}) {
const facultyPublications = publications[faculty] || [];

return (
<div className="faculty-publications">
{facultyPublications.map((publication, index) => (
<div className="faculty-publication" key={`${publication.title}-${publication.year || 'unknown'}-${index}`}>
<div className="faculty-publication__title">{publication.title}</div>
{publication.authors && <div>{publication.authors}</div>}
{(publication.venue || publication.year) && (
<div className="faculty-publication__details">
{[publication.venue, publication.year].filter(Boolean).join(', ')}
</div>
)}
</div>
))}
</div>
);
}
15 changes: 14 additions & 1 deletion src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,17 @@

.custom-dot-list-style {
bottom: 10px;
}
}
/* Locally rendered Google Scholar publication records. */
.faculty-publication {
margin-bottom: 1rem;
}

.faculty-publication__title {
font-weight: 600;
}

.faculty-publication__details {
color: var(--ifm-color-emphasis-700);
font-style: italic;
}
Loading