Skip to content
Merged
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
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ build-watch: ## Build the project in watch mode
@pnpm turbo watch build

dev: ## Start containers
@pnpm turbo run dev
@pnpm turbo watch dev --filter="@istex/viewer-demo"

test: ## Run Unit tests
@pnpm turbo run test
Expand Down
11 changes: 3 additions & 8 deletions packages/react-tei/src/DocumentBody.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import type { DocumentJsonValue } from "./parser/document.js";
import { DocumentTag } from "./tags/DocumentTag.js";

export const DocumentBody = ({
jsonDocument,
}: {
jsonDocument: {
TEI: Record<string, unknown>;
};
}) => {
return <DocumentTag name="TEI" data={jsonDocument.TEI} />;
export const DocumentBody = ({ tei }: { tei: DocumentJsonValue }) => {
return <DocumentTag data={tei} />;
};
5 changes: 3 additions & 2 deletions packages/react-tei/src/DocumentContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createContext } from "react";
import type { DocumentJsonValue } from "./parser/document.js";

export type DocumentContextType = {
jsonDocument: Record<string, unknown> | null;
jsonDocument: DocumentJsonValue;
};

export const DocumentContext = createContext<DocumentContextType | undefined>(
Expand All @@ -13,7 +14,7 @@ export function DocumentContextProvider({
jsonDocument,
}: {
children: React.ReactNode;
jsonDocument: Record<string, unknown> | null;
jsonDocument: DocumentJsonValue;
}) {
return (
<DocumentContext.Provider
Expand Down
6 changes: 4 additions & 2 deletions packages/react-tei/src/DocumentDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { DocumentJsonValue } from "./parser/document.js";

type DocumentDrawerProps = {
teiHeader: Record<string, unknown>;
teiHeader: DocumentJsonValue;
};

export const DocumentDrawer = ({ teiHeader }: DocumentDrawerProps) => {
export const DocumentDrawer = (_props: DocumentDrawerProps) => {
return null;
};
26 changes: 14 additions & 12 deletions packages/react-tei/src/Viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import { Box } from "@mui/material";
import { XMLParser } from "fast-xml-parser";
import { useMemo } from "react";

import { DocumentBody } from "./DocumentBody.js";
import { DocumentContextProvider } from "./DocumentContextProvider.js";
import { DocumentDrawer } from "./DocumentDrawer.js";
import { I18nProvider } from "./i18n/I18nProvider.js";

const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "@",
});
import type { DocumentJsonValue } from "./parser/document.js";
import { useDocumentParser } from "./parser/useDocumentParser.js";

export const Viewer = ({ document }: { document: string }) => {
const jsonDocument = useMemo(() => parser.parse(document), [document]);
const jsonDocument = useDocumentParser(document);

const tei: DocumentJsonValue = Array.isArray(jsonDocument)
? (jsonDocument.find(({ tag }) => tag === "TEI") ?? [])
: [];

const header: DocumentJsonValue = Array.isArray(tei)
? (tei.find(({ tag }) => tag === "teiHeader") ?? [])
: [];

return (
<I18nProvider>
<DocumentContextProvider jsonDocument={jsonDocument}>
<Box component="main" sx={{ flexGrow: 1, display: "flex" }}>
<Box sx={{ maxWidth: "1200px", margin: "auto" }} component="section">
<DocumentBody jsonDocument={jsonDocument} />
<DocumentBody tei={tei} />
</Box>
<DocumentDrawer
teiHeader={jsonDocument.TEI.teiHeader as Record<string, unknown>}
/>
<DocumentDrawer teiHeader={header} />
</Box>
</DocumentContextProvider>
</I18nProvider>
Expand Down
7 changes: 7 additions & 0 deletions packages/react-tei/src/parser/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type DocumentJson = {
tag: string;
attributes?: Record<string, string>;
value?: DocumentJsonValue;
};

export type DocumentJsonValue = DocumentJson | DocumentJson[] | string;
41 changes: 41 additions & 0 deletions packages/react-tei/src/parser/useDocumentParser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { XMLParser } from "fast-xml-parser";
import { useMemo } from "react";
import type { DocumentJson } from "./document.js";

const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "@",
preserveOrder: true,
});

function transform(obj: unknown): DocumentJson | DocumentJson[] | string {
if (Array.isArray(obj)) {
return obj.map(transform) as DocumentJson[];
}

if (obj && typeof obj === "object") {
return Object.fromEntries(
Object.entries(obj).flatMap(
([key, value]: [string, unknown]): [string, unknown][] => {
if (key === ":@") {
return [["attributes", value]];
}

return [
["tag", key],
["value", transform(value)],
];
},
),
) as DocumentJson;
}

return obj as string;
}

export function useDocumentParser(document: string) {
return useMemo(() => {
const jsonDocument = parser.parse(document);
return transform(jsonDocument);
}, [document]);
}
28 changes: 16 additions & 12 deletions packages/react-tei/src/tags/Div.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import type { DocumentJson } from "../parser/document.js";
import { DocumentTag } from "./DocumentTag.js";

export const Div = ({ data }: { data: Record<string, unknown>[] }) => {
return (
<div>
{Object.entries(data).map(([key, value]) => (
<DocumentTag
key={key}
name={key}
data={value as Record<string, unknown>}
/>
))}
</div>
);
export const Div = ({ data: { value, attributes }, depth = 1 }: DivProps) => {
if (!Array.isArray(value)) {
console.warn("Div tag with non-array value:", value);
return null;
}

return value.map((value, index) => (
<DocumentTag
key={index}
data={value}
depth={attributes?.["@type"] === "ElsevierSections" ? depth : depth + 1}
/>
));
};

type DivProps = { data: DocumentJson; depth?: number };
80 changes: 28 additions & 52 deletions packages/react-tei/src/tags/DocumentTag.tsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,52 @@
import type { DocumentJson, DocumentJsonValue } from "../parser/document.js";
import { tagCatalog } from "./tagCatalog.js";

export const DocumentTag = ({
name,
data,
debug,
depth = 1,
}: {
name: string;
data: string | Record<string, unknown> | Record<string, unknown>[] | string[];
data?: DocumentJsonValue;
debug?: boolean;
depth?: number;
}) => {
if (!data) {
return null;
}

if (Array.isArray(data)) {
return (
<>
{data.map((item, index) => (
<DocumentTag
key={index}
name={name}
data={item as Record<string, unknown>}
/>
))}
</>
);
return data.map((item, index) => (
<DocumentTag key={index} data={item} depth={depth} />
));
}

if (typeof data === "string") {
return <p>{data}</p>;
return data;
}

const TagComponent = tagCatalog[name];
if (TagComponent) {
return <TagComponent data={data} />;
const { tag, value } = data as DocumentJson;
if (tag === "#text") {
return value as string;
}

if (["TEI", "text", "body"].includes(name)) {
return (
<>
{Object.entries(data).map(([key, value]) => (
<DocumentTag
key={key}
name={key}
data={value as Record<string, unknown>}
/>
))}
</>
);
const TagComponent = tagCatalog[tag];
if (TagComponent) {
return <TagComponent data={data} depth={depth} />;
}

console.warn(`Unsupported tag encountered:`, {
name,
data,
});
if (["TEI", "text", "body"].includes(tag)) {
if (!Array.isArray(value)) {
return <DocumentTag data={value} depth={depth} />;
}

return value.map((value, index) => (
<DocumentTag key={index} data={value} depth={depth} />
));
}

if (!debug) {
return null;
}

if (typeof data === "object" && data !== null) {
return (
<div>
{Object.entries(data as Record<string, unknown>).map(([key, value]) => (
<DocumentTag
key={key}
name={key}
data={value as Record<string, unknown>}
/>
))}
</div>
);
}
return (
<div>
<strong>{name}:</strong> {JSON.stringify(data)}
</div>
);
return <div>{JSON.stringify(data)}</div>;
};
19 changes: 12 additions & 7 deletions packages/react-tei/src/tags/Head.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type { JSX } from "react";
import type { DocumentJson } from "../parser/document.js";
import { DocumentTag } from "./DocumentTag.js";

export const Head = ({ data }: { data: string | Record<string, unknown> }) => {
if (typeof data === "string") {
return <h2>{data}</h2>;
}
export const Head = ({ data: { value }, depth = 1 }: HeadProps) => {
const headerLevel = Math.max(2, Math.min(6, depth));
const Tag = `h${headerLevel}` as keyof JSX.IntrinsicElements;

return Object.entries(data).map(([key, value]) => (
<DocumentTag key={key} name={key} data={value as Record<string, unknown>} />
));
return (
<Tag>
<DocumentTag data={value ?? []} />
</Tag>
);
};

type HeadProps = { data: DocumentJson; depth?: number };
18 changes: 9 additions & 9 deletions packages/react-tei/src/tags/P.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export const P = ({
data,
}: {
data: {
"@_xml:id": string;
"#text": string;
};
}) => {
return <p id={data["@_xml:id"]}>{data["#text"]}</p>;
import type { DocumentJson } from "../parser/document.js";
import { DocumentTag } from "./DocumentTag.js";

export const P = ({ data }: { data: DocumentJson }) => {
return (
<p>
<DocumentTag data={data.value ?? []} />
</p>
);
};
46 changes: 33 additions & 13 deletions packages/react-tei/src/tags/TeiHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
import { Box, Typography } from "@mui/material";
import type { DocumentJson } from "../parser/document.js";

type TeiHeaderProps = {
data: {
fileDesc: {
titleStmt: {
title: {
"#text": string;
};
};
};
};
data: DocumentJson;
};

export const TeiHeader = ({ data }: TeiHeaderProps) => {
export const TeiHeader = ({ data: { value } }: TeiHeaderProps) => {
if (!Array.isArray(value)) {
console.warn("teiHeader with non-array value:", value);
return null;
}

const fileDesc = value.find(({ tag }) => tag === "fileDesc");
if (!fileDesc || !Array.isArray(fileDesc.value)) {
console.warn("teiHeader missing fileDesc or invalid value:", fileDesc);
return null;
}

const titleStmt = fileDesc.value.find(({ tag }) => tag === "titleStmt");
if (!titleStmt || !Array.isArray(titleStmt.value)) {
console.warn("teiHeader missing titleStmt or invalid value:", titleStmt);
return null;
}

const title = titleStmt.value.find(({ tag }) => tag === "title");
if (!title || !Array.isArray(title.value)) {
console.warn("teiHeader missing title or invalid value:", titleStmt);
return null;
}

const titleText = title.value.find(({ tag }) => tag === "#text");
if (typeof titleText?.value !== "string") {
console.warn("teiHeader missing titleText or invalid value:", titleText);
return null;
}

return (
<Box sx={{ margin: 8 }}>
<Typography variant="h1">
{data.fileDesc.titleStmt.title["#text"]}
</Typography>
<Typography variant="h1">{titleText.value}</Typography>
</Box>
);
};
Loading