"""bib.py — lecteur BibTeX/biblatex minimaliste (assez pour references.bib) + rendu authoryear.""" from __future__ import annotations import html import re from latex2html import find_matching_brace def _clean(v: str) -> str: """LaTeX → texte/HTML léger pour les champs bib.""" v = v.strip() v = re.sub(r"\\textsuperscript\{([^}]*)\}", r"\1", v) v = re.sub(r"\\emph\{([^}]*)\}", r"\1", v) v = re.sub(r"\\textit\{([^}]*)\}", r"\1", v) v = re.sub(r"\\textbf\{([^}]*)\}", r"\1", v) v = v.replace("\\&", "&").replace("---", "—").replace("--", "–").replace("~", "\u00a0") v = v.replace("\\'e", "é").replace("\\`a", "à").replace("\\^o", "ô").replace('\\"e', "ë") v = re.sub(r"\\url\{([^}]*)\}", r"\1", v) v = v.replace("{", "").replace("}", "") v = re.sub(r"\s+", " ", v) return v def parse_bib(text: str) -> dict[str, dict]: entries: dict[str, dict] = {} pos = 0 while True: m = re.search(r"@([a-zA-Z]+)\s*\{", text[pos:]) if not m: break start = pos + m.end() - 1 # position de '{' end = find_matching_brace(text, start) body = text[start + 1:end] typ = m.group(1).lower() pos = end + 1 if typ in ("comment", "preamble", "string"): continue key, _, rest = body.partition(",") key = key.strip() fields: dict[str, str] = {} i = 0 n = len(rest) while i < n: fm = re.match(r"\s*([a-zA-Z_-]+)\s*=\s*", rest[i:]) if not fm: # avancer jusqu'à la prochaine virgule j = rest.find(",", i) if j == -1: break i = j + 1 continue name = fm.group(1).lower() i += fm.end() if i >= n: break if rest[i] == "{": j = find_matching_brace(rest, i) val = rest[i + 1:j] i = j + 1 elif rest[i] == '"': j = rest.find('"', i + 1) val = rest[i + 1:j] i = j + 1 else: j = rest.find(",", i) if j == -1: j = n val = rest[i:j] i = j fields[name] = val # virgule k = rest.find(",", i) if k == -1: break i = k + 1 e = {"key": key, "type": typ, "raw": fields} e["authors"] = parse_authors(fields.get("author") or fields.get("editor") or fields.get("organization") or "") e["short"] = short_label(e["authors"]) e["year"] = _clean(fields.get("year") or (fields.get("date") or "")[:4] or "s.d.") e["title"] = _clean(fields.get("title", "")) for f in ("publisher", "address", "edition", "organization", "institution", "note", "url", "urldate", "journal", "volume", "number", "pages", "howpublished", "type"): if f in fields: e[f] = _clean(fields[f]) entries[key] = e return entries def parse_authors(a: str) -> list[dict]: a = a.strip() if not a: return [] out = [] for part in re.split(r"\s+and\s+", a): part = part.strip() if not part: continue corporate = part.startswith("{") and part.endswith("}") p = _clean(part) if corporate or "," not in part and len(p.split()) > 3: out.append({"last": p, "first": "", "corporate": True}) elif "," in part: last, first = [x.strip() for x in p.split(",", 1)] out.append({"last": last, "first": first, "corporate": False}) else: toks = p.split() out.append({"last": toks[-1], "first": " ".join(toks[:-1]), "corporate": False}) return out def short_label(authors: list[dict]) -> str: if not authors: return "s.n." names = [a["last"] for a in authors] if len(names) == 1: return names[0] if len(names) == 2: return f"{names[0]} et {names[1]}" return f"{names[0]} et al." def format_entry(e: dict) -> str: """Rendu HTML d'une entrée, style auteur-année.""" auths = e["authors"] if auths: parts = [] for a in auths: if a["corporate"] or not a["first"]: parts.append(a["last"]) else: parts.append(f"{a['last']}, {a['first']}") if len(parts) > 1: authors_html = ", ".join(parts[:-1]) + " et " + parts[-1] else: authors_html = parts[0] else: authors_html = "s.n." year = e["year"] title = e["title"] bits = [] if e.get("edition"): ed = e["edition"] if re.fullmatch(r"\d+", ed): ed = ed + "e édition" bits.append(ed) pub = " : ".join(x for x in [e.get("address"), e.get("publisher") or e.get("organization") or e.get("institution")] if x) if pub: bits.append(pub) if e.get("journal"): j = f"{e['journal']}" if e.get("volume"): j += f", vol. {e['volume']}" if e.get("number"): j += f", no {e['number']}" if e.get("pages"): j += f", p. {e['pages']}" bits.append(j) if e.get("howpublished"): bits.append(e["howpublished"]) if e.get("note"): bits.append(e["note"]) tail = ". ".join(bits) h = f'{authors_html} ({year}). {title}.' if tail: h += f" {tail}." if e.get("url"): u = html.escape(e["url"], quote=True) h += f' {html.escape(e["url"])}' if e.get("urldate"): h += f' (consulté le {e["urldate"]})' return h if __name__ == "__main__": import sys d = parse_bib(open(sys.argv[1], encoding="utf-8").read()) for k, e in d.items(): print(k, "→", e["short"], e["year"]) print(" ", format_entry(e)[:160])