import lxml.etree as ET from fastapi import APIRouter, FastAPI, Response from fastapi.middleware.cors import CORSMiddleware from formex_viewer.formex4 import FormexArticleConverter from formex_viewer.main import ( CellarClient, CellarIdentifier, ContentType, Language, SystemName, ) origins = [ "http://localhost:5173", ] app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) type CacheKey = tuple[str, Language] CACHE: dict[CacheKey, str] = {} def _get_fmx4_data(celex_id: str, language: Language) -> ET.Element: """ Fetch the FMX4 data from the server. """ if (celex_id, language) in CACHE: return CACHE[(celex_id, language)] client = CellarClient(language) cellar_id = CellarIdentifier( system_name=SystemName.CELEX, system_id=celex_id, ) fmx4_data = client.publication_text(cellar_id, ContentType.ZIP_FMX4) xml = ET.fromstring(fmx4_data.encode("utf-8")) CACHE[(celex_id, language)] = xml return xml api_router = APIRouter() @api_router.get("/{celex_id}/articles") def article_ids(celex_id: str, language: Language = Language.ENG): """ Fetch the article IDs from the server. """ xml = _get_fmx4_data(celex_id, language) article_xpath = "//ARTICLE/@IDENTIFIER" article_ids = xml.xpath(article_xpath) article_ids = [int(article_id.lstrip("0")) for article_id in article_ids] article_ids.sort() return article_ids @api_router.get("/{celex_id}/toc/{language}") def toc(celex_id: str, language: Language = Language.ENG): def _handle_division(division: ET.Element, level: int): title = ti_el[0] if (ti_el := division.xpath("TITLE/TI//text()")) else "" subtitle = sti_el[0] if (sti_el := division.xpath("TITLE/STI//text()")) else "" subdivisions = [] for subdivision in division.xpath("DIVISION") or []: subdivisions.append(_handle_division(subdivision, level + 1)) articles = [] for article in division.xpath("ARTICLE") or []: art_id = article.get("IDENTIFIER") if not art_id: continue art_title = ti_el[0] if (ti_el := article.xpath("TI.ART//text()")) else "" art_subtitle = ( sti_el[0] if (sti_el := article.xpath("STI.ART//text()")) else "" ) articles.append( { "id": int(art_id.lstrip("0")), "type": "article", "title": art_title, "subtitle": art_subtitle, } ) return { "type": "division", "title": title, "subtitle": subtitle, "level": level, "content": subdivisions + articles, } """ Fetch the table of contents from the server. """ xml = _get_fmx4_data(celex_id, language) toc = [] for division in xml.xpath("//ENACTING.TERMS/DIVISION"): toc.append(_handle_division(division, 0)) return toc @api_router.get("/{celex_id}/articles/{article_id}/{language}") def article(celex_id: str, article_id: int, language: Language = Language.ENG): """ Fetch an article from the server. """ xml = _get_fmx4_data(celex_id, language) article_xpath = "//ARTICLE" articles = xml.xpath(article_xpath) for article in articles: num = article.get("IDENTIFIER").lstrip("0") if num == str(article_id): return Response( FormexArticleConverter().convert_article(article), media_type="text/html", ) app.include_router(api_router, prefix="/api")