Text Encoding Initiative (TEI) Episodic Profile schema

Updated Jul 01, 2026

The TEI Episodic Profile is formally documented in an ODD (One Document Does it all) specification at schemas/tei-episodic-profile.odd. This specification:

  • Defines the exact elements and attributes permitted in the profile
  • Includes Schematron rules for validation constraints such as unique xml:id values and speaker cross-referencing
  • Can be processed by TEI tools (Roma, TEI Stylesheets) to generate Relax NG and Schematron schemas for external validation
  • Ships with a pre-generated Relax NG schema at schemas/tei-episodic-profile.rng. Rust callers can retrieve it via tei_xml::relax_ng_schema() or write it to disk using tei_xml::write_relax_ng_schema(path) before invoking external validators such as jing.

The profile supports:

  • Header metadata: title, speaker declarations, annotation systems, canonical citation declarations (refsDecl / citeStructure / citeData), revision history
  • Body structure: paragraphs (<p>), utterances (<u>) with optional speaker attribution via @who plus local provenance attributes (@n, @source, @resp, @cert, @corresp, @ana), and thematic divisions (<div>) with @type (required and validated via DivType), optional @subtype, optional @xml:id, and an optional Head wrapper for a single leading <head> element. Divisions can contain paragraphs, utterances, lists (<list>), and nested divisions. Lists hold ordered items (<item>) that carry optional @n (numbering or timestamp metadata), @corresp (pointer list for cross-references), and @xml:id. Each item may include an optional label prefix (<label>) followed by inline content. Paragraphs, utterances, items, labels, and heads all store ordered Inline nodes; plain strings can be constructed via P::from_text_segments, Utterance::from_text_segments, Item::from_text_segments, Label::from_text, and Head::from_text. Lists are permitted within <div> elements only; <list> cannot appear directly as a child of <body> and must instead be wrapped in a <div>. Document validation also rejects duplicate xml:id values across nested division content and enforces declared-speaker checks when a profile cast is present.

Building divisions

Use the root tei_core re-exports to assemble a division tree before wrapping it in a document body block:

use tei_core::{Div, Head, TeiDocument, BodyBlock};

fn build_episode_doc() -> Result<(), tei_core::TeiError> {
    // Parent division.
    let mut parent_div = Div::new("segment")?;
    parent_div.set_subtype("chapter-markers")?;
    parent_div.set_id("ch-01".to_string())?;
    parent_div.set_head(Head::from_text("Chapter markers")?);

    // Nested child division.
    let mut child_div = Div::new("segment")?;
    child_div.set_subtype("cold-open")?;
    child_div.set_head(Head::from_text("Cold open")?);

    // Attach the child and wrap the parent as a body block.
    parent_div.push_div(child_div);

    let header = tei_core::TeiHeader::new(tei_core::FileDesc::from_title_str(
        "Episode outline",
    )?);
    let mut text = tei_core::TeiText::empty();
    text.extend([BodyBlock::Div(parent_div)]);

    let _document = TeiDocument::new(header, text);
    Ok(())
}
  • Stand-off overlays: root-level <standOff> containers with <spanGrp>/<span> layers for many-to-many citation and analytical markup
  • Inline elements: emphasis (<hi> with optional @rend attribute), pause markers (<pause> with optional @dur and @type)

See schemas/README.md for instructions on generating schemas and validating documents. In this profile, canonical citation declarations belong in <encodingDesc><refsDecl>...</refsDecl></encodingDesc>, while citation and provenance overlays that target multiple body nodes belong in the root <standOff> section.