SPB Git forge

spb/uqo-imm1003

Public
7commits 1branches 0releases
189.5 MBsize
maindefault branch
7 h agolast push
JavaScript 68% CSS 32%
6.1 KB · 180 lines python
Raw Blame History
1"""bib.py — lecteur BibTeX/biblatex minimaliste (assez pour references.bib) + rendu authoryear."""2from __future__ import annotations34import html5import re67from latex2html import find_matching_brace8910def _clean(v: str) -> str:11    """LaTeX → texte/HTML léger pour les champs bib."""12    v = v.strip()13    v = re.sub(r"\\textsuperscript\{([^}]*)\}", r"<sup>\1</sup>", v)14    v = re.sub(r"\\emph\{([^}]*)\}", r"<em>\1</em>", v)15    v = re.sub(r"\\textit\{([^}]*)\}", r"<em>\1</em>", v)16    v = re.sub(r"\\textbf\{([^}]*)\}", r"<strong>\1</strong>", v)17    v = v.replace("\\&", "&amp;").replace("---", "—").replace("--", "–").replace("~", "\u00a0")18    v = v.replace("\\'e", "é").replace("\\`a", "à").replace("\\^o", "ô").replace('\\"e', "ë")19    v = re.sub(r"\\url\{([^}]*)\}", r"\1", v)20    v = v.replace("{", "").replace("}", "")21    v = re.sub(r"\s+", " ", v)22    return v232425def parse_bib(text: str) -> dict[str, dict]:26    entries: dict[str, dict] = {}27    pos = 028    while True:29        m = re.search(r"@([a-zA-Z]+)\s*\{", text[pos:])30        if not m:31            break32        start = pos + m.end() - 1  # position de '{'33        end = find_matching_brace(text, start)34        body = text[start + 1:end]35        typ = m.group(1).lower()36        pos = end + 137        if typ in ("comment", "preamble", "string"):38            continue39        key, _, rest = body.partition(",")40        key = key.strip()41        fields: dict[str, str] = {}42        i = 043        n = len(rest)44        while i < n:45            fm = re.match(r"\s*([a-zA-Z_-]+)\s*=\s*", rest[i:])46            if not fm:47                # avancer jusqu'à la prochaine virgule48                j = rest.find(",", i)49                if j == -1:50                    break51                i = j + 152                continue53            name = fm.group(1).lower()54            i += fm.end()55            if i >= n:56                break57            if rest[i] == "{":58                j = find_matching_brace(rest, i)59                val = rest[i + 1:j]60                i = j + 161            elif rest[i] == '"':62                j = rest.find('"', i + 1)63                val = rest[i + 1:j]64                i = j + 165            else:66                j = rest.find(",", i)67                if j == -1:68                    j = n69                val = rest[i:j]70                i = j71            fields[name] = val72            # virgule73            k = rest.find(",", i)74            if k == -1:75                break76            i = k + 177        e = {"key": key, "type": typ, "raw": fields}78        e["authors"] = parse_authors(fields.get("author") or fields.get("editor") or fields.get("organization") or "")79        e["short"] = short_label(e["authors"])80        e["year"] = _clean(fields.get("year") or (fields.get("date") or "")[:4] or "s.d.")81        e["title"] = _clean(fields.get("title", ""))82        for f in ("publisher", "address", "edition", "organization", "institution", "note", "url", "urldate", "journal", "volume", "number", "pages", "howpublished", "type"):83            if f in fields:84                e[f] = _clean(fields[f])85        entries[key] = e86    return entries878889def parse_authors(a: str) -> list[dict]:90    a = a.strip()91    if not a:92        return []93    out = []94    for part in re.split(r"\s+and\s+", a):95        part = part.strip()96        if not part:97            continue98        corporate = part.startswith("{") and part.endswith("}")99        p = _clean(part)100        if corporate or "," not in part and len(p.split()) > 3:101            out.append({"last": p, "first": "", "corporate": True})102        elif "," in part:103            last, first = [x.strip() for x in p.split(",", 1)]104            out.append({"last": last, "first": first, "corporate": False})105        else:106            toks = p.split()107            out.append({"last": toks[-1], "first": " ".join(toks[:-1]), "corporate": False})108    return out109110111def short_label(authors: list[dict]) -> str:112    if not authors:113        return "s.n."114    names = [a["last"] for a in authors]115    if len(names) == 1:116        return names[0]117    if len(names) == 2:118        return f"{names[0]} et {names[1]}"119    return f"{names[0]} et al."120121122def format_entry(e: dict) -> str:123    """Rendu HTML d'une entrée, style auteur-année."""124    auths = e["authors"]125    if auths:126        parts = []127        for a in auths:128            if a["corporate"] or not a["first"]:129                parts.append(a["last"])130            else:131                parts.append(f"{a['last']}, {a['first']}")132        if len(parts) > 1:133            authors_html = ", ".join(parts[:-1]) + " et " + parts[-1]134        else:135            authors_html = parts[0]136    else:137        authors_html = "s.n."138    year = e["year"]139    title = e["title"]140    bits = []141    if e.get("edition"):142        ed = e["edition"]143        if re.fullmatch(r"\d+", ed):144            ed = ed + "<sup>e</sup> édition"145        bits.append(ed)146    pub = " : ".join(x for x in [e.get("address"), e.get("publisher") or e.get("organization") or e.get("institution")] if x)147    if pub:148        bits.append(pub)149    if e.get("journal"):150        j = f"<em>{e['journal']}</em>"151        if e.get("volume"):152            j += f", vol. {e['volume']}"153        if e.get("number"):154            j += f", n<sup>o</sup> {e['number']}"155        if e.get("pages"):156            j += f", p. {e['pages']}"157        bits.append(j)158    if e.get("howpublished"):159        bits.append(e["howpublished"])160    if e.get("note"):161        bits.append(e["note"])162    tail = ". ".join(bits)163    h = f'<span class="bib-auth">{authors_html}</span> <span class="bib-year">({year})</span>. <em class="bib-title">{title}</em>.'164    if tail:165        h += f" {tail}."166    if e.get("url"):167        u = html.escape(e["url"], quote=True)168        h += f' <a class="bib-url" href="{u}" target="_blank" rel="noopener">{html.escape(e["url"])}</a>'169        if e.get("urldate"):170            h += f' <span class="bib-urldate">(consulté le {e["urldate"]})</span>'171    return h172173174if __name__ == "__main__":175    import sys176    d = parse_bib(open(sys.argv[1], encoding="utf-8").read())177    for k, e in d.items():178        print(k, "→", e["short"], e["year"])179        print("   ", format_entry(e)[:160])180