use crate::types::*;
use crate::Result;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use quick_xml::Writer;
use std::io::Cursor;
pub struct CmlGenerator;
impl CmlGenerator {
pub fn generate(document: &CmlDocument) -> Result<String> {
let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 2);
Self::write_cml(&mut writer, document)?;
let result = writer.into_inner().into_inner();
Ok(String::from_utf8(result)?)
}
fn write_cml<W: std::io::Write>(writer: &mut Writer<W>, document: &CmlDocument) -> Result<()> {
let mut cml = BytesStart::new("cml");
cml.push_attribute(("profile", document.profile.as_str()));
cml.push_attribute(("version", document.version.as_str()));
cml.push_attribute(("encoding", document.encoding.as_str()));
if let Some(id) = &document.id {
cml.push_attribute(("id", id.as_str()));
}
writer.write_event(Event::Start(cml))?;
Self::write_header(writer, &document.header)?;
Self::write_body(writer, &document.body)?;
Self::write_footer(writer, &document.footer)?;
writer.write_event(Event::End(BytesEnd::new("cml")))?;
Ok(())
}
fn write_header<W: std::io::Write>(writer: &mut Writer<W>, header: &Header) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("header")))?;
Self::write_text_element(writer, "title", &header.title)?;
for author in &header.authors {
Self::write_author(writer, author)?;
}
for date in &header.dates {
Self::write_date_entry(writer, date)?;
}
for identifier in &header.identifiers {
Self::write_identifier(writer, identifier)?;
}
if let Some(version) = &header.version {
Self::write_text_element(writer, "version", version)?;
}
if let Some(description) = &header.description {
Self::write_text_element(writer, "description", description)?;
}
if let Some(provenance) = &header.provenance {
Self::write_text_element(writer, "provenance", provenance)?;
}
if let Some(source) = &header.source {
Self::write_text_element(writer, "source", source)?;
}
for meta in &header.meta {
Self::write_meta_entry(writer, meta)?;
}
writer.write_event(Event::End(BytesEnd::new("header")))?;
Ok(())
}
fn write_author<W: std::io::Write>(writer: &mut Writer<W>, author: &Author) -> Result<()> {
let mut elem = BytesStart::new("author");
if let Some(role) = &author.role {
elem.push_attribute(("role", role.as_str()));
}
if let Some(reference) = &author.reference {
elem.push_attribute(("reference", reference.as_str()));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&author.name)))?;
writer.write_event(Event::End(BytesEnd::new("author")))?;
Ok(())
}
fn write_date_entry<W: std::io::Write>(writer: &mut Writer<W>, date: &DateEntry) -> Result<()> {
let mut elem = BytesStart::new("date");
elem.push_attribute(("type", date.date_type.as_str()));
elem.push_attribute(("when", date.when.as_str()));
writer.write_event(Event::Empty(elem))?;
Ok(())
}
fn write_identifier<W: std::io::Write>(
writer: &mut Writer<W>,
identifier: &Identifier,
) -> Result<()> {
let mut elem = BytesStart::new("identifier");
elem.push_attribute(("scheme", identifier.scheme.as_str()));
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&identifier.value)))?;
writer.write_event(Event::End(BytesEnd::new("identifier")))?;
Ok(())
}
fn write_meta_entry<W: std::io::Write>(writer: &mut Writer<W>, meta: &MetaEntry) -> Result<()> {
let mut elem = BytesStart::new("meta");
elem.push_attribute(("name", meta.name.as_str()));
elem.push_attribute(("value", meta.value.as_str()));
writer.write_event(Event::Empty(elem))?;
Ok(())
}
fn write_body<W: std::io::Write>(writer: &mut Writer<W>, body: &Body) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("body")))?;
for block in &body.blocks {
Self::write_block_element(writer, block)?;
}
writer.write_event(Event::End(BytesEnd::new("body")))?;
Ok(())
}
fn write_block_element<W: std::io::Write>(
writer: &mut Writer<W>,
block: &BlockElement,
) -> Result<()> {
match block {
BlockElement::Section(section) => Self::write_section(writer, section)?,
BlockElement::Paragraph(para) => Self::write_paragraph(writer, para)?,
BlockElement::Heading(heading) => Self::write_heading(writer, heading)?,
BlockElement::Aside(aside) => Self::write_aside(writer, aside)?,
BlockElement::Quote(quote) => Self::write_quote(writer, quote)?,
BlockElement::List(list) => Self::write_list(writer, list)?,
BlockElement::Table(table) => Self::write_table(writer, table)?,
BlockElement::Code(code) => Self::write_code(writer, code)?,
BlockElement::Break(br) => Self::write_break(writer, br)?,
BlockElement::Figure(fig) => Self::write_figure(writer, fig)?,
}
Ok(())
}
fn write_section<W: std::io::Write>(writer: &mut Writer<W>, section: &Section) -> Result<()> {
let mut elem = BytesStart::new("section");
if let Some(id) = §ion.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(section_type) = §ion.section_type {
elem.push_attribute(("type", section_type.as_str()));
}
if let Some(reference) = §ion.reference {
elem.push_attribute(("ref", reference.as_str()));
}
writer.write_event(Event::Start(elem))?;
for block in §ion.content {
Self::write_block_element(writer, block)?;
}
writer.write_event(Event::End(BytesEnd::new("section")))?;
Ok(())
}
fn write_paragraph<W: std::io::Write>(writer: &mut Writer<W>, para: &Paragraph) -> Result<()> {
let mut elem = BytesStart::new("paragraph");
if let Some(id) = ¶.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(paragraph_type) = ¶.paragraph_type {
elem.push_attribute(("type", paragraph_type.as_str()));
}
writer.write_event(Event::Start(elem))?;
Self::write_inline_content(writer, ¶.content)?;
writer.write_event(Event::End(BytesEnd::new("paragraph")))?;
Ok(())
}
fn write_heading<W: std::io::Write>(writer: &mut Writer<W>, heading: &Heading) -> Result<()> {
let mut elem = BytesStart::new("heading");
if let Some(id) = &heading.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(heading_type) = &heading.heading_type {
elem.push_attribute(("type", heading_type.as_str()));
}
elem.push_attribute(("size", heading.size.to_string().as_str()));
writer.write_event(Event::Start(elem))?;
Self::write_inline_content(writer, &heading.content)?;
writer.write_event(Event::End(BytesEnd::new("heading")))?;
Ok(())
}
fn write_aside<W: std::io::Write>(writer: &mut Writer<W>, aside: &Aside) -> Result<()> {
let mut elem = BytesStart::new("aside");
if let Some(id) = &aside.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(aside_type) = &aside.aside_type {
elem.push_attribute(("type", aside_type.as_str()));
}
let side_str = match aside.side {
Side::Left => "left",
Side::Right => "right",
};
elem.push_attribute(("side", side_str));
writer.write_event(Event::Start(elem))?;
for block in &aside.content {
Self::write_block_element(writer, block)?;
}
writer.write_event(Event::End(BytesEnd::new("aside")))?;
Ok(())
}
fn write_quote<W: std::io::Write>(writer: &mut Writer<W>, quote: &Quote) -> Result<()> {
let mut elem = BytesStart::new("quote");
if let Some(id) = "e.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(reference) = "e.reference {
elem.push_attribute(("ref", reference.as_str()));
}
if let Some(source) = "e.source {
elem.push_attribute(("source", source.as_str()));
}
writer.write_event(Event::Start(elem))?;
for block in "e.content {
Self::write_block_element(writer, block)?;
}
writer.write_event(Event::End(BytesEnd::new("quote")))?;
Ok(())
}
fn write_list<W: std::io::Write>(writer: &mut Writer<W>, list: &List) -> Result<()> {
let mut elem = BytesStart::new("list");
if let Some(id) = &list.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(list_type) = &list.list_type {
let type_str = match list_type {
ListType::Ordered => "ordered",
ListType::Unordered => "unordered",
};
elem.push_attribute(("type", type_str));
}
if let Some(style) = &list.style {
let style_str = match style {
ListStyle::Numeric => "numeric",
ListStyle::Roman => "roman",
ListStyle::Alpha => "alpha",
ListStyle::Symbolic => "symbolic",
};
elem.push_attribute(("style", style_str));
}
writer.write_event(Event::Start(elem))?;
for item in &list.items {
Self::write_list_item(writer, item)?;
}
writer.write_event(Event::End(BytesEnd::new("list")))?;
Ok(())
}
fn write_list_item<W: std::io::Write>(writer: &mut Writer<W>, item: &ListItem) -> Result<()> {
let mut elem = BytesStart::new("item");
if let Some(id) = &item.id {
elem.push_attribute(("id", id.as_str()));
}
writer.write_event(Event::Start(elem))?;
match &item.content {
ListItemContent::Inline(inlines) => {
Self::write_inline_content(writer, inlines)?;
}
ListItemContent::Block(blocks) => {
for block in blocks {
Self::write_block_element(writer, block)?;
}
}
}
writer.write_event(Event::End(BytesEnd::new("item")))?;
Ok(())
}
fn write_table<W: std::io::Write>(writer: &mut Writer<W>, table: &Table) -> Result<()> {
let mut elem = BytesStart::new("table");
if let Some(id) = &table.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(table_type) = &table.table_type {
elem.push_attribute(("type", table_type.as_str()));
}
writer.write_event(Event::Start(elem))?;
if let Some(header) = &table.header {
Self::write_table_header(writer, header)?;
}
Self::write_table_body(writer, &table.body)?;
if let Some(footer) = &table.footer {
Self::write_table_footer(writer, footer)?;
}
writer.write_event(Event::End(BytesEnd::new("table")))?;
Ok(())
}
fn write_table_header<W: std::io::Write>(
writer: &mut Writer<W>,
header: &TableHeader,
) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("header")))?;
for row in &header.rows {
Self::write_table_row(writer, row, true)?;
}
writer.write_event(Event::End(BytesEnd::new("header")))?;
Ok(())
}
fn write_table_body<W: std::io::Write>(writer: &mut Writer<W>, body: &TableBody) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("body")))?;
for row in &body.rows {
Self::write_table_row(writer, row, false)?;
}
writer.write_event(Event::End(BytesEnd::new("body")))?;
Ok(())
}
fn write_table_footer<W: std::io::Write>(
writer: &mut Writer<W>,
footer: &TableFooter,
) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("footer")))?;
writer.write_event(Event::Start(BytesStart::new("caption")))?;
Self::write_inline_content(writer, &footer.caption.content)?;
writer.write_event(Event::End(BytesEnd::new("caption")))?;
writer.write_event(Event::End(BytesEnd::new("footer")))?;
Ok(())
}
fn write_table_row<W: std::io::Write>(
writer: &mut Writer<W>,
row: &TableRow,
is_header: bool,
) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("row")))?;
for column in &row.columns {
Self::write_table_column(writer, column, is_header)?;
}
writer.write_event(Event::End(BytesEnd::new("row")))?;
Ok(())
}
fn write_table_column<W: std::io::Write>(
writer: &mut Writer<W>,
column: &TableColumn,
is_header: bool,
) -> Result<()> {
let mut elem = BytesStart::new("column");
if is_header {
if let Some(sort) = &column.sort {
let sort_str = match sort {
SortOrder::Asc => "asc",
SortOrder::Desc => "desc",
};
elem.push_attribute(("sort", sort_str));
}
}
writer.write_event(Event::Start(elem))?;
Self::write_table_cell(writer, &column.cell)?;
writer.write_event(Event::End(BytesEnd::new("column")))?;
Ok(())
}
fn write_table_cell<W: std::io::Write>(writer: &mut Writer<W>, cell: &TableCell) -> Result<()> {
let mut elem = BytesStart::new("cell");
if let Some(colspan) = cell.colspan {
elem.push_attribute(("colspan", colspan.to_string().as_str()));
}
if let Some(rowspan) = cell.rowspan {
elem.push_attribute(("rowspan", rowspan.to_string().as_str()));
}
writer.write_event(Event::Start(elem))?;
Self::write_inline_content(writer, &cell.content)?;
writer.write_event(Event::End(BytesEnd::new("cell")))?;
Ok(())
}
fn write_code<W: std::io::Write>(writer: &mut Writer<W>, code: &Code) -> Result<()> {
let mut elem = BytesStart::new("code");
if let Some(id) = &code.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(lang) = &code.lang {
elem.push_attribute(("lang", lang.as_str()));
}
if let Some(copyable) = code.copyable {
elem.push_attribute(("copyable", if copyable { "true" } else { "false" }));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&code.content)))?;
writer.write_event(Event::End(BytesEnd::new("code")))?;
Ok(())
}
fn write_break<W: std::io::Write>(writer: &mut Writer<W>, br: &Break) -> Result<()> {
let mut elem = BytesStart::new("break");
if let Some(break_type) = &br.break_type {
elem.push_attribute(("type", break_type.as_str()));
}
writer.write_event(Event::Empty(elem))?;
Ok(())
}
fn write_figure<W: std::io::Write>(writer: &mut Writer<W>, fig: &Figure) -> Result<()> {
let mut elem = BytesStart::new("figure");
if let Some(id) = &fig.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(figure_type) = &fig.figure_type {
elem.push_attribute(("type", figure_type.as_str()));
}
if let Some(reference) = &fig.reference {
elem.push_attribute(("ref", reference.as_str()));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::End(BytesEnd::new("figure")))?;
Ok(())
}
fn write_inline_content<W: std::io::Write>(
writer: &mut Writer<W>,
content: &[InlineElement],
) -> Result<()> {
for elem in content {
Self::write_inline_element(writer, elem)?;
}
Ok(())
}
fn write_inline_element<W: std::io::Write>(
writer: &mut Writer<W>,
elem: &InlineElement,
) -> Result<()> {
match elem {
InlineElement::Text(text) => {
writer.write_event(Event::Text(BytesText::new(text)))?;
}
InlineElement::Em(em) => Self::write_em(writer, em)?,
InlineElement::Bo(bo) => Self::write_bo(writer, bo)?,
InlineElement::Un(un) => Self::write_un(writer, un)?,
InlineElement::St(st) => Self::write_st(writer, st)?,
InlineElement::Snip(snip) => Self::write_snip(writer, snip)?,
InlineElement::Key(key) => Self::write_key(writer, key)?,
InlineElement::Rf(rf) => Self::write_rf(writer, rf)?,
InlineElement::Tg(tg) => Self::write_tg(writer, tg)?,
InlineElement::Lk(lk) => Self::write_lk(writer, lk)?,
InlineElement::Curr(curr) => Self::write_curr(writer, curr)?,
InlineElement::End(end) => Self::write_end(writer, end)?,
}
Ok(())
}
fn write_em<W: std::io::Write>(writer: &mut Writer<W>, em: &Em) -> Result<()> {
let mut elem = BytesStart::new("em");
if let Some(em_type) = &em.em_type {
let type_str = match em_type {
EmphasisType::Stress => "stress",
EmphasisType::Contrast => "contrast",
};
elem.push_attribute(("type", type_str));
}
writer.write_event(Event::Start(elem))?;
Self::write_inline_content(writer, &em.content)?;
writer.write_event(Event::End(BytesEnd::new("em")))?;
Ok(())
}
fn write_bo<W: std::io::Write>(writer: &mut Writer<W>, bo: &Bo) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("bo")))?;
Self::write_inline_content(writer, &bo.content)?;
writer.write_event(Event::End(BytesEnd::new("bo")))?;
Ok(())
}
fn write_un<W: std::io::Write>(writer: &mut Writer<W>, un: &Un) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("un")))?;
Self::write_inline_content(writer, &un.content)?;
writer.write_event(Event::End(BytesEnd::new("un")))?;
Ok(())
}
fn write_st<W: std::io::Write>(writer: &mut Writer<W>, st: &St) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("st")))?;
Self::write_inline_content(writer, &st.content)?;
writer.write_event(Event::End(BytesEnd::new("st")))?;
Ok(())
}
fn write_snip<W: std::io::Write>(writer: &mut Writer<W>, snip: &Snip) -> Result<()> {
let mut elem = BytesStart::new("snip");
if let Some(char) = &snip.char {
elem.push_attribute(("char", char.as_str()));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&snip.content)))?;
writer.write_event(Event::End(BytesEnd::new("snip")))?;
Ok(())
}
fn write_key<W: std::io::Write>(writer: &mut Writer<W>, key: &Key) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("key")))?;
writer.write_event(Event::Text(BytesText::new(&key.content)))?;
writer.write_event(Event::End(BytesEnd::new("key")))?;
Ok(())
}
fn write_rf<W: std::io::Write>(writer: &mut Writer<W>, rf: &Rf) -> Result<()> {
let mut elem = BytesStart::new("rf");
elem.push_attribute(("ref", rf.reference.as_str()));
if let Some(role) = &rf.role {
elem.push_attribute(("role", role.as_str()));
}
if let Some(title) = &rf.title {
elem.push_attribute(("title", title.as_str()));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&rf.content)))?;
writer.write_event(Event::End(BytesEnd::new("rf")))?;
Ok(())
}
fn write_tg<W: std::io::Write>(writer: &mut Writer<W>, tg: &Tg) -> Result<()> {
let mut elem = BytesStart::new("tg");
elem.push_attribute(("ref", tg.reference.as_str()));
if let Some(role) = &tg.role {
elem.push_attribute(("role", role.as_str()));
}
if let Some(title) = &tg.title {
elem.push_attribute(("title", title.as_str()));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&tg.content)))?;
writer.write_event(Event::End(BytesEnd::new("tg")))?;
Ok(())
}
fn write_lk<W: std::io::Write>(writer: &mut Writer<W>, lk: &Lk) -> Result<()> {
let mut elem = BytesStart::new("lk");
elem.push_attribute(("ref", lk.reference.as_str()));
if let Some(role) = &lk.role {
elem.push_attribute(("role", role.as_str()));
}
if let Some(title) = &lk.title {
elem.push_attribute(("title", title.as_str()));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&lk.content)))?;
writer.write_event(Event::End(BytesEnd::new("lk")))?;
Ok(())
}
fn write_curr<W: std::io::Write>(writer: &mut Writer<W>, curr: &Curr) -> Result<()> {
let mut elem = BytesStart::new("curr");
elem.push_attribute(("type", curr.currency_type.as_str()));
if let Some(format) = &curr.format {
let format_str = match format {
CurrencyFormat::Symbol => "symbol",
CurrencyFormat::Code => "code",
CurrencyFormat::Name => "name",
};
elem.push_attribute(("format", format_str));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&curr.value)))?;
writer.write_event(Event::End(BytesEnd::new("curr")))?;
Ok(())
}
fn write_end<W: std::io::Write>(writer: &mut Writer<W>, end: &End) -> Result<()> {
let mut elem = BytesStart::new("end");
if let Some(kind) = &end.kind {
let kind_str = match kind {
EndKind::Line => "line",
EndKind::Verse => "verse",
EndKind::Item => "item",
};
elem.push_attribute(("kind", kind_str));
}
writer.write_event(Event::Empty(elem))?;
Ok(())
}
fn write_footer<W: std::io::Write>(writer: &mut Writer<W>, footer: &Footer) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("footer")))?;
if let Some(signatures) = &footer.signatures {
Self::write_signatures(writer, signatures)?;
}
if let Some(citations) = &footer.citations {
Self::write_citations(writer, citations)?;
}
if let Some(annotations) = &footer.annotations {
Self::write_annotations(writer, annotations)?;
}
writer.write_event(Event::End(BytesEnd::new("footer")))?;
Ok(())
}
fn write_signatures<W: std::io::Write>(
writer: &mut Writer<W>,
signatures: &Signatures,
) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("signatures")))?;
for signature in &signatures.signatures {
Self::write_signature(writer, signature)?;
}
writer.write_event(Event::End(BytesEnd::new("signatures")))?;
Ok(())
}
fn write_signature<W: std::io::Write>(
writer: &mut Writer<W>,
signature: &Signature,
) -> Result<()> {
let mut elem = BytesStart::new("signature");
elem.push_attribute(("when", signature.when.as_str()));
if let Some(role) = &signature.role {
elem.push_attribute(("role", role.as_str()));
}
if let Some(reference) = &signature.reference {
elem.push_attribute(("ref", reference.as_str()));
}
writer.write_event(Event::Start(elem))?;
writer.write_event(Event::Text(BytesText::new(&signature.content)))?;
writer.write_event(Event::End(BytesEnd::new("signature")))?;
Ok(())
}
fn write_citations<W: std::io::Write>(
writer: &mut Writer<W>,
citations: &Citations,
) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("citations")))?;
for citation in &citations.citations {
Self::write_citation(writer, citation)?;
}
writer.write_event(Event::End(BytesEnd::new("citations")))?;
Ok(())
}
fn write_citation<W: std::io::Write>(
writer: &mut Writer<W>,
citation: &Citation,
) -> Result<()> {
let mut elem = BytesStart::new("citation");
elem.push_attribute(("ref", citation.reference.as_str()));
if let Some(citation_type) = &citation.citation_type {
elem.push_attribute(("type", citation_type.as_str()));
}
writer.write_event(Event::Start(elem))?;
Self::write_inline_content(writer, &citation.content)?;
writer.write_event(Event::End(BytesEnd::new("citation")))?;
Ok(())
}
fn write_annotations<W: std::io::Write>(
writer: &mut Writer<W>,
annotations: &Annotations,
) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new("annotations")))?;
for note in &annotations.notes {
Self::write_note(writer, note)?;
}
writer.write_event(Event::End(BytesEnd::new("annotations")))?;
Ok(())
}
fn write_note<W: std::io::Write>(writer: &mut Writer<W>, note: &Note) -> Result<()> {
let mut elem = BytesStart::new("note");
if let Some(id) = ¬e.id {
elem.push_attribute(("id", id.as_str()));
}
if let Some(note_type) = ¬e.note_type {
elem.push_attribute(("type", note_type.as_str()));
}
if let Some(reference) = ¬e.reference {
elem.push_attribute(("ref", reference.as_str()));
}
writer.write_event(Event::Start(elem))?;
match ¬e.content {
NoteContent::Inline(inlines) => {
Self::write_inline_content(writer, inlines)?;
}
NoteContent::Block(blocks) => {
for block in blocks {
Self::write_block_element(writer, block)?;
}
}
}
writer.write_event(Event::End(BytesEnd::new("note")))?;
Ok(())
}
fn write_text_element<W: std::io::Write>(
writer: &mut Writer<W>,
tag: &str,
text: &str,
) -> Result<()> {
writer.write_event(Event::Start(BytesStart::new(tag)))?;
writer.write_event(Event::Text(BytesText::new(text)))?;
writer.write_event(Event::End(BytesEnd::new(tag)))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_minimal_document() {
let header = Header::new("Test Document".to_string());
let mut body = Body::new();
body.add_block(BlockElement::Paragraph(Paragraph::from_text(
"Hello, world!".to_string(),
)));
let doc = CmlDocument::new("core".to_string(), header)
.with_body(body)
.with_footer(Footer::empty());
let xml = CmlGenerator::generate(&doc).unwrap();
assert!(!xml.starts_with("<?xml"));
assert!(xml.starts_with("<cml"));
assert!(xml.contains("<header>"));
assert!(xml.contains("<title>Test Document</title>"));
assert!(xml.contains("<body>"));
assert!(xml.contains("<paragraph>Hello, world!</paragraph>"));
assert!(xml.contains("<footer>"));
}
#[test]
fn test_generate_with_metadata() {
let mut header = Header::new("Test".to_string());
header.add_author(Author::new("John Doe".to_string()).with_role("editor".to_string()));
header.add_date(DateEntry::created("2025-12-22".to_string()));
header.add_identifier(Identifier::doi("10.1234/test".to_string()));
header.add_meta("status".to_string(), "draft".to_string());
let mut body = Body::new();
body.add_block(BlockElement::Paragraph(Paragraph::from_text(
"Content".to_string(),
)));
let doc = CmlDocument::new("core".to_string(), header)
.with_body(body)
.with_footer(Footer::empty());
let xml = CmlGenerator::generate(&doc).unwrap();
assert!(xml.contains("<author role=\"editor\">John Doe</author>"));
assert!(xml.contains("<date type=\"created\" when=\"2025-12-22\"/>"));
assert!(xml.contains("<identifier scheme=\"doi\">10.1234/test</identifier>"));
assert!(xml.contains("<meta name=\"status\" value=\"draft\"/>"));
}
#[test]
fn test_generate_inline_elements() {
let header = Header::new("Test".to_string());
let mut body = Body::new();
body.add_block(BlockElement::Paragraph(Paragraph {
id: None,
paragraph_type: None,
content: vec![
InlineElement::Text("This is ".to_string()),
InlineElement::Em(Em {
em_type: None,
content: vec![InlineElement::Text("emphasized".to_string())],
}),
InlineElement::Text(" and ".to_string()),
InlineElement::Bo(Bo {
content: vec![InlineElement::Text("bold".to_string())],
}),
InlineElement::Text(" text.".to_string()),
],
}));
let doc = CmlDocument::new("core".to_string(), header)
.with_body(body)
.with_footer(Footer::empty());
let xml = CmlGenerator::generate(&doc).unwrap();
assert!(xml.contains("<em>emphasized</em>"));
assert!(xml.contains("<bo>bold</bo>"));
}
#[test]
fn test_generate_list() {
let header = Header::new("Test".to_string());
let mut body = Body::new();
body.add_block(BlockElement::List(List {
id: None,
list_type: Some(ListType::Ordered),
style: Some(ListStyle::Numeric),
items: vec![
ListItem {
id: None,
content: ListItemContent::Inline(vec![InlineElement::Text(
"First".to_string(),
)]),
},
ListItem {
id: None,
content: ListItemContent::Inline(vec![InlineElement::Text(
"Second".to_string(),
)]),
},
],
}));
let doc = CmlDocument::new("core".to_string(), header)
.with_body(body)
.with_footer(Footer::empty());
let xml = CmlGenerator::generate(&doc).unwrap();
assert!(xml.contains("<list type=\"ordered\" style=\"numeric\">"));
assert!(xml.contains("<item>First</item>"));
assert!(xml.contains("<item>Second</item>"));
}
#[test]
fn test_generate_footer_with_signature() {
let header = Header::new("Test".to_string());
let mut body = Body::new();
body.add_block(BlockElement::Paragraph(Paragraph::from_text(
"Content".to_string(),
)));
let signature = Signature::new("2025-12-22T10:30:00Z".to_string(), "Jane Doe".to_string())
.with_role("author".to_string());
let signatures = Signatures {
signatures: vec![signature],
};
let footer = Footer::empty().with_signatures(signatures);
let doc = CmlDocument::new("core".to_string(), header)
.with_body(body)
.with_footer(footer);
let xml = CmlGenerator::generate(&doc).unwrap();
assert!(xml.contains("<signatures>"));
assert!(xml.contains(
"<signature when=\"2025-12-22T10:30:00Z\" role=\"author\">Jane Doe</signature>"
));
}
#[test]
fn test_self_closing_elements() {
let mut header = Header::new("Test".to_string());
header.add_date(DateEntry::created("2025-12-22".to_string()));
header.add_meta("key".to_string(), "value".to_string());
let mut body = Body::new();
body.add_block(BlockElement::Break(Break {
break_type: Some("scene".to_string()),
}));
let doc = CmlDocument::new("core".to_string(), header)
.with_body(body)
.with_footer(Footer::empty());
let xml = CmlGenerator::generate(&doc).unwrap();
assert!(xml.contains("<date type=\"created\" when=\"2025-12-22\"/>"));
assert!(xml.contains("<meta name=\"key\" value=\"value\"/>"));
assert!(xml.contains("<break type=\"scene\"/>"));
}
}