🐷

Next.js + CETEIcean + React TEI Routerを使ったビューア開発

2025/03/02に公開

概要

Next.js、CETEIcean、React TEI Routerを組み合わせたTEI/XMLビューアの開発についての備忘録です。

背景

CETEIceanは、TEI/XML を HTML5 に変換する JavaScript ライブラリです。

https://github.com/TEIC/CETEIcean

そして、React TEI Routerは、CETEIcean をベースに React コンポーネントで TEI/XML を構造化して表示できるライブラリです。以下のように説明されています。

https://github.com/pfefferniels/react-teirouter

TEI for React using CETEIcean and routes

これらを組み合わせることで、Next.js において TEI/XML をカスタマイズして表示できるビューア を作成しました。

リポジトリ

以下がサンプルリポジトリです。

https://github.com/nakamura196/next-ceteicean-router

実際に動作するデモも用意しています。

https://next-ceteicean-router.vercel.app/

実装

Next.js のページコンポーネント (page.tsx)

CETEIcean を利用して XML を変換し、カスタムコンポーネントで描画します。

src/app/page.tsx
import React from "react";
import Render from "@/components/tei";
export default function App() {
  const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
    <TEI xmlns="http://www.tei-c.org/ns/1.0">
      <text>
        <body>
          <div type="original">
            私の名前は<persName corresp="#id1">田中太郎</persName>です。
          </div>
          <div>
            <p style="color: green;">こんにちは</p>
            <p style="color: green;">こんばんは <seg style="color: blue;">xxx</seg>
            </p>
          </div>
        </body>
      </text>
    </TEI>`;

  return <Render xmlContent={xmlContent} />;
}

TEIレンダリングコンポーネント

  • CETEIcean を使って XML を HTML5 に変換。
  • TEIRender + TEIRoute を使い、TEI 要素ごとにカスタムコンポーネントを適用。

import { TEIRender, TEIRoute } from "react-teirouter";を使用した上で、要素毎にコンポーネントを用意しています。

src/components/tei/index.tsx
"use client";

import React from "react";
import CETEI from "CETEIcean";
import { TEIRender, TEIRoute } from "react-teirouter";
import { Div } from "@/components/tei/element/div";
import { PersName } from "@/components/tei/element/persname";
import { P } from "@/components/tei/element/p";
import { Seg } from "@/components/tei/element/seg";
export default function Render({ xmlContent }: { xmlContent: string }) {
  const [teiDoc, setTeiDoc] = React.useState<Document | null>(null);

  React.useEffect(() => {
    const fetchData = async () => {
      // CETEIceanをインスタンス化
      const CETEIcean = new CETEI();

      const data = await CETEIcean.makeHTML5(xmlContent);

      setTeiDoc(data);
    };

    fetchData();
  }, [xmlContent]);

  if (teiDoc) {
    const teiElement = teiDoc.querySelector("tei-tei");
    if (teiElement) {
      return (
        <div
          className=""
          style={{ writingMode: "vertical-rl", height: "calc(100vh - 128px)" }}
        >
          <TEIRender data={teiElement}>
            <TEIRoute el="tei-div" component={Div} />
            <TEIRoute el="tei-p" component={P} />
            <TEIRoute el="tei-persname" component={PersName} />
            <TEIRoute el="tei-seg" component={Seg} />
          </TEIRender>
        </div>
      );
    }
  }

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "calc(100vh - 128px)",
      }}
    >
      <h1
        style={{
          fontFamily: "Arial, sans-serif",
          color: "#333",
          fontSize: "24px",
        }}
      >
        Loading TEI...
      </h1>
    </div>
  );
}

カスタムコンポーネント (p.tsx の例)

pタグの例です。<TEINodes teiNodes={props.teiNode.childNodes} {...props} />を用いることで、再帰的にタグの処理を行うことができました。

src/components/tei/element/p.tsx
import { TEINodes } from "react-teirouter";
import { getStyle } from "@/utils";

export function P(props: { teiNode: ChildNode }) {
  const teiNode = props.teiNode;
  const style = getStyle(teiNode);
  return (
    <p style={style} className="m-1">
      <TEINodes teiNodes={props.teiNode.childNodes} {...props} />
    </p>
  );
}

補足

この方法を利用したものとして、CETEIcean を React コンポーネントとして活用し、カスタム要素を TEI にマッピングする「Astro TEI - React」がありました。

https://github.com/raffazizzi/astro-tei/tree/main/packages/react#readme

An Astro component for publishing TEI as Custom Elements powered by CETEIcean.

This utility provides the React version of CETEIcean's default behaviors and provides a way of mapping your own React components to TEI elements via React TEI Router.

ただし、サイズが大きいTEI/XMLに対しては、 React TEI Router でレンダリングすると 再帰的に回す処理が多くなり、パフォーマンスが低下する可能性があります。

その場合、React componentとしては使用せず、CETEIceanのみを使用する方がよいかもしれません。

var CETEIcean = new CETEI()
CETEIcean.getHTML5("URL_TO_YOUR_TEI.xml", function(data) {
  document.getElementById("TEI").appendChild(data)
})

https://next-ceteicean-router.vercel.app/simple/

https://github.com/nakamura196/next-ceteicean-router/blob/main/src/app/simple/page.tsx

まとめ

ReactでのTEI/XMLの利用にあたり、参考になりましたら幸いです。

Discussion