Hierarchical TOC + routing
This commit is contained in:
63
frontend/package-lock.json
generated
63
frontend/package-lock.json
generated
@@ -11,7 +11,8 @@
|
|||||||
"@tanstack/react-query": "^5.74.4",
|
"@tanstack/react-query": "^5.74.4",
|
||||||
"@tanstack/react-query-devtools": "^5.74.6",
|
"@tanstack/react-query-devtools": "^5.74.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0",
|
||||||
|
"react-router-dom": "^7.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.22.0",
|
||||||
@@ -1934,6 +1935,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -2905,6 +2915,45 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.1.tgz",
|
||||||
|
"integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1",
|
||||||
|
"set-cookie-parser": "^2.6.0",
|
||||||
|
"turbo-stream": "2.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "7.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.1.tgz",
|
||||||
|
"integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.5.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve-from": {
|
"node_modules/resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
@@ -3006,6 +3055,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-cookie-parser": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@@ -3136,6 +3191,12 @@
|
|||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/turbo-stream": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
"@tanstack/react-query": "^5.74.4",
|
"@tanstack/react-query": "^5.74.4",
|
||||||
"@tanstack/react-query-devtools": "^5.74.6",
|
"@tanstack/react-query-devtools": "^5.74.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0",
|
||||||
|
"react-router-dom": "^7.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.22.0",
|
||||||
|
|||||||
@@ -2,16 +2,20 @@ import { useState } from "react";
|
|||||||
import Panel from "./components/Panel";
|
import Panel from "./components/Panel";
|
||||||
|
|
||||||
import { useQueries } from "@tanstack/react-query";
|
import { useQueries } from "@tanstack/react-query";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import ArticleSelector from "./components/ArticleSelector";
|
import ArticleSelector from "./components/ArticleSelector";
|
||||||
import CelexSelector from "./components/CelexSelector";
|
|
||||||
import TOC from "./components/TOC";
|
import TOC from "./components/TOC";
|
||||||
import { getArticleIds, getToc } from "./lib/api";
|
import { getArticleIds, getToc } from "./lib/api";
|
||||||
import { Language } from "./lib/types";
|
import { Language } from "./lib/types";
|
||||||
|
|
||||||
function App() {
|
type Props = {
|
||||||
const [celexId, setCelexId] = useState("32024R1689");
|
celexId: string;
|
||||||
const [selectedArticle, setSelectedArticle] = useState<number>(1);
|
articleId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function App({ celexId, articleId }: Props) {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [numPanels, setNumPanels] = useState(1);
|
const [numPanels, setNumPanels] = useState(1);
|
||||||
const [selectedParagraphId, setSelectedParagraphId] = useState<string | null>(
|
const [selectedParagraphId, setSelectedParagraphId] = useState<string | null>(
|
||||||
null
|
null
|
||||||
@@ -35,10 +39,10 @@ function App() {
|
|||||||
const error = results.find((result) => result.isError);
|
const error = results.find((result) => result.isError);
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return <div>Loading...</div>;
|
return <div className="panel">Loading...</div>;
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Error: {error.error?.message}</div>;
|
return <div className="panel">Error: {error.error?.message}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const examples = [
|
const examples = [
|
||||||
@@ -56,9 +60,7 @@ function App() {
|
|||||||
id="examples"
|
id="examples"
|
||||||
value={celexId}
|
value={celexId}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSelectedArticle(1);
|
navigate(`/${e.target.value}`);
|
||||||
setSelectedParagraphId(null);
|
|
||||||
setCelexId(e.currentTarget.value);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{examples.map((example) => (
|
{examples.map((example) => (
|
||||||
@@ -68,11 +70,11 @@ function App() {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<CelexSelector defaultId={celexId} onSelected={setCelexId} />
|
{/* <CelexSelector defaultId={celexId} onSelected={setCelexId} /> */}
|
||||||
<ArticleSelector
|
<ArticleSelector
|
||||||
articleIds={results[0].data!}
|
articleIds={results[0].data!}
|
||||||
selectedId={selectedArticle}
|
selectedId={articleId}
|
||||||
onSelected={setSelectedArticle}
|
onSelected={(id) => navigate(`/${celexId}/articles/${id}`)}
|
||||||
/>
|
/>
|
||||||
<button onClick={() => setNumPanels((prev) => prev + 1)}>
|
<button onClick={() => setNumPanels((prev) => prev + 1)}>
|
||||||
Add Panel
|
Add Panel
|
||||||
@@ -81,8 +83,8 @@ function App() {
|
|||||||
<div className="panel-container">
|
<div className="panel-container">
|
||||||
<TOC
|
<TOC
|
||||||
toc={results[1].data!}
|
toc={results[1].data!}
|
||||||
selectedArticleId={selectedArticle}
|
selectedArticleId={articleId}
|
||||||
onArticleSelected={setSelectedArticle}
|
onArticleSelected={(id) => navigate(`/${celexId}/articles/${id}`)}
|
||||||
/>
|
/>
|
||||||
{Array.from({ length: numPanels }, (_, index) => (
|
{Array.from({ length: numPanels }, (_, index) => (
|
||||||
<Panel
|
<Panel
|
||||||
@@ -91,7 +93,7 @@ function App() {
|
|||||||
language={
|
language={
|
||||||
Object.values(Language)[index % Object.values(Language).length]
|
Object.values(Language)[index % Object.values(Language).length]
|
||||||
}
|
}
|
||||||
articleId={selectedArticle}
|
articleId={articleId}
|
||||||
selectedParagraphId={selectedParagraphId || undefined}
|
selectedParagraphId={selectedParagraphId || undefined}
|
||||||
onParagraphSelected={setSelectedParagraphId}
|
onParagraphSelected={setSelectedParagraphId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
13
frontend/src/MainView.tsx
Normal file
13
frontend/src/MainView.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
function MainView() {
|
||||||
|
const { celexId, articleId } = useParams();
|
||||||
|
if (!celexId) {
|
||||||
|
return <div>Error: No CELEX ID provided</div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<App celexId={celexId} articleId={articleId ? parseInt(articleId) : 1} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default MainView;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Language } from "../lib/types";
|
import { Language } from "../lib/types";
|
||||||
|
|
||||||
import "./LanguageSwitcher.css";
|
import "./LanguageSwitcher.css";
|
||||||
|
|
||||||
function LanguageSwitcher({
|
function LanguageSwitcher({
|
||||||
|
|||||||
0
frontend/src/components/MainApp.tsx
Normal file
0
frontend/src/components/MainApp.tsx
Normal file
@@ -8,22 +8,23 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
transition: width 0.3s ease-in-out;
|
|
||||||
|
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
|
|
||||||
.division-list {
|
.toc-division {
|
||||||
list-style: none;
|
margin-block: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin-block: 1rem;
|
padding: 1rem;
|
||||||
}
|
list-style-type: disc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article {
|
.article {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Division } from "../lib/types";
|
||||||
import "./TOC.css";
|
import "./TOC.css";
|
||||||
|
|
||||||
type Article = {
|
|
||||||
type: "article";
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
};
|
|
||||||
type Division = {
|
|
||||||
type: "division";
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
articles: Article[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type TOC = Division[];
|
type TOC = Division[];
|
||||||
|
|
||||||
type TOCProps = {
|
type TOCProps = {
|
||||||
@@ -23,6 +11,49 @@ type TOCProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function TOC({ toc, selectedArticleId, onArticleSelected }: TOCProps) {
|
function TOC({ toc, selectedArticleId, onArticleSelected }: TOCProps) {
|
||||||
|
function containsArticle(division: Division, articleId: number): boolean {
|
||||||
|
return division.content.some((c) => {
|
||||||
|
if (c.type === "division") {
|
||||||
|
return containsArticle(c, articleId);
|
||||||
|
}
|
||||||
|
return c.type === "article" && c.id === articleId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function renderDivision(division: Division) {
|
||||||
|
return (
|
||||||
|
<details
|
||||||
|
key={division.title}
|
||||||
|
className={`toc-division level-${division.level}`}
|
||||||
|
open={
|
||||||
|
!!selectedArticleId && containsArticle(division, selectedArticleId)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<summary>
|
||||||
|
{division.title}: {division.subtitle}
|
||||||
|
</summary>
|
||||||
|
<ul>
|
||||||
|
{division.content.map((c) => {
|
||||||
|
if (c.type === "division") {
|
||||||
|
return renderDivision(c);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={c.id}
|
||||||
|
className={[
|
||||||
|
"article",
|
||||||
|
selectedArticleId === c.id ? "selected" : "",
|
||||||
|
].join(" ")}
|
||||||
|
onClick={() => onArticleSelected(c.id)}
|
||||||
|
>
|
||||||
|
{c.title}: {c.subtitle}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
);
|
||||||
|
}
|
||||||
const [isVisible, setIsVisible] = useState(true);
|
const [isVisible, setIsVisible] = useState(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -33,29 +64,7 @@ function TOC({ toc, selectedArticleId, onArticleSelected }: TOCProps) {
|
|||||||
>
|
>
|
||||||
{isVisible ? "<" : ">"}
|
{isVisible ? "<" : ">"}
|
||||||
</button>
|
</button>
|
||||||
<ul className="division-list">
|
{toc.map((division) => renderDivision(division))}
|
||||||
{toc.map((division, index) => (
|
|
||||||
<li key={index}>
|
|
||||||
<strong>{division.title}</strong>
|
|
||||||
<br />
|
|
||||||
{division.subtitle}
|
|
||||||
<ul>
|
|
||||||
{division.articles.map((article) => (
|
|
||||||
<li
|
|
||||||
key={article.id}
|
|
||||||
className={[
|
|
||||||
"article",
|
|
||||||
selectedArticleId === article.id ? "selected" : "",
|
|
||||||
].join(" ")}
|
|
||||||
onClick={() => onArticleSelected(article.id)}
|
|
||||||
>
|
|
||||||
{article.title}: <em>{article.subtitle}</em>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,19 @@ enum Language {
|
|||||||
ITA = "ita",
|
ITA = "ita",
|
||||||
ESP = "esp",
|
ESP = "esp",
|
||||||
}
|
}
|
||||||
|
type Article = {
|
||||||
|
type: "article";
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
};
|
||||||
|
type Division = {
|
||||||
|
type: "division";
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
level: number;
|
||||||
|
content: Article[] | Division[];
|
||||||
|
};
|
||||||
|
|
||||||
export { Language };
|
export { Language };
|
||||||
|
export type { Article, Division };
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import App from "./App.tsx";
|
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
|
import MainView from "./MainView";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
@@ -11,7 +13,14 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ReactQueryDevtools />
|
<ReactQueryDevtools />
|
||||||
<App />
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route index element={<div>Select a CELEX ID</div>} />
|
||||||
|
<Route path=":celexId/articles?/:articleId?">
|
||||||
|
<Route index element={<MainView />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type CacheKey = tuple[str, Language]
|
|||||||
CACHE: dict[CacheKey, str] = {}
|
CACHE: dict[CacheKey, str] = {}
|
||||||
|
|
||||||
|
|
||||||
def _get_fmx4_data(celex_id: str, language: Language) -> str:
|
def _get_fmx4_data(celex_id: str, language: Language) -> ET.Element:
|
||||||
"""
|
"""
|
||||||
Fetch the FMX4 data from the server.
|
Fetch the FMX4 data from the server.
|
||||||
"""
|
"""
|
||||||
@@ -43,9 +43,10 @@ def _get_fmx4_data(celex_id: str, language: Language) -> str:
|
|||||||
)
|
)
|
||||||
fmx4_data = client.publication_text(cellar_id, ContentType.ZIP_FMX4)
|
fmx4_data = client.publication_text(cellar_id, ContentType.ZIP_FMX4)
|
||||||
|
|
||||||
CACHE[(celex_id, language)] = fmx4_data
|
xml = ET.fromstring(fmx4_data.encode("utf-8"))
|
||||||
|
CACHE[(celex_id, language)] = xml
|
||||||
|
|
||||||
return fmx4_data
|
return xml
|
||||||
|
|
||||||
|
|
||||||
@app.get("/{celex_id}/articles")
|
@app.get("/{celex_id}/articles")
|
||||||
@@ -53,8 +54,7 @@ def article_ids(celex_id: str, language: Language = Language.ENG):
|
|||||||
"""
|
"""
|
||||||
Fetch the article IDs from the server.
|
Fetch the article IDs from the server.
|
||||||
"""
|
"""
|
||||||
fmx4_data = _get_fmx4_data(celex_id, language)
|
xml = _get_fmx4_data(celex_id, language)
|
||||||
xml = ET.fromstring(fmx4_data.encode("utf-8"))
|
|
||||||
|
|
||||||
article_xpath = "//ARTICLE/@IDENTIFIER"
|
article_xpath = "//ARTICLE/@IDENTIFIER"
|
||||||
article_ids = xml.xpath(article_xpath)
|
article_ids = xml.xpath(article_xpath)
|
||||||
@@ -65,18 +65,16 @@ def article_ids(celex_id: str, language: Language = Language.ENG):
|
|||||||
|
|
||||||
@app.get("/{celex_id}/toc/{language}")
|
@app.get("/{celex_id}/toc/{language}")
|
||||||
def toc(celex_id: str, language: Language = Language.ENG):
|
def toc(celex_id: str, language: Language = Language.ENG):
|
||||||
"""
|
def _handle_division(division: ET.Element, level: int):
|
||||||
Fetch the table of contents from the server.
|
|
||||||
"""
|
|
||||||
fmx4_data = _get_fmx4_data(celex_id, language)
|
|
||||||
xml = ET.fromstring(fmx4_data.encode("utf-8"))
|
|
||||||
|
|
||||||
toc = []
|
|
||||||
|
|
||||||
for division in xml.xpath("//DIVISION"):
|
|
||||||
print(division)
|
print(division)
|
||||||
|
|
||||||
title = ti_el[0] if (ti_el := division.xpath("TITLE/TI//text()")) else ""
|
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 ""
|
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 = []
|
articles = []
|
||||||
for article in division.xpath("ARTICLE") or []:
|
for article in division.xpath("ARTICLE") or []:
|
||||||
art_id = article.get("IDENTIFIER")
|
art_id = article.get("IDENTIFIER")
|
||||||
@@ -94,14 +92,23 @@ def toc(celex_id: str, language: Language = Language.ENG):
|
|||||||
"subtitle": art_subtitle,
|
"subtitle": art_subtitle,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
toc.append(
|
|
||||||
{
|
return {
|
||||||
"title": title,
|
|
||||||
"type": "division",
|
"type": "division",
|
||||||
|
"title": title,
|
||||||
"subtitle": subtitle,
|
"subtitle": subtitle,
|
||||||
"articles": articles,
|
"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
|
return toc
|
||||||
|
|
||||||
@@ -111,8 +118,7 @@ def article(celex_id: str, article_id: int, language: Language = Language.ENG):
|
|||||||
"""
|
"""
|
||||||
Fetch an article from the server.
|
Fetch an article from the server.
|
||||||
"""
|
"""
|
||||||
fmx4_data = _get_fmx4_data(celex_id, language)
|
xml = _get_fmx4_data(celex_id, language)
|
||||||
xml = ET.fromstring(fmx4_data.encode("utf-8"))
|
|
||||||
|
|
||||||
article_xpath = "//ARTICLE"
|
article_xpath = "//ARTICLE"
|
||||||
articles = xml.xpath(article_xpath)
|
articles = xml.xpath(article_xpath)
|
||||||
|
|||||||
Reference in New Issue
Block a user