Skip to main content

ttml_subtitle/
lib.rs

1//! TTML2 / IMSC 1.1 timed-text subtitle parser.
2//!
3//! Parses W3C Timed Text Markup Language 2 (TTML2) documents and validates them
4//! against IMSC 1.1 Text Profile and Image Profile constraints. Parse a document,
5//! then validate it separately — the two passes are independent so callers can
6//! inspect a non-conformant document before deciding whether to reject it.
7//!
8//! ## Round-trip guarantee
9//!
10//! Parsing, serializing, and re-parsing yields a semantically equal document
11//! (`parse → serialize → re-parse → equal`). But the serialized XML is **not**
12//! byte-identical to the input: comments, processing instructions, and XML
13//! declarations are not stored; attribute order is deterministic but not
14//! preserved; namespace prefixes are generated by the serializer;
15//! `other_attributes` (custom attributes not explicitly modeled) are not
16//! re-emitted; and foreign/unknown child elements (allowed by TTML2 §7.2)
17//! are silently dropped during parsing, never re-emitted. See
18//! [`README.md §Round-Trip Guarantee`] for the full verified list.
19//!
20//! ## From-scratch authoring
21//!
22//! All element types implement `Default`. Construct a document from nothing:
23//!
24//! ```
25//! use ttml_subtitle::{Document, InlineContent};
26//!
27//! let mut doc = Document::default();
28//! doc.tt.xml_lang = Some("en".into());
29//!
30//! let mut body = ttml_subtitle::BodyElement::default();
31//! let mut div = ttml_subtitle::DivElement::default();
32//! let mut p = ttml_subtitle::PElement::default();
33//! p.begin = Some("0s".into());
34//! p.end = Some("5s".into());
35//! p.content.push(InlineContent::Text("Hello".into()));
36//! div.paragraphs.push(p);
37//! body.divs.push(div);
38//! doc.tt.body = Some(body);
39//!
40//! let xml = doc.to_xml();
41//! assert!(xml.contains("Hello"));
42//! ```
43//!
44//! Spec citations:
45//! - W3C TTML2 Recommendation (08 Nov 2018): `ttml2-syntax.md` in this crate's `docs/`.
46//! - W3C IMSC 1.1 Recommendation (08 Nov 2018, edited 27 Apr 2020): `imsc11-profiles.md` in this crate's `docs/`.
47//!
48//! ```
49//! use ttml_subtitle::Document;
50//!
51//! let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
52//! <tt xml:lang="en" xmlns="http://www.w3.org/ns/ttml"
53//!    xmlns:ttp="http://www.w3.org/ns/ttml#parameter"
54//!    ttp:contentProfiles="http://www.w3.org/ns/ttml/profile/imsc1.1/text">
55//!   <body><div><p begin="0s" end="5s">Hello</p></div></body>
56//! </tt>"#;
57//!
58//! let doc = Document::parse_str(xml).unwrap();
59//! let body = doc.tt.body.as_ref().unwrap();
60//! assert_eq!(body.divs[0].paragraphs[0].begin.as_deref(), Some("0s"));
61//! ```
62#![cfg_attr(not(feature = "std"), no_std)]
63#![warn(missing_docs)]
64#![cfg_attr(docsrs, feature(doc_cfg))]
65
66extern crate alloc;
67
68pub mod document;
69pub mod error;
70pub mod time;
71pub mod validation;
72
73pub use document::{
74    BodyElement, BrElement, DivElement, Document, HeadElement, ImageElement, InlineContent,
75    LayoutElement, PElement, RegionElement, SpanElement, StyleAttributes, StyleElement,
76    StylingElement, TtElement, XmlDeclaration,
77};
78pub use error::{Error, Result};
79pub use time::TimeExpression;
80pub use validation::{ImscVersion, Profile, ValidationError, ValidationResult, Validator};
81
82/// Parse a TTML document from a string.
83///
84/// This is a convenience wrapper around [`Document::parse_str`].
85pub fn parse(xml: &str) -> Result<Document> {
86    Document::parse_str(xml)
87}