# Open Processing Model (opm) > Convert TEI, Docbook or JATS XML to HTML, PDF, EPUB, Word or Markdown, or publish it as a static website. A Python tool for digital editions, driven by TEI ODD. `opm` is a Python command-line tool and library that converts TEI XML (and DocBook or JATS) into HTML, PDF (via Typst or CSS for print), EPUB, Word, Markdown or JSON, and splits documents into static websites. Rendering rules are written in a TEI ODD file following the TEI Processing Model; `opm` compiles the ODD into a Python module on demand. ODDs are compatible with TEI Publisher. Key facts for agents: - Install with `pip install open-processing-model` (Python 3.12+). The command is `opm`. - `opm init` scaffolds a project: `opm.toml` (settings), `odd/custom.odd` (rendering rules, inheriting `teipublisher.odd`), `templates/`, `data/`. - `opm transform FILE -t TYPE -o OUT` converts one file; TYPE is one of web, print, epub, markdown, docx, typst, json. `-o out.pdf` with `-t typst` compiles a PDF. - `opm chunk FILE_OR_DIR --force` splits documents into pages for a static site; `opm serve` previews it. - There is no separate compile step: change the ODD and rerun. - Change rendering in the ODD first (elementSpec/model/@behaviour/outputRendition), then in templates; Python XPath extensions are a last resort. --- # Open Processing Model **Open Processing Model** (`opm`) converts TEI and other XML such as DocBook or JATS into HTML, PDF (via [Typst](https://typst.app/)), EPUB, Word, Markdown or JSON. It can also be used to transform an entire edition of XML into a static website or prepare the data for an external static site generator. Instead of writing XSLT or XQuery, describe in XML how each element should be presented, and `opm` compiles it into a reusable Python module. While originally conceived for documents following the TEI XML standard, the [TEI Processing Model](https://tei-c.org/release/doc/tei-p5-doc/en/html/TD.html#TDPM) is in fact not limited to TEI, but works equally well for any other kind of text-centric XML. `opm` is pure Python with minimal dependencies and implements the full _TEI Processing Model_. Most ODDs will be compatible and can be exchanged between [_TEI Publisher_](https://tei-publisher.org) and `opm`, allowing users to combine the benefits of a dynamic, database-backed website with the speed of static rendering. To get started, have a look at the [short tutorial](https://opm.e-editiones.org/tutorials/tei-to-html/) included in this documentation. To learn more about ODD as the underlying standard, it is best to read the [TEI Publisher documentation](https://tei-publisher.org/doc/documentation.xml?id=odd#odd). It also includes a more extensive tutorial in the [Gentle Introduction](https://tei-publisher.org/doc/quickstart.xml?id=pm-tutorial#pm-tutorial) document. ## Uses Typical usage scenarios for `opm` include: * quick transformation of XML documents on the command line * split large documents into chunks, so they can be read page by page * prepare HTML data to be used by other systems such as static site generators * provide [pre-rendered content for TEI Publisher](https://opm.e-editiones.org/guide/tei-publisher/) to speed up load times * generate a static HTML reference site from an ODD (`opm odd document`), including processing models and the TEI Guidelines when you have `p5.xml` locally ## Where to go next - New here? Start with [Installation](https://opm.e-editiones.org/getting-started/installation/) and the [Quickstart](https://opm.e-editiones.org/getting-started/quickstart/), or follow one of the tutorials: [Convert XML to HTML](https://opm.e-editiones.org/tutorials/tei-to-html/), [Publish a website](https://opm.e-editiones.org/tutorials/tei-website/), [Create a PDF](https://opm.e-editiones.org/tutorials/tei-to-pdf/), [EPUB and Word](https://opm.e-editiones.org/tutorials/tei-to-epub-word/). - Working with an AI assistant? Point it to [llms.txt](https://opm.e-editiones.org/llms.txt) or [llms-full.txt](https://opm.e-editiones.org/llms-full.txt), plain-text versions of this documentation written for AI tools. - Learn the concepts: [ODD files](https://opm.e-editiones.org/guide/odd-files/), [Output formats](https://opm.e-editiones.org/guide/output-formats/), [XPath extensions](https://opm.e-editiones.org/guide/xpath-extensions/), [Templates & CSS](https://opm.e-editiones.org/guide/templates-and-css/), [Chunking](https://opm.e-editiones.org/guide/chunking/), [ODD documentation](https://opm.e-editiones.org/guide/odd-documentation/), [Integration with TEI Publisher](https://opm.e-editiones.org/guide/tei-publisher/), and the [`opm.toml` configuration](https://opm.e-editiones.org/guide/configuration/). - Reference: the [CLI](https://opm.e-editiones.org/cli/) and the [Python API](https://opm.e-editiones.org/api/). ## Supporters Development of `opm` is supported by: ### An initiative of Source: https://opm.e-editiones.org/ --- # Installation The minimum supported Python version is **3.12**. If you need help installing Python or are unsure of your Python version, go to [Troubleshooting](#troubleshooting) below. ## Users Install the published package: ```bash pip install open-processing-model # or: uv add open-processing-model ``` Then you can start by either scaffolding a project or running a one-off transformation. The latter uses the packaged stock ODD (`teipublisher.odd`), templates, CSS, and Word styles. Try it out with: ```bash opm transform my.xml --preview opm transform my.xml --preview --odd myodd.odd # provide your own ODD ``` To scaffold a project run: ```bash opm init ``` and in the terminal select what to start from: an empty project, or a copy of one of the example projects that come with the package. See the [Quickstart](https://opm.e-editiones.org/getting-started/quickstart/) for more details. ### Troubleshooting #### Installing Python The quickest way to check the Python version on a system is via the command line or terminal. * open your command line interface (type `cmd` in the Windows search bar or open terminal on macOS/Linux) * type `python --version` and press Enter * on some systems (Mac) you may need to use `python3 --version` if you get a `command not found` error * if you see a version number higher than **3.12** you can directly install the package. If you need to install or upgrade Python, go to [python.org](https://www.python.org/)’s Download page, select the pertinent installer for your operating system, download it and run it. During installation, make sure to check the box that says *Add Python to PATH* so you can run Python from the command line. To verify the installation, follow the steps above to check the installed version. #### Installing the package ```bash pip install open-processing-model # or pip3 on some systems ``` If this returns an error similar to `command not found`, it usually means `pip` isn’t added to your system's PATH variable. Use `python3 -m pip install open-processing-model` (macOS/Linux) or `py -m pip install open-processing-model` (in Windows). ## Contributors This repository uses [`uv`](https://docs.astral.sh/uv/) as its package manager. `uv` does not force-upgrade your system Python, but it can provision a compatible interpreter: ```bash uv python install 3.12 ``` ### 1. Install `uv` ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` Restart your shell (or source your profile) so `uv` is on `PATH`. ### 2. Install dependencies From the project root: ```bash uv sync ``` This creates `.venv` and installs the runtime dependencies. Optional dependency groups: ```bash uv sync --group dev # pytest and other dev tools uv sync --group docs # zensical + mkdocstrings (this site) ``` ### 3. Verify ```bash uv run opm --help uv run --group dev pytest -q ``` ## Running tests ```bash uv run --group dev pytest # all tests uv run --group dev pytest -v -x # verbose, stop on first failure uv run --group dev pytest tests/test_odd_compiler.py -q # one file ``` ## Building this documentation ```bash uv run python scripts/gen_cli_docs.py # refresh CLI reference from the Typer app uv run --group docs zensical serve # live-reload at http://127.0.0.1:8000 uv run --group docs zensical build # static site into ./site ``` Source: https://opm.e-editiones.org/getting-started/installation/ --- # Quickstart This walkthrough scaffolds a local project, transforms a sample document, and builds a chunked static site. After [installation](https://opm.e-editiones.org/getting-started/installation/), you only need the `opm` command-line interface. > **Note:** If you cloned the repository, you'll find all example projects directly in the `/examples/` folder, so you can skip the scaffolding step. See the section [below](#running-from-the-repository). ## 1. Create a project You might want to start by creating the directory where you want to store the project files, then open the terminal, go to that directory (`cd`) and run: ```bash opm init ``` or pass the path as an argument: `opm init PATH` Without additional arguments you'll be prompted to select the type of project: either an empty project for one of the supported vocabularies, or a copy of one of the example projects. You can bypass interactive mode and directly create the type of project you want. E.g.: ```bash opm init --vocabulary docbook # empty project for a specific vocabulary opm init --example jats # copy of the JATS journal-article project opm init --example jats --templates # include all available templates into `/templates/` opm init --list-examples # list all available example projects ``` An **empty project** writes a configuration file (`opm.toml`), templating and CSS files (`/templates/`), an ODD (`/odd/`), sample data (`/data/sample.xml`), agent guidance (`AGENTS.md` and `CLAUDE.md`; existing copies are left untouched) and `.gitignore`. The ODD will depend on the selected vocabulary, TEI being the default one, and it . An **example** is a complete project — ODD, templates, config and real source documents — copied out of the package, with its own `README.md` describing what it demonstrates,`.gitignore` and agent guidance. | `--example` | Project | | --- | --- | | `jats` | Journal article: masthead, TOC rail, margin notes in print | | `docbook` | Software handbook: section chunking, global TOC, breadcrumbs, PDF print and EPUB | | `serafin` | Correspondence (TEI): transcription and translation, with person/place registers | | `monograph` | Monograph (TEI): critical edition of a book chapter, with the original pagination in the margin, two note series and breadcrumbs | | `shakespeare` | Shakespeare play (TEI): chunked by page rather than division, with IIIF facsimiles | The client distinguishes two modes of processing: `transform` and `chunk`. * `transform` is meant to transform a single input file into a single output. It's mainly used for generating a quick HTML preview or for media formats not targetting the web, like PDF, epub or markdown. * `chunk` can process entire collections of document. It also paginates large documents into smaller units and provides different formats for the generated output. In the simplest case, `chunk` can be used to generate a simple static website for your data. ## 2. Transform a document ODDs are compiled on demand into the user cache. To preview the HTML transformation in the browser: ```bash opm transform data/sample.xml --preview ``` To write to a file instead of previewing: ```bash opm transform data/sample.xml -o output.html ``` Other output formats can be specified with the `--type` flag: ```bash opm transform data/sample.xml -t markdown --preview opm transform data/sample.xml -t typst -o output.typ opm transform data/sample.xml -t docx -o output.docx ``` Parameters (exposed to XPath in the ODD as `$parameters`) can be passed with the `-p` flag: ```bash opm transform data/sample.xml -p mode=toc -p display=browse -o output.html ``` See this [ODD files](https://opm.e-editiones.org/guide/odd-files/#parameters)’s section to learn more about parameters. ## 3. Chunk a document for a static site To divide a document in chunks, that is, in semantically structured divisions that can be used for creating a static edition that you can navigate by chapter, poem, etc., run: ```bash opm chunk data/sample.xml --force ``` To create JSON data files for a static site generator (e.g. Eleventy, Hugo): ```bash opm chunk data/sample.xml --format json -o output/chunks ``` Add `--preview` to chunk and serve in one step (this will create a `chunks` folder and then serve it) ```bash opm chunk data/sample.xml --force --preview ``` You can also serve an existing output directory with: ```bash opm serve ``` Most options have defaults in `opm.toml` — see [Configuration](https://opm.e-editiones.org/guide/configuration/). For driving these steps from Python instead of the CLI, see the [Project API](https://opm.e-editiones.org/api/project/). ## Running from the repository Developers working in the git clone can still use the demo tree without `opm init`: ```bash uv run opm transform examples/tei-test.xml --preview uv run opm transform examples/tei-test.xml -t web --preview uv run opm chunk examples/tei-test.xml -o chunks/ --force --preview ``` Example projects live under `examples/`. `uv run` still finds the repo package from those directories, and `opm` loads the local `opm.toml`: ```bash cd examples/serafin uv run opm chunk data/letters/serafin01.xml --force --preview cd ../docbook uv run opm chunk data/doc/quickstart.xml --force --preview ``` Source: https://opm.e-editiones.org/getting-started/quickstart/ --- # Convert XML to HTML This tutorial shows how to turn an XML document into a web page you can open in any browser. It takes about ten minutes. You do not need to know XSLT or any programming language: the rules for how each element should look are written in a document of their own, called an ODD. The examples use TEI. `opm` works the same way for DocBook and JATS, and ships ready-made rules for all three; for any other XML format, you write the rules yourself. Where the steps differ, this tutorial says so. By the end you will have: 1. a small project folder with your XML file in it 2. an HTML version of that file 3. your first change to the way the output looks ## Before you start You need `opm` installed on your computer. If you have not done this yet, follow the [installation instructions](https://opm.e-editiones.org/getting-started/installation/). To check that it worked, open a terminal and type: ```bash opm --help ``` You should see a list of commands. ## 1. Create a project A project is simply a folder that holds your XML files, the ODD with your rendering rules, and a configuration file. Create a new one with: ```bash opm init my-edition --vocabulary tei cd my-edition ``` `--vocabulary` names the XML format of your documents. For DocBook or JATS, write `--vocabulary docbook` or `--vocabulary jats` instead. `opm` creates the folder `my-edition` and fills it with everything you need to get started. The most important parts are: | Path | What it is for | | --- | --- | | `data/` | your XML documents. There is already a small `sample.xml` in it. | | `odd/custom.odd` | your rendering rules. It starts out empty and inherits sensible defaults for all common elements of the format you chose. | | `templates/` | the page layout and stylesheet wrapped around your text | | `opm.toml` | the project settings | ## 2. Convert the sample document Let us first try the sample document that came with the project: ```bash opm transform data/sample.xml --preview ``` `--preview` opens the result in your web browser. The first run takes a few seconds longer, because `opm` first reads the ODD and prepares it. Later runs reuse that work and are much faster. To save the result as a file instead, name the output with `-o`: ```bash opm transform data/sample.xml -o sample.html ``` ## 3. Convert your own document Now copy one of your own XML files into the `data` folder and run the same command on it: ```bash opm transform data/letter.xml --preview ``` Most documents will display reasonably well right away, since the default rules cover many of the common elements: divisions, headings, paragraphs, notes, lists, tables, figures and, for TEI, also verse, drama, and many more. ## 4. Change how something looks Suppose you want person names to appear in italics and in dark red. Open `odd/custom.odd` in a text editor. Inside the `schemaSpec` element, add an `elementSpec` for `persName`: ```xml ``` !!! tip "Graphical Editor" If you don't feel comfortable with editing the XML, there's also a graphical editor as a plugin for Visual Studio Code and derived editors like Cursor or Antigravity, called [ODDity](https://open-vsx.org/extension/e-editiones/oddity). Let us look at what this says: * `elementSpec ident="persName"` selects the element we want to change. In a DocBook or JATS project, you would pick one of their elements instead, for example `emphasis` or `italic`. `mode="change"` means we only replace its rendering rules and keep everything else from the default ODD. * `model behaviour="inline"` says that a person name should run along with the surrounding text, rather than start a new block like a paragraph would. * `outputRendition` holds the styling, written in CSS. Save the file and run the transform command again. The names are now shown in italic red. `opm` notices that the ODD has changed and prepares it anew. This is the general pattern for all changes: find the element, decide on a *behaviour* (paragraph, heading, inline, note, link, …), and add styling where needed. The [TEI Publisher documentation](https://tei-publisher.org/doc/documentation.xml?id=odd#odd) explains the available behaviours and includes a short tutorial on writing processing models. !!! tip "One set of rules for all formats" The same ODD is also used when you create a [PDF](https://opm.e-editiones.org/tutorials/tei-to-pdf/), an [EPUB or Word file](https://opm.e-editiones.org/tutorials/tei-to-epub-word/), or a [website](https://opm.e-editiones.org/tutorials/tei-website/). Your change to `persName` applies to all of them. ## Why not XSLT? XSLT stylesheets, such as the [TEI Stylesheets](https://github.com/TEIC/Stylesheets), are a well-established way of transforming XML. The approach taken by `opm` differs in where the rules live. Instead of a program, you write a document that describes the desired output, following the *TEI Processing Model*. It is part of the TEI Guidelines, but works for any XML format. This has some practical advantages: * the rules are easier to read and change for people who know their XML format but are not programmers * one set of rules produces HTML, PDF, EPUB, Word and Markdown * ODD expands to _One Document Does it all_ for a reason: it can host schema definitions, encoding guidelines, documentation and the processing document — all in the same TEI file * the same ODD also works in [TEI Publisher](https://tei-publisher.org), so you can move between a static setup and a full web application without starting over ## Next steps * Split a long document into pages and [publish it as a website](https://opm.e-editiones.org/tutorials/tei-website/). * Change the page layout and stylesheet: see [Templates & CSS](https://opm.e-editiones.org/guide/templates-and-css/). * Learn more about writing rules: see [ODD files](https://opm.e-editiones.org/guide/odd-files/). Source: https://opm.e-editiones.org/tutorials/tei-to-html/ --- # Publish XML documents as a website A long document, a whole collection of letters, or a set of articles is too much for a single web page. This tutorial shows how to split your XML files into smaller pages, with navigation between them, and turn the result into a website. The examples use TEI; the steps are the same for DocBook, JATS or any other XML format. The website `opm` creates is *static*: it is just a folder of HTML files. You do not need a database or special server software to publish it. Any web space will do, including free services such as GitHub Pages or GitLab Pages. ## Before you start You need `opm` installed on your computer (see the [installation instructions](https://opm.e-editiones.org/getting-started/installation/)) and a project folder. If you do not have a project yet, create one: ```bash opm init my-edition --vocabulary tei cd my-edition ``` If you have followed [Convert XML to HTML](https://opm.e-editiones.org/tutorials/tei-to-html/), you can use the project from that tutorial. ## 1. Split a document into pages Splitting a document into pages is called *chunking*. Try it on the sample document: ```bash opm chunk data/sample.xml --force --preview ``` `opm` creates a folder called `chunks`, writes one HTML page for each division of the text into it, and opens the first page in your browser. Use the arrows to move between pages. `--force` allows `opm` to overwrite the `chunks` folder when you run the command again. Without `--preview`, the pages are written but not opened. ## 2. Decide where to split In a new project, `opm` starts a new page for each `div` in the body of the text, up to two levels deep. You can change this in `opm.toml`, in the section headed `[chunking]`: ```toml [chunking] selector = "opm.navigation.tei_div_chunks" depth = 2 ``` `depth` controls how deep `opm` looks: with `depth = 1`, only top-level divisions get their own page; with `depth = 3`, divisions three levels down do as well. Some editions are better read page by page, following the original source. To start a new web page at every page break (`pb`) in the text, use: ```toml [chunking] selector = "opm.navigation.tei_pb_chunks" view = "page" ``` Save `opm.toml` and run the `opm chunk` command again to see the difference. ## 3. Add your whole collection Put all your XML files into the `data` folder, then pass the folder rather than a single file: ```bash opm chunk data --force --preview ``` Every document gets its own set of pages, and `opm` adds a start page listing all documents in the collection. ## 4. Look at the result Open the `chunks` folder. You will find: | Path | What it is | | --- | --- | | `index.html` | the start page | | `letter.xml/001.html`, `002.html`, … | the pages of each document | | `letter.xml/manifest.json` | information about the pages, used for navigation | | `css/` | the stylesheets | To view the site again later without rebuilding it, run: ```bash opm serve ``` !!! note "Why not just open the files?" Some features, such as navigation, need the pages to be loaded from a web server rather than directly from disk. `opm serve` runs a small one on your computer for testing. ## 5. Publish it To publish the edition, copy the contents of the `chunks` folder to your web space. That is all. If you rebuild the site after editing your XML files, copy the folder again. Many projects automate this step, so the website updates itself whenever the XML files change. GitHub and GitLab both offer this for free. ## Next steps * Change the page layout, or add a site title and a logo: see [Templates & CSS](https://opm.e-editiones.org/guide/templates-and-css/) and [Configuration](https://opm.e-editiones.org/guide/configuration/). * Add a table of contents, breadcrumbs, or other navigation: see [Chunking](https://opm.e-editiones.org/guide/chunking/). * Build a more elaborate site with a static site generator such as [Eleventy](https://www.11ty.dev/) or [Hugo](https://gohugo.io/): run `opm chunk data --format json` to produce data files they can read. See [Chunking](https://opm.e-editiones.org/guide/chunking/). * Add full-text search: see [Search indexing](https://opm.e-editiones.org/guide/search-indexing/). * For a complete example, create a copy of the correspondence edition with `opm init --example serafin` or the Shakespeare edition with `opm init --example shakespeare`. Source: https://opm.e-editiones.org/tutorials/tei-website/ --- # Create a PDF from XML This tutorial shows how to create a PDF from an XML document, for example to print an edition or an article, to share a reading copy, or to submit a text to a publisher. The examples use TEI; the steps are the same for DocBook, JATS or any other XML format. `opm` offers two ways of doing this. Both use the same rules (the ODD) as the HTML and EPUB versions, so you do not have to maintain a separate set of stylesheets for print. | Way | Good for | Additional software | | --- | --- | --- | | Typst | high-quality typesetting, quick to set up | [Typst](https://typst.app/open-source/), free | | HTML and CSS for print | reusing your web stylesheets, complex page layouts | a CSS print processor such as [PrinceXML](https://www.princexml.com/) | If you are unsure, start with Typst. ## Before you start You need `opm` installed on your computer (see the [installation instructions](https://opm.e-editiones.org/getting-started/installation/)) and a project folder. If you do not have a project yet, create one: ```bash opm init my-edition --vocabulary tei cd my-edition ``` If you have followed [Convert XML to HTML](https://opm.e-editiones.org/tutorials/tei-to-html/), you can use the project from that tutorial. ## Using Typst [Typst](https://typst.app) is a modern typesetting system, similar in purpose to LaTeX but easier to use and much faster. `opm` converts your XML document into Typst, and Typst produces the PDF. ### 1. Install Typst Follow the [installation instructions on the Typst website](https://github.com/typst/typst#installation). On a Mac with [Homebrew](https://brew.sh), for example, it is: ```bash brew install typst ``` Check that it works by typing `typst --version` in a terminal. ### 2. Create the PDF ```bash opm transform data/sample.xml -t typst -o sample.pdf ``` `-t typst` tells `opm` to produce Typst. Because the output file ends in `.pdf`, `opm` then asks Typst to turn it into a PDF for you. To see the result straight away, use `--preview` instead of `-o`: ```bash opm transform data/sample.xml -t typst --preview ``` ### 3. Change the page layout The paper size, fonts, margins and title page are set in a template, `templates/book.typ.j2`. Open it in a text editor to make changes. The template uses a ready-made book layout for Typst called [ilm](https://typst.app/universe/package/ilm). For example, to print on A5 paper, look for the lines starting with `#show: ilm.with(` and add a `paper-size` setting: ```typst #show: ilm.with( title: [...], ... listing-index: (enabled: true), paper-size: "a5", ) ``` Save the template and create the PDF again. The [Typst documentation](https://typst.app/docs/) explains what else you can set. If you want to see what `opm` passes on to Typst, write the Typst file instead of the PDF: ```bash opm transform data/sample.xml -t typst -o sample.typ ``` ### 4. Change how individual elements look Changes made in the ODD apply to the PDF as well. If you followed [Convert XML to HTML](https://opm.e-editiones.org/tutorials/tei-to-html/) and made person names italic and red, they are italic and red in the PDF too. Not every CSS property has a counterpart in Typst. `opm` carries over bold, italic, strike-through, text colour and font size. For anything else, see [CSS class mapping](https://opm.e-editiones.org/guide/output-formats/#css-class-mapping), which explains how to add your own Typst styling for an element. ## Using HTML and CSS for print The second way produces an HTML file meant for printing, and leaves the step to PDF to a separate program. Page size, margins, running headers and footnotes are all set in CSS. There are different processors available for generating a PDF based on CSS for print. The example below uses [Prince XML](https://www.princexml.com/). ```bash opm transform data/sample.xml -t print -o print.html prince print.html -o print.pdf ``` To check the print version in your browser first, use: ```bash opm transform data/sample.xml -t print --preview ``` This approach is useful if you already have CSS for your web edition and want the PDF to look similar. The DocBook example shows a complete setup, including running headers and a table of contents. Create a copy with `opm init --example docbook`. ## Next steps * Learn more about each format: see [Output formats](https://opm.e-editiones.org/guide/output-formats/). * Create an [EPUB or Word file](https://opm.e-editiones.org/tutorials/tei-to-epub-word/) from the same document. Source: https://opm.e-editiones.org/tutorials/tei-to-pdf/ --- # Create an EPUB or Word file from XML This tutorial shows how to turn an XML document into an e-book (EPUB) for e-readers, or into a Word document you can share with colleagues who do not work with XML. Both are made from the same rules (the ODD) as your web and PDF versions, so a change you make once shows up everywhere. The examples use TEI; the steps are the same for DocBook, JATS or any other XML format. ## Before you start You need `opm` installed on your computer (see the [installation instructions](https://opm.e-editiones.org/getting-started/installation/)) and a project folder. If you do not have a project yet, create one: ```bash opm init my-edition --vocabulary tei cd my-edition ``` If you have followed [Convert XML to HTML](https://opm.e-editiones.org/tutorials/tei-to-html/), you can use the project from that tutorial. ## Creating an EPUB ### 1. Create the e-book ```bash opm transform data/sample.xml -t epub -o sample.epub ``` `-t epub` selects the EPUB format, and `-o` names the file to write. Open `sample.epub` in an e-book reader, for example Apple Books, Calibre or your e-reader's desktop application. To open it right away in the default application on your computer, use `--preview` instead of `-o`. ### 2. Decide on the chapters An EPUB is divided into chapters. `opm` divides it the same way it divides a [website](https://opm.e-editiones.org/tutorials/tei-website/) into pages, following the `[chunking]` section of `opm.toml`. In a new project, that means a new chapter for each `div`, up to two levels deep. Sometimes an e-book should be divided differently from a website. In that case, add a section `[transform.epub]` to `opm.toml` with its own setting: ```toml [transform.epub] depth = 1 ``` With `depth = 1`, only top-level divisions become chapters. A higher number also turns nested divisions into chapters of their own. If your website is split at page breaks, the e-book is still divided into chapters by `div`, since e-readers lay out the pages themselves. ### 3. Change the look E-readers have their own ideas about fonts and page size, so EPUB styling is best kept simple. `opm` uses a plain stylesheet for e-books, combined with the styling from your ODD. To add your own rules, put them into a CSS file, for example `templates/epub.css`, and name it in `opm.toml`: ```toml [transform.epub] css = "templates/epub.css" ``` ## Creating a Word document ```bash opm transform data/sample.xml -t docx -o sample.docx ``` Open `sample.docx` in Word, LibreOffice or any other word processor. Headings, paragraphs, lists, tables, notes and emphasis come out as regular Word formatting, so the document can be edited further like any other. ### Using your own Word styles If your institution or publisher has a Word template, `opm` can use its styles. Save the template as a normal `.docx` file in the `templates` folder and name it in `opm.toml`: ```toml [transform.docx] template = "templates/my-styles.docx" ``` `opm` then uses the paragraph and character styles from that file, such as *Heading 1* or *Caption*, so the result looks like the rest of your documents. ## Creating Markdown Markdown is a plain-text format, useful for example for notes, wikis or further processing with other tools: ```bash opm transform data/sample.xml -t markdown -o sample.md ``` ## Next steps * Learn more about each format: see [Output formats](https://opm.e-editiones.org/guide/output-formats/). * Create a [PDF](https://opm.e-editiones.org/tutorials/tei-to-pdf/) from the same document. * For a complete e-book example, create a copy of the Shakespeare edition with `opm init --example shakespeare`, or of the DocBook handbook with `opm init --example docbook`. Source: https://opm.e-editiones.org/tutorials/tei-to-epub-word/ --- # ODD files An **ODD** (One Document Does it all) file is a TEI XML document which includes *processing model* instructions. `opm` compiles an ODD on demand into a Python transform module. The compiled module will automatically be cached for further use. To learn more about ODD, it is best to read the [TEI Publisher documentation](https://tei-publisher.org/doc/documentation.xml?id=odd#odd). It also includes a small tutorial in the [Gentle Introduction](https://tei-publisher.org/doc/quickstart.xml?id=pm-tutorial#pm-tutorial) document. > **Note**: if you are using an editor based on Visual Studio Code (Cursor, Antigravity, Windsurf, Kiro …), check the marketplace for an extension called [ODDity](https://open-vsx.org/extension/e-editiones/oddity) – it provides a graphical environment for editing ODDs. The library packages three stock ODDs: - `teipublisher.odd` (+ `tp.css`) — main [TEI Publisher](https://tei-publisher.org) model, and the fallback when `--odd` is omitted - `docbook.odd` (+ `docbook.css`) — DocBook v5 model (`opm init --vocabulary docbook`) - `jats.odd` (+ `jats.css`) — JATS journal article model (`opm init --vocabulary jats`) `opm init` writes a short `odd/custom.odd`. For TEI projects, it inherits the packaged `teipublisher.odd`, or otherwise copies `docbook.odd` / `jats.odd`. To also include a copy of the base `teipublisher.odd`, pass `--copy-base-odd`. For example customisations, see the projects under `examples/`, each of which ships its own ODD (for instance `examples/shakespeare/odd/shakespeare.odd`). OPM supports ODD chaining, a technique of ordering individual ODD documents in such a way that it is possible for one document to re-use (and potentially modify) declarations and definitions from another ODD. A base ODD can be defined in the `schemaSpec/@source` attribute or with the flag `source=""`. For TEI edition projects, we recommend always having the `teipublisher.odd` as the base of your custom ODD. It includes default renditions for the most common TEI elements. ## Compiling on demand Pass an ODD to `opm transform` / `opm chunk` (or set `odd = "…"` in the configuration). The first run compiles it into the user cache (and prints the path on stderr); later runs reuse the cache until the ODD (or its inherited sources / CSS) changes. ```bash opm transform examples/tei-test.xml --preview opm transform examples/tei-test.xml -t docx -o out.docx ``` ## Reusing ODDs between TEI Publisher and `opm` The shipped ODDs are shared between TEI Publisher and opm to make it easy to switch between implementations. However, TEI Publisher runs within a database and has the full power of XQuery at its disposal. Consequently, not every expression that works in TEI Publisher will work in `opm`, which is limited to XPath 3.1. To cope with this, two mechanisms are implemented: 1. `` which target `opm` can have an `@output` attribute value prefixed with `opm-` (e.g. `output="opm-web"`). TEI Publisher will simply skip over those. If you put the prefixed model before the unprefixed one, `opm` will use it while TEI Publisher ignores it and selects the next matching model instead. DocBook listings do this: `tp:highlight` (Pygments) on `opm-web` / `opm-print`, then `pb-code-highlight` for tei-publisher-lib. 2. `opm`'s compiler tests each XPath expression and recognises the ones it cannot execute. If such an expression occurs inside a predicate, it will evaluate to `false` and the processor consequently skips it. Parameter expressions will return the context node instead of failing. The client prints a count of ignored expressions to the console. To get a full report for an ODD, see [`opm odd coverage`](https://opm.e-editiones.org/guide/coverage/#expressions-opm-cannot-run). ## Supported extensions `opm` implements the same extensions to the TEI processing model as TEI Publisher. ### `$parameters` External parameters are passed to the ODD in a `$parameters` map. Values come from `[transform.parameters]` in the `opm.toml` configuration file, the command line passed as `-p key=value`, and per-fragment `parameters` in [chunking](https://opm.e-editiones.org/guide/chunking/#fragments). ### `$parameters?root` The special parameter, `$parameters?root`, always contains a reference to the root node being processed. This is mainly relevant in `chunk` mode: it splits the document into fragments, and each fragment becomes a document of its own. The link to the source document therefore gets lost. Use `$parameters?root` to get it back. `root($parameters?root)` always points to the document node. `teipublisher.odd` uses that, for example, to reach the `teiHeader` from a `div` or page: ```xpath root($parameters?root)//teiHeader/fileDesc/titleStmt ``` ### `$get()` and `tp:source-node()` While `$parameters?root` returns the root node, the special function pointer `$get($n)` in TEI Publisher gives you the source node corresponding to its argument. `opm` implements the same for backwards compatibility, e.g. to be used in: ```xml ``` Inside `opm`, `$get()` is an alias for the function `tp:source-node($n)`, which you can use alternatively. ### `` A model in TEI Publisher may provide a custom markup template in a [``](https://tei-publisher.org/doc/documentation.xml?id=pb-template#pb-template). Without it, producing more complex output for a single element would not be possible. Markup templates also help with plain-text markup languages like LaTeX or typst. `opm` processes `` in the same way as TEI Publisher does. ## XPath errors at run time An expression that is valid XPath can still fail on a particular document, for example, `xs:date(date)` on a date written as "circa 1850", say. As before, such a predicate counts as false and a param as empty. `opm transform`, `opm chunk`, `opm index` and `opm odd coverage` list these failures at the end of the run, each expression once, with the error, how often it failed and where it first did: ``` opm: warning: 1 XPath expression failed at run time and was treated as false or empty: xs:date(date) lt xs:date('1900-01-01') FORG0001: Invalid datetime string 'circa' for Date 3×, first at data/letter.xml:41 ``` Pass `--strict` to `opm transform`, `opm chunk` or `opm index` to make that an error (exit status 1), for example in CI. Expressions the compiler already skipped do not count. Errors that mean the project configuration is missing something are shown as a single note naming the setting instead, and never fail a run: an undeclared namespace prefix (`[transform.namespaces]`), an unset variable (`[transform.variables]`), a `tp:` function that no configured module provides (`[transform] xpath_extensions`), or an unknown collection (`[[transform.collections]]`). Source: https://opm.e-editiones.org/guide/odd-files/ --- # Output formats `opm` can transform XML to a number of output formats. The format is chosen (at **compile** time) via the `--type` / `-t` parameter. This selects the ODD `@output` channel and the specific [`ProcessingModelFunctions`](https://opm.e-editiones.org/api/output-functions/) implementation used to generate that output. | Mode | Output | Implementation | | --- | --- | --- | | `web` (default) | HTML5 | `HtmlOutputFunctions` | | `print` | HTML for paged-media CSS | `PrintOutputFunctions` (extends HTML) | | `epub` | EPUB 3 (`.epub` ZIP) | `EpubOutputFunctions` + packager | | `markdown` | Markdown | `MarkdownOutputFunctions` | | `docx` | Word `.docx` (binary) | `DocxOutputFunctions` | | `typst` | Typst markup | `TypstOutputFunctions` | | `json` | The processing model's decisions as data | `JsonOutputFunctions` | `print`, `epub` and `json` extend `web`, which means they will also accept ODD models tagged `@output="web"`. `markdown` and `typst` both extend `plain`: a model tagged `@output="plain"` serves the two text formats at once. Models without `@output` apply always. Models may use an optional `opm-` prefix on `@output` (e.g. `opm-web`) for rules that apply only when compiling with this Python implementation. tei-publisher-lib ignores those models. Among models that match the compile mode, the usual ODD rule applies: the first whose conditions apply wins. ```bash opm transform examples/tei-test.xml -t web --preview opm transform examples/tei-test.xml -t print --preview opm transform examples/tei-test.xml -t epub -o book.epub opm transform examples/tei-test.xml -t markdown --preview opm transform examples/tei-test.xml -t docx -o out.docx opm transform examples/tei-test.xml -t typst -o out.typ opm transform examples/tei-test.xml -t json -o out.json ``` ## Choosing an ODD at transform time All commands above will use the default configured in the `opm.toml`. You can change the ODD with parameter `--odd`/`-d`. ```bash # Explicit ODD (compiled on demand) opm transform examples/tei-test.xml --preview # Looked up from config (see Configuration) opm transform examples/tei-test.xml -t web --preview opm transform examples/tei-test.xml -t print --preview opm transform examples/tei-test.xml -t typst -o out.pdf opm transform examples/tei-test.xml -t docx -o out.docx ``` ## HTML (`web`) Full-document output is wrapped in a Jinja2 template and can include ODD-generated CSS, a user stylesheet, and optional [tei-publisher web components](https://unpkg.com/@teipublisher/pb-components@latest/dist/api.html). See [Templates & CSS](https://opm.e-editiones.org/guide/templates-and-css/). ```bash opm transform data/sample.xml \ --preview --template templates/tufte.html.j2 ``` ## Print (paged media) `print` emits HTML like `web`, but targets a print processor rather than a web browser. Therefore you would use CSS Paged Media for the styling, which allows you to set page size, margins, footnotes etc. On the command line, [PrinceXML](https://www.princexml.com/) works well for converting the resulting HTML to PDF. ```bash opm transform examples/tei-test.xml -t print -o print.html prince print.html -o print.pdf ``` `print` output mode wraps the generated content into an HTML template, which can be customized. For example, the docbook example does that: ```bash cd examples/docbook opm transform data/doc/quickstart.xml -t print --preview ``` ## EPUB `epub` compiles with EPUB-oriented HTML behaviours (synthetic fragment ids, `epub:type` pagebreaks, footnote asides) and packages the result as an EPUB 3 ZIP (mimetype, OPF, `nav.xhtml`, NCX, chapters, CSS, images). Chapters are selected with the same `[chunking]` rules used by `opm chunk` (default: TEI `tei_div_chunks` / DocBook `dbk_section_chunks` at depth 1). `[transform.epub]` may override that selection with its own `xpath`, `selector` or `depth`: the EPUB contents might be very different from the browser-based reading view. For example, `examples/serafin` chunks only the source text and fills the translation into a second panel in the HTML preview, but not in the EPUB. A page-milestone selector (`tei_pb_chunks`) is always replaced by divisions, since a EPUB readers repaginate anyway. The book's language comes from `xml:lang` on the root element or the header (`teiHeader` / DocBook `info`), and falls back to English. Reading systems use it for hyphenation, so set it in the source. ```bash opm transform examples/tei-test.xml -t epub -o book.epub ``` Optional config: ```toml [transform.epub] # odd = "odd/my-epub.odd" css = "templates/epub.css" # appended last to the packaged stylesheet skip_title = false # omit the generated title page when true # xpath = "//text[@type]" # chapter selection, when it differs from [chunking] # selector = "opm.navigation.tei_div_chunks" # depth = 2 [chunking] selector = "opm.navigation.tei_div_chunks" depth = 1 ``` ### Styling `OEBPS/stylesheet.css` is a cascade of three parts, each able to override the one before: 1. **The packaged baseline** (`opm/resources/styles/epub.css`) — typography for headings, lists, code, tables, figures, footnotes and the title page. It also defines fallbacks for the `--jinks-*` / `--pb-*` custom properties that web-oriented ODD stylesheets reference, so rules carried over from the reading view degrade instead of dropping out. 2. **The ODD stylesheet** — base rules plus `outputRendition` styles. 3. **`[transform.epub] css`** — the project stylesheet, for restyling anything the reading view brought along that does not suit an e-reader (sticky chrome, `color-mix()`, viewport units). `examples/docbook/templates/epub.css` shows how to recreate the handbook’s web look; `examples/shakespeare/templates/epub.css` is the opposite case, dropping the reading view’s two-column Folio layout and its page furniture for a plain reading text. Rules a project wants in *every* view belong in the ODD stylesheet. `@rend` tokens in particular reach the output as bare class names that the processing model never defines, so they are the ODD stylesheet’s to declare — see `examples/shakespeare/odd/shakespeare.css`. Reading systems render EPUB 3 XHTML, which has no room for custom elements or their attributes. Web components are therefore degraded rather than emitted: `pb-code-highlight` becomes `
`, `pb-link` becomes an `` pointing
at its cross-reference, and anything else — including custom elements
introduced by a `pb:template` — becomes a transparent `
` or ``. Fragment links are rewritten to name the chapter file that holds the target, so cross-references keep working after the document is split. Images referenced by `img/@src` are resolved next to the source document and in a sibling `images/` directory. Images that cannot be found are left out of the manifest, since an EPUB OPF entry without a file makes the package invalid. ## Markdown ```bash opm transform examples/tei-test.xml -t markdown --preview ``` `--preview` renders the Markdown in the terminal with [Rich](https://rich.readthedocs.io/). ## DOCX DOCX is binary, so the `-o` flag is required to write the file (it cannot be previewed). A custom Word `.docx` can be supplied as a **style template** via the CLI `--template` flag or in the configuration file under `[transform.docx] template`. Its paragraph and character styles are reused in the output. If none is given, the packaged `default.docx` is used. Missing built-in styles (`Hyperlink`, `footnote text`, `footnote reference`) are injected automatically. ```bash opm transform examples/tei-test.xml -t docx -o report.docx ``` ## Typst Typst output uses a `.typ.j2` Jinja2 template configured under `[transform.typst]`. Project templates include `templates/book.typ.j2` (TEI) and `templates/docbook.typ.j2` (DocBook UI classes). The packaged fallback is `default_document.typ.j2`. ```bash opm transform examples/tei-test.xml -t typst -o out.typ ``` The resulting `.typ` file you can then process with ```bash typst compile out.typ --open ``` ### CSS class mapping In `web` output mode, `@cssClass` on a model and `@rend` attributes on TEI elements are output as CSS class names. In `typst` mode, class names are mapped to Typst functions. However, Typst would fail with a compilation error if the function does not exist. We therefore need a way to register functions while gracefully ignoring unknown class names. Instead of calling the function directly, opm looks it up in a dictionary, which you can extend with additional functions in your typst `.typ.j2` template: ```typst #let opm-css-fns = opm-css-fns + ( center: (body) => align(center, body), note: (body) => block( width: 100%, inset: (left: 0.8em, y: 0.35em), stroke: (left: 4pt + rgb("#d07f00")), body, ), doc_title: (body) => block(above: 2.5em, width: 100%, align(center, text(style: "italic", body))), ) ``` Using an attribute `@cssClass="doc-title"` on the model should then result in a centered, italic block. Mind these points: - Place the extension **before** `{% include "opm_css.typ.j2" %}`. That fragment reads `opm-css-fns` when it is included, so entries added after it never take effect and the content comes out unstyled. - Place it **after** the include of `opm_css_fns_default.typ.j2` (if your template has one), which creates the dictionary and its defaults such as `pb` and `marginnote`. Writing `opm-css-fns + (…)` earlier fails because the name does not exist yet. A key that matches a default replaces it. - Write keys with underscores. A class such as `doc-subtitle` or `tei:note` is looked up as `doc_subtitle` or `tei_note`. - A function receives only the content. Anything else you need, such as a font or a colour, belongs inside it. Renditions defined in the ODD itself (`tei-*` and `simple_*` classes) do not use this dictionary: the ODD compiler emits a Typst function for each of them, and those are called directly. ### PDF With the [`typst`](https://typst.app/open-source/) command on your `PATH`, `opm` can run `typst compile` for you. Name a `.pdf` output file, or preview: ```bash opm transform examples/tei-test.xml -t typst -o out.pdf opm transform examples/tei-test.xml -t typst --preview # Typst opens the PDF ``` Image paths in the output are taken from the XML as they are and resolve against the document's directory; Typst refuses a path that leaves it (`../`). From Python, `transform_file()` returns the Typst source; pass it to `opm.typst_compile.compile_pdf()` for the PDF. Refer to the [Typst documentation](https://typst.app/docs/) for more information about running the command. ## JSON The flag `-t json` does not render the document. It records **what the processing model did to it**: which behaviour ran for each element, which model was fired, where the element came from, and what text it contributed. ```bash opm transform examples/tei-test.xml -t json -o out.json ``` !!! note "Not the same as `opm chunk --format json`" `opm chunk --format json` wraps rendered **HTML** in a JSON envelope for static site generators. `-t json` emits structured **data** about the transformation itself. They serve different consumers. Every element that reaches a behaviour produces a record — inline and `pass_through` ones included, and this output is very useful for debugging. ```json { "id": "pi-first-steps", "xpath": "/TEI/text[1]/body[1]/div[2]", "line": 84, "col": 7, "element": "div", "behaviour": "section", "model": "tei-div11", "children": [ { "element": "head", "behaviour": "heading", "level": 2, "children": ["First steps"] }, { "element": "p", "behaviour": "paragraph", "children": ["To open the visual editor…"] } ] } ``` | Field | Meaning | | --- | --- | | `id` | `@xml:id` / `@id`, when the element has one | | `xpath` | Path to the source element, by name — see below | | `line`, `col` | Where the element's start tag begins in the source file | | `element` | Local name | | `behaviour` | The behaviour that ran; `null` when no model matched | | `model` | Key into the top-level `models` table | | `children` | Ordered mix of text runs and child records | | `suppressed` | Present and `true` for `omit` / `index` / `metadata` | `xpath` names its elements (`/TEI/text[1]/body[1]/div[2]`) because opm resolves unprefixed names against the document's default namespace. document: ```bash opm transform doc.xml -x '/TEI/text[1]/body[1]/div[2]' --preview ``` Elements outside that default namespace — e.g. MathML inside TEI — keep the positional form for those steps (`…/formula[1]/*[1]/*[1]`), since an unprefixed step would not match them. `line` and `col` point at the `<` of the start tag, 1-based, so a record can be opened directly: ```bash opm transform doc.xml -t json -o out.json # then, for any record: $EDITOR +84 doc.xml / code -g doc.xml:84:7 ``` There are two things that this output format makes visible that no other output can: **Suppressed content.** `omit`, `index` and `metadata` produce no output, so a renderer cannot distinguish "the ODD dropped this" from "it was never in the source". Here they leave a record marked `"suppressed": true`. **Unmatched elements.** An element with no matching model never reaches a behaviour at all — its text just surfaces in some ancestor. In JSON it appears with `"behaviour": null`, so everything an ODD does not cover is findable: ```bash opm odd coverage doc.xml # counts them, with a file:line for each ``` [`opm odd coverage`](https://opm.e-editiones.org/guide/coverage/) rolls these records up across a corpus: unmatched elements, models that never fired, models that can never fire, and elements whose spec exists but whose predicates were all false. ### Choosing which channel to inspect `-t json` on its own records the **web** channel's decisions. That is the wrong answer when the ODD you are debugging targets another one — typst and docx models in particular are the hardest to eyeball, since there is no browser to open. `--channel` picks the channel whose models participate: ```bash opm transform doc.xml -t json --channel typst -o typst-decisions.json opm transform doc.xml -t json --channel docx -o docx-decisions.json ``` Accepted channels are `web` (default), `print`, `epub`, `markdown`, `docx` and `typst`. Each compiles to its own cached module, because which models participate is fixed at compile time. The channel changes the answer. In the packaged `teipublisher.odd`, the same `teiHeader` element resolves differently: | Channel | Behaviour | Model | | --- | --- | --- | | `web` | `metadata` (suppressed) | `tei-teiHeader8` | | `typst` | `pass_through` | `tei-teiHeader3` (`@output="typst"`) | So the web view stops at the header, while the typst view descends into it and records `title` and `author` under typst-only models. Diffing two channels is a quick way to see what a channel actually changes: ```bash opm transform doc.xml -t json -o web.json opm transform doc.xml -t json --channel typst -o typst.json ``` `--channel` follows the same fallback rules as the channel itself: with `--channel print` or `--channel epub`, `@output="web"` models take part too, as they do for `print` and `epub`. With `--channel typst` or `--channel markdown`, `@output="plain"` models take part instead. ### The `models` table A record names the model that won, but not what that model *was*. The `models` table resolves the key: ```json "models": { "tei-div11": { "element": "div", "behaviour": "section", "predicate": "@type='section'", "desc": "Sections of the document body" } } ``` `source` appears when the model was inherited from an extended ODD rather than written in the one being compiled, and names that file: ```json "tei-hi3": { "element": "hi", "behaviour": "inline", "source": "teipublisher.odd", "predicate": "@rend='italic'" } ``` Its absence means the model is local. Because a local `elementSpec` replaces the inherited one, every model of an element you redeclare is local; the flag tells you whether a decision you dislike can be changed here at all, or has to be overridden by redeclaring the element. `predicate`, `desc`, `output` and `source` appear nowhere in the record tree, so the table is not derivable from the records. Neither are the models that **did not** fire — and those are the more valuable half, because "why did my model not win?" is answered by reading the predicate of the one that beat it: ```bash # every model competing for `hi`, in ODD order — the first match wins python -c " import json d = json.load(open('out.json')) for key, m in d['models'].items(): if m['element'] == 'hi': print(key, m['behaviour'], m.get('predicate', '(no predicate)')) " ``` The table is pruned to elements the document actually contains. A model for an element that never appears can explain nothing about this document, and on a large ODD those are most of the entries — for `examples/tei-test.xml` against the packaged ODD, 239 models prune to 138. Pruning is keyed on the source tree, not on the emitted records, so a subtree the ODD suppresses (`teiHeader`, say) keeps its models: that is precisely what you are reading when you ask why nothing came out of it. Models that survive pruning but never fired are the coverage report: elements the document uses, models the ODD declares for them, and no match. That reading is what [`opm odd coverage`](https://opm.e-editiones.org/guide/coverage/) automates, across a whole corpus and with the local/inherited split applied. For building a search index from this data, see [Search indexing](https://opm.e-editiones.org/guide/search-indexing/). ## Adding a new format Subclass [`ProcessingModelFunctions`](https://opm.e-editiones.org/api/output-functions/) and implement its behaviour methods. Because the generated transform code is format-agnostic, the same compiled module works with any implementation you provide. Modes that extend HTML (like `print`) typically subclass `HtmlOutputFunctions` and override only the behaviours that differ. Source: https://opm.e-editiones.org/guide/output-formats/ --- # Chunking Large documents are awkward to serve as one giant HTML page. `opm chunk` splits a document into smaller pieces, transforms each one, and writes the results plus a manifest — this is ideal for static site generators and web components. Three different types of output are supported (use `--format` to switch between them): - **`html`** (default) — renders each chunk through a template (`chunking.template`, else the `[transform.web]` template), generating a series of HTML files. Use this for quick previews or to create a simple static edition which does not need a complex framework. - **`json`** — one JSON file per chunk, including the rendered content and optional fragments. Ideal for integration into static site generators like [Eleventy](https://www.11ty.dev/), [Hugo](https://gohugo.io/), [Astro](https://astro.build/) and others that consume data files. - **`pb-view`** — an index table plus part files to be consumed by [TEI Publisher's](https://tei-publisher.org) viewer web component. Use this to pre-render content for fast display in an existing TEI Publisher-based website (see [Integration with TEI Publisher](https://opm.e-editiones.org/guide/tei-publisher/)) for uploading those files into an app, and switching the webcomponent to static mode. Example using the Shakespeare sample: ```bash opm init --example shakespeare shakespeare-demo cd shakespeare-demo opm chunk data/F-ado.xml --force --preview # chunk and preview in browser opm chunk data/F-ado.xml -o pages/ --force # override chunking.output_dir # provide pre-rendered data to a TEI Publisher instance # requests will call demo/F-ado.xml, so we need to specify this path prefix opm chunk data --doc-path demo --format pb-view --force ``` Serafin example: ```bash opm init --example serafin serafin-demo cd serafin-demo opm chunk data/letters -o chunks/ --force opm chunk data/letters --format json -o output/chunks opm chunk data/letters --format pb-view --doc-path letters -o public ``` Most settings come from the `[chunking]` section of `opm.toml`; which can be overridden by the CLI commands. See [Configuration](https://opm.e-editiones.org/guide/configuration/) for the full schema. ## Selecting chunks Chunk roots are selected by an XPath expression (default `//text/body/div`), configured as: ```toml [chunking] xpath = "//text/body/div" depth = 2 # maximum heading depth at which to split # file_pattern = "{xml_id}.html" # stable names instead of 001.html ``` For logic that XPath can't express, you can use a `selector` (dotted path) to call Python objects that return the chunk elements. Built-in selectors: | Selector | Use | | --- | --- | | `opm.navigation.tei_div_chunks` | TEI by-division (`depth`) | | `opm.navigation.tei_pb_chunks` | TEI by page-break (`tei:pb` milestones, `view="page"`) | | `opm.navigation.dbk_section_chunks` | DocBook by `section` | | `opm.navigation.jats_sec_chunks` | JATS by `sec`, with `front` and `back` as their own chunks | Configuration sample: ```toml [chunking] selector = "opm.navigation.tei_pb_chunks" view = "page" ``` The bundled `shakespeare` example (`opm init --example shakespeare`) is a page-chunked project (`opm chunk data/F-ado.xml --force`): a First Folio play split at every ``. The `jats` example is a counterpart in which chunks are done by semantic divisions instead of pages: a real journal article where `front` and `back` become chunks alongside the body sections. ## Naming documents Each document gets a directory of its own below the output directory, named after its file: `data/letters/K_0096.xml` is published as `K_0096.xml/001.html`. To use an identifier from the document instead, give an XPath. It is evaluated on each document's root and its string value becomes the directory name: ```toml [chunking] document_name = "'B' || substring-after(/TEI/@xml:id, 'K_')" # B0096/ file_pattern = "index.html" # B0096/index.html ``` The name is also what templates get as `document`, and what `documents` lists, so links between documents can be built from identifiers found in the TEI (see the `escher` example). Two documents with the same name, or a document whose expression gives an empty string, stop the run with an error. `--format pb-view` ignores the setting: pb-view fetches its data by file path. ## Fragments Alongside the main chunk content, you can extract **fragments** — secondary pieces pulled from the document or from each chunk, such as a table of contents, breadcrumbs, or the work title. Each one of them is configured under `[chunking.fragments]` and there are two different scopes: `global` (document root as context) and `per-chunk` (chunk as context). They are listed in the manifest, and you can retrieve them in the templates with `fragments.`. Global fragments also appear when you run `opm transform` on the whole document. The page template gets them under the same name, so one template can show the work title on chunk pages and on the single page alike. With the flag `--format pb-view`, global fragments become `{name}.json` part files (e.g. `toc.json`) plus a sibling `{name}.html` with the same markup, and per-chunk fragments become `{name}-{xml:id}.json`. All JSON parts are registered in `index.json` under the fragment XPath and any `user.*` parameters so a second `pb-view` can load them in static mode. Thus a fragment who is configured as: ```toml [[chunking.fragments]] name = "title" scope = "global" xpath = "(//teiHeader/fileDesc/titleStmt/title)[1]" parameters = { mode = "title" } ``` ... will be listed in the index as: ```json "odd=shakespeare.odd&user.mode=title&view=page&xpath=(//teiHeader/fileDesc/titleStmt/title)[1]": "title.json" ``` More configuration examples: ```toml [[chunking.fragments]] name = "title" scope = "global" xpath = "(/article/info/title, /book/info/title)[1]" parameters = { mode = "title" } [[chunking.fragments]] name = "toc" scope = "global" xpath = "(/article, /book)[1]" parameters = { mode = "toc" } [[chunking.fragments]] name = "breadcrumbs" scope = "per-chunk" xpath = "." parameters = { mode = "breadcrumb" } ``` | Key | Meaning | | --- | --- | | `name` | Template / JSON key (`fragments.title`, `fragments.breadcrumbs`, …) | | `scope` | `global` — evaluate once against the document root; `per-chunk` — once per chunk with the chunk as context | | `xpath` | XPath expression (XPath 3.1) selecting the node(s) or string to emit (default `.`) | | `parameters` | Extra `$parameters` for that transformation (e.g. `mode = "breadcrumb"`). | | `odd` / `mode` | Rare. Use a different ODD for this fragment only (`odd`), compiled for output channel `mode` (`web` by default — not the same as `$parameters?mode` above). Omit to reuse the chunking ODD. | ## Manifest and navigation Chunking writes a manifest JSON describing every chunk: its file, anchors, fragment locations, and `prev`/`next` navigation links. Cross-chunk links follow the `chunking.link_pattern` configuration (placeholders `{file}`, `{stem}`, `{anchor}`, `{doc}`, `{doc_stem}`), so you can match your site's URL scheme. `{doc}` is the per-document subdirectory when chunking a directory of XML files (empty otherwise), and `{doc_stem}` is that name without the `.xml` suffix: ```toml [chunking] link_pattern = "/{doc}/{file}" # /serafin01.xml/001.html link_pattern = "/letters/{doc_stem}/{stem}/#{anchor}" # /letters/serafin01/001/ ``` ## Several runs One `[chunking]` table splits a document one way. When one document should yield pages of different kinds — chapters and reference entries, letters and the persons they mention — declare the table as an array instead, one `[[chunking]]` entry per run: ```toml [[chunking]] name = "text" output_dir = "site" xpath = "//body/div" file_pattern = "{xml_id}.html" [[chunking.fragments]] # belongs to the run above it name = "toc" scope = "global" xpath = "." [[chunking]] name = "register" xpath = "//listPerson/person" file_pattern = "{xml_id}.html" ``` The runs execute in order, over each document, into one output directory: only the first may set `output_dir`. Each run is handed the anchors of the runs before it, so a register entry's `#chapter-3` link resolves to the text run's page. `manifest.json` lists every run's chunks, each tagged with its `run` name. The search index (`opm index`) covers all runs; an EPUB is built from the first. A single `[chunking]` table works exactly as before. ## `$parameters?root` While a chunk is transformed, `$parameters?root` is the **original node** that chunk was copied from (see [ODD files](https://opm.e-editiones.org/guide/odd-files/#parametersroot)). The document node is `root($parameters?root)`. Chunks created by `dbk_section_chunks` and `tei_pb_chunks` sometimes yield a node detached from the original XML tree. Thus, this node has no ancestors, but it keeps the source `xml:id`, so `opm` is able to map it back. ODD models that need the rest of the document should walk from `$parameters?root`, and not from `.`. For example, to access the title of the document from a chunked article or section: ```xpath (($parameters?root)/ancestor::article/info/title, ($parameters?root)/ancestor::section/title, title) ``` ## Manifest and navigation Chunking writes a manifest JSON describing every chunk: its file, anchors, fragment locations, and `prev`/`next` navigation links. Cross-chunk links follow `chunking.link_pattern` (placeholders `{file}`, `{stem}`, `{anchor}`, `{doc}`, `{doc_stem}`), so you can match your site's URL scheme. `{doc}` is the per-document subdirectory every chunk run writes, and `{doc_stem}` is that name without the `.xml` suffix — which is what a framework route usually wants: ```toml [chunking] link_pattern = "/{doc}/{file}" # /serafin01.xml/001.html link_pattern = "/letters/{doc_stem}/{stem}/#{anchor}" # /letters/serafin01/001/ ``` ## Stylesheets and static assets Chunking always writes the stylesheets as files under `/css/` and hands templates the path to the CSS (`odd_css_url`). The file `css/.css` holds the ODD's own `` rules, preceded by the base rules every ODD-rendered document needs (e.g. the `.alternate` / `.altcontent` popover behind the `choice` behaviour , `.tei-cb` for column breaks, etc.; these can still be overridden by the ODD's `outputRendition`). `[transform] css` does not add a second stylesheet — it *replaces* those base rules, so a project can change the default style without losing the ODD's own renditions. ```jinja ``` Anything else a page needs, such as the template's own stylesheet, images, fonts or scripts, is listed under `assets` in `[transform.web]`. Chunking copies these files into `/assets/`, shared by all pages, unless `[chunking]` names its own `assets`. See [Assets](https://opm.e-editiones.org/guide/assets/) for how to list them and refer to them from templates. A template that extends opm's page layout (see [Template inheritance](https://opm.e-editiones.org/guide/templates-and-css/#template-inheritance)) also gets the layout's own stylesheet, written to `/css/opm-layout.css`. Images the document itself references need no entry. When a chunk page contains an `` whose `src` is a relative path, the file is looked up next to the source XML, then in a sibling `images/` directory — the same rule EPUB output follows — and copied to that path beside the chunk pages. Remote URLs, root-relative paths, and paths leading out of the output directory are left alone; images that cannot be found are skipped. ## Collection index Chunking writes one subdirectory per document, plus an `index.html` at the output root listing them all — one document or fifty. `http.server` serves `index.html` in preference to a directory listing, so `opm serve` shows a real landing page with no further configuration. Each entry links to its document's first chunk. What the entry *shows* comes from the document's global fragments, so the index is built the same way TEI Publisher builds `browse.html` — through the ODD. This is thus done by declaring a fragment using the `display='browse'` models the stock ODDs already provide: ```toml [[chunking.fragments]] name = "browse" scope = "global" xpath = "(/article/info, /book/info)[1]" # TEI: "//teiHeader" parameters = { display = "browse" } ``` Those models emit the whole browse record — heading, author, description — and build their own link from `$parameters?doc`. That parameter is supplied automatically, per document. To use a different URL scheme, set it explicitly; `{doc}`, `{doc_stem}`, `{file}` and `{stem}` expand exactly as in `link_pattern`: ```toml parameters = { display = "browse", doc = "/exist/apps/edition/{doc}/{stem}" } ``` The same expansion applies to `[transform.parameters]`, with one placeholder more: `{prefix}` is the path from a chunk page back to the output root, where the shared `css/` and `assets/` live — `../` for the pages `opm chunk` writes, and empty for `opm transform`, which writes `assets/` right beside its output. A parameter holding a URL into `assets/` should use it rather than hard-coding that hop: ```toml [transform.parameters] context-path = "{prefix}assets" ``` The index degrades gracefully when a project has no such models: it falls back to a `title` fragment if one is declared, and to a readable form of the filename otherwise. Every entry stays clickable in all three cases. You can override the page itself with `chunking.index_template`. The template receives `documents` — each with `name`, `stem`, `label`, `href`, `chunks` and `fragments` — plus `title`: ```jinja {% for doc in documents %} {% endfor %} ``` Because `fragments` is passed whole, adding an author or date column needs no code — just another global fragment in `opm.toml` and a reference to it here. The template also receives `odd_css` and the assets' stylesheets, exactly as chunk pages do, so the `tei-*` classes of a browse record are styled the same way on the index as inside the edition. The `serafin` example goes one step further. Its letter pages and its index both extend one template of its own, `serafin.html.j2`, which holds the menu bar and the breadcrumb band: ```jinja {% extends "serafin.html.j2" %} {% block breadcrumb %}{{ title }}{% endblock %} {% block content %}
    …
{% endblock %} ``` So the landing page cannot drift from the letters it links to. The list's own rules live in `letter.css` too, under `.letter-list`, rather than in a `