Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Single Source Format for All Publishing

Date: 03-06-2026

A single source format can drive:

  • Printed books (PDF, EPUB)
  • HTML books with quizzes
  • TTS audiobook generation
  • Video generation with images, animations, and narration
  • Mobile apps and web apps

Recommendation: YAML metadata

A practical architecture would look like this:

book.yaml
    ↓
serde_yaml
    ↓
Rust structs
    ↓
Grade validation
    ↓
XML inline parser
    ↓
Document AST
    ↓
HTML Generator
Typst Generator
TTS Generator

1. YAML

title: Biology
grade: 11
language: en

chapters:
  - id: cell
    title: Cell

    sections:
      - title: Introduction

        content:
          - type: paragraph
            text: |
              Cells are the <b>basic unit of life</b>.

              The <i>cell membrane</i> protects the cell.

              <color value="red">DNA</color> stores genetic information.

          - type: image
            src: images/cell.png
            alt: Animal Cell

          - type: quiz
            question: What is the basic unit of life?

            options:
              - Cell
              - Tissue
              - Organ

            answer: 0

2. Rust Structures

#![allow(unused)]
fn main() {
use serde::Deserialize;
use garde::Validate;

#[derive(Debug, Deserialize, Validate)]
pub struct Book {
    #[garde(length(min = 1))]
    pub title: String,

    #[garde(range(min = 1, max = 12))]
    pub grade: u8,

    pub language: String,

    pub chapters: Vec<Chapter>,
}

#[derive(Debug, Deserialize)]
pub struct Chapter {
    pub id: String,
    pub title: String,
    pub sections: Vec<Section>,
}

#[derive(Debug, Deserialize)]
pub struct Section {
    pub title: String,
    pub content: Vec<Content>,
}

#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
pub enum Content {
    #[serde(rename = "paragraph")]
    Paragraph {
        text: String,
    },

    #[serde(rename = "image")]
    Image {
        src: String,
        alt: String,
    },

    #[serde(rename = "quiz")]
    Quiz {
        question: String,
        options: Vec<String>,
        answer: usize,
    },
}
}

3. Grade Validation

#![allow(unused)]
fn main() {
use garde::Validate;

let yaml = std::fs::read_to_string("book.yaml")?;

let book: Book = serde_yaml::from_str(&yaml)?;

book.validate()?;
}

Fails:

grade: 15

Error:

grade must be between 1 and 12

4. Parse Inline XML

Wrap text inside a root tag:

#![allow(unused)]
fn main() {
let wrapped = format!("<root>{}</root>", text);
}

Parse with:

roxmltree = "0.20"

Example:

#![allow(unused)]
fn main() {
use roxmltree::Document;

let doc = Document::parse(&wrapped)?;
}

5. Internal AST

Convert XML to:

#![allow(unused)]
fn main() {
pub enum Inline {
    Text(String),

    Bold(Vec<Inline>),

    Italic(Vec<Inline>),

    Color {
        color: String,
        children: Vec<Inline>,
    },
}
}

Example:

Cells are the <b>basic unit of life</b>.

becomes:

#![allow(unused)]
fn main() {
[
    Text("Cells are the "),
    Bold([
        Text("basic unit of life")
    ]),
    Text(".")
]
}

6. HTML Generator

#![allow(unused)]
fn main() {
fn html_inline(node: &Inline) -> String {
    match node {
        Inline::Text(t) => t.clone(),

        Inline::Bold(children) => {
            format!(
                "<strong>{}</strong>",
                children.iter()
                    .map(html_inline)
                    .collect::<String>()
            )
        }

        Inline::Italic(children) => {
            format!(
                "<em>{}</em>",
                children.iter()
                    .map(html_inline)
                    .collect::<String>()
            )
        }

        Inline::Color {
            color,
            children
        } => {
            format!(
                "<span style=\"color:{}\">{}</span>",
                color,
                children.iter()
                    .map(html_inline)
                    .collect::<String>()
            )
        }
    }
}
}

Output:

<p>
Cells are the
<strong>basic unit of life</strong>.
</p>

<p>
<em>cell membrane</em>
</p>

<p>
<span style="color:red">
DNA
</span>
</p>

7. Typst Generator

#![allow(unused)]
fn main() {
fn typst_inline(node: &Inline) -> String {
    match node {
        Inline::Text(t) => t.clone(),

        Inline::Bold(children) => {
            format!(
                "#strong[{}]",
                children.iter()
                    .map(typst_inline)
                    .collect::<String>()
            )
        }

        Inline::Italic(children) => {
            format!(
                "#emph[{}]",
                children.iter()
                    .map(typst_inline)
                    .collect::<String>()
            )
        }

        Inline::Color {
            color,
            children
        } => {
            format!(
                "#text(fill: {})[{}]",
                color,
                children.iter()
                    .map(typst_inline)
                    .collect::<String>()
            )
        }
    }
}
}

Output:

Cells are the
#strong[basic unit of life].

The
#emph[cell membrane]
protects the cell.

#text(fill: red)[DNA]
stores genetic information.

8. Quiz HTML

<div class="quiz">
  <h4>
    What is the basic unit of life?
  </h4>

  <input type="radio"> Cell
  <input type="radio"> Tissue
  <input type="radio"> Organ
</div>

9. TTS

Ignore formatting tags entirely:

#![allow(unused)]
fn main() {
fn extract_text(nodes: &[Inline]) -> String
}

Output:

Cells are the basic unit of life.

The cell membrane protects the cell.

DNA stores genetic information.

Feed directly into a TTS engine.


10. Video Generation

Each content block becomes a scene:

- paragraph
- image
- quiz

Scene 1:
Narration:
Cells are the basic unit of life.

Scene 2:
Show:
images/cell.png

Scene 3:
Quiz Screen

This gives you a single YAML source that can generate:

  • HTML books
  • Typst/PDF textbooks
  • EPUB
  • TTS audiobooks
  • Educational videos
  • Interactive quizzes

while keeping authoring simple with XML-like inline formatting only where needed.