Skip to main content

cml_rs/
generator.rs

1//! CML v0.2 XML Generator
2//!
3//! Generates CML v0.2 XML documents from strongly-typed structures
4//!
5//! Output format: Clean XML without <?xml...?> declaration
6
7use crate::types::*;
8use crate::Result;
9use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
10use quick_xml::Writer;
11use std::io::Cursor;
12
13/// Generator for CML v0.2 documents
14pub struct CmlGenerator;
15
16impl CmlGenerator {
17    /// Generate XML from CML v0.2 document
18    ///
19    /// Output starts directly with <cml>, no XML declaration
20    pub fn generate(document: &CmlDocument) -> Result<String> {
21        let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 2);
22
23        // NO XML DECLARATION - start directly with <cml>
24        Self::write_cml(&mut writer, document)?;
25
26        let result = writer.into_inner().into_inner();
27        Ok(String::from_utf8(result)?)
28    }
29
30    /// Write <cml> root element
31    fn write_cml<W: std::io::Write>(writer: &mut Writer<W>, document: &CmlDocument) -> Result<()> {
32        let mut cml = BytesStart::new("cml");
33        cml.push_attribute(("profile", document.profile.as_str()));
34        cml.push_attribute(("version", document.version.as_str()));
35        cml.push_attribute(("encoding", document.encoding.as_str()));
36
37        if let Some(id) = &document.id {
38            cml.push_attribute(("id", id.as_str()));
39        }
40
41        writer.write_event(Event::Start(cml))?;
42
43        // Header (required)
44        Self::write_header(writer, &document.header)?;
45
46        // Body (required)
47        Self::write_body(writer, &document.body)?;
48
49        // Footer (required, may be empty)
50        Self::write_footer(writer, &document.footer)?;
51
52        writer.write_event(Event::End(BytesEnd::new("cml")))?;
53
54        Ok(())
55    }
56
57    /// Write <header> element
58    fn write_header<W: std::io::Write>(writer: &mut Writer<W>, header: &Header) -> Result<()> {
59        writer.write_event(Event::Start(BytesStart::new("header")))?;
60
61        // Title (required)
62        Self::write_text_element(writer, "title", &header.title)?;
63
64        // Authors
65        for author in &header.authors {
66            Self::write_author(writer, author)?;
67        }
68
69        // Dates (self-closing)
70        for date in &header.dates {
71            Self::write_date_entry(writer, date)?;
72        }
73
74        // Identifiers
75        for identifier in &header.identifiers {
76            Self::write_identifier(writer, identifier)?;
77        }
78
79        // Version
80        if let Some(version) = &header.version {
81            Self::write_text_element(writer, "version", version)?;
82        }
83
84        // Description
85        if let Some(description) = &header.description {
86            Self::write_text_element(writer, "description", description)?;
87        }
88
89        // Provenance
90        if let Some(provenance) = &header.provenance {
91            Self::write_text_element(writer, "provenance", provenance)?;
92        }
93
94        // Source
95        if let Some(source) = &header.source {
96            Self::write_text_element(writer, "source", source)?;
97        }
98
99        // Meta entries (self-closing)
100        for meta in &header.meta {
101            Self::write_meta_entry(writer, meta)?;
102        }
103
104        writer.write_event(Event::End(BytesEnd::new("header")))?;
105
106        Ok(())
107    }
108
109    /// Write <author> element
110    fn write_author<W: std::io::Write>(writer: &mut Writer<W>, author: &Author) -> Result<()> {
111        let mut elem = BytesStart::new("author");
112
113        if let Some(role) = &author.role {
114            elem.push_attribute(("role", role.as_str()));
115        }
116
117        if let Some(reference) = &author.reference {
118            elem.push_attribute(("reference", reference.as_str()));
119        }
120
121        writer.write_event(Event::Start(elem))?;
122        writer.write_event(Event::Text(BytesText::new(&author.name)))?;
123        writer.write_event(Event::End(BytesEnd::new("author")))?;
124
125        Ok(())
126    }
127
128    /// Write <date> element (self-closing)
129    fn write_date_entry<W: std::io::Write>(writer: &mut Writer<W>, date: &DateEntry) -> Result<()> {
130        let mut elem = BytesStart::new("date");
131        elem.push_attribute(("type", date.date_type.as_str()));
132        elem.push_attribute(("when", date.when.as_str()));
133        writer.write_event(Event::Empty(elem))?;
134        Ok(())
135    }
136
137    /// Write <identifier> element
138    fn write_identifier<W: std::io::Write>(
139        writer: &mut Writer<W>,
140        identifier: &Identifier,
141    ) -> Result<()> {
142        let mut elem = BytesStart::new("identifier");
143        elem.push_attribute(("scheme", identifier.scheme.as_str()));
144
145        writer.write_event(Event::Start(elem))?;
146        writer.write_event(Event::Text(BytesText::new(&identifier.value)))?;
147        writer.write_event(Event::End(BytesEnd::new("identifier")))?;
148
149        Ok(())
150    }
151
152    /// Write <meta> element (self-closing)
153    fn write_meta_entry<W: std::io::Write>(writer: &mut Writer<W>, meta: &MetaEntry) -> Result<()> {
154        let mut elem = BytesStart::new("meta");
155        elem.push_attribute(("name", meta.name.as_str()));
156        elem.push_attribute(("value", meta.value.as_str()));
157        writer.write_event(Event::Empty(elem))?;
158        Ok(())
159    }
160
161    /// Write <body> element
162    fn write_body<W: std::io::Write>(writer: &mut Writer<W>, body: &Body) -> Result<()> {
163        writer.write_event(Event::Start(BytesStart::new("body")))?;
164
165        for block in &body.blocks {
166            Self::write_block_element(writer, block)?;
167        }
168
169        writer.write_event(Event::End(BytesEnd::new("body")))?;
170
171        Ok(())
172    }
173
174    /// Write a block element
175    fn write_block_element<W: std::io::Write>(
176        writer: &mut Writer<W>,
177        block: &BlockElement,
178    ) -> Result<()> {
179        match block {
180            BlockElement::Section(section) => Self::write_section(writer, section)?,
181            BlockElement::Paragraph(para) => Self::write_paragraph(writer, para)?,
182            BlockElement::Heading(heading) => Self::write_heading(writer, heading)?,
183            BlockElement::Aside(aside) => Self::write_aside(writer, aside)?,
184            BlockElement::Quote(quote) => Self::write_quote(writer, quote)?,
185            BlockElement::List(list) => Self::write_list(writer, list)?,
186            BlockElement::Table(table) => Self::write_table(writer, table)?,
187            BlockElement::Code(code) => Self::write_code(writer, code)?,
188            BlockElement::Break(br) => Self::write_break(writer, br)?,
189            BlockElement::Figure(fig) => Self::write_figure(writer, fig)?,
190        }
191
192        Ok(())
193    }
194
195    /// Write <section> element
196    fn write_section<W: std::io::Write>(writer: &mut Writer<W>, section: &Section) -> Result<()> {
197        let mut elem = BytesStart::new("section");
198
199        if let Some(id) = &section.id {
200            elem.push_attribute(("id", id.as_str()));
201        }
202
203        if let Some(section_type) = &section.section_type {
204            elem.push_attribute(("type", section_type.as_str()));
205        }
206
207        if let Some(reference) = &section.reference {
208            elem.push_attribute(("ref", reference.as_str()));
209        }
210
211        writer.write_event(Event::Start(elem))?;
212
213        for block in &section.content {
214            Self::write_block_element(writer, block)?;
215        }
216
217        writer.write_event(Event::End(BytesEnd::new("section")))?;
218
219        Ok(())
220    }
221
222    /// Write <paragraph> element
223    fn write_paragraph<W: std::io::Write>(writer: &mut Writer<W>, para: &Paragraph) -> Result<()> {
224        let mut elem = BytesStart::new("paragraph");
225
226        if let Some(id) = &para.id {
227            elem.push_attribute(("id", id.as_str()));
228        }
229
230        if let Some(paragraph_type) = &para.paragraph_type {
231            elem.push_attribute(("type", paragraph_type.as_str()));
232        }
233
234        writer.write_event(Event::Start(elem))?;
235        Self::write_inline_content(writer, &para.content)?;
236        writer.write_event(Event::End(BytesEnd::new("paragraph")))?;
237
238        Ok(())
239    }
240
241    /// Write <heading> element
242    fn write_heading<W: std::io::Write>(writer: &mut Writer<W>, heading: &Heading) -> Result<()> {
243        let mut elem = BytesStart::new("heading");
244
245        if let Some(id) = &heading.id {
246            elem.push_attribute(("id", id.as_str()));
247        }
248
249        if let Some(heading_type) = &heading.heading_type {
250            elem.push_attribute(("type", heading_type.as_str()));
251        }
252
253        elem.push_attribute(("size", heading.size.to_string().as_str()));
254
255        writer.write_event(Event::Start(elem))?;
256        Self::write_inline_content(writer, &heading.content)?;
257        writer.write_event(Event::End(BytesEnd::new("heading")))?;
258
259        Ok(())
260    }
261
262    /// Write <aside> element
263    fn write_aside<W: std::io::Write>(writer: &mut Writer<W>, aside: &Aside) -> Result<()> {
264        let mut elem = BytesStart::new("aside");
265
266        if let Some(id) = &aside.id {
267            elem.push_attribute(("id", id.as_str()));
268        }
269
270        if let Some(aside_type) = &aside.aside_type {
271            elem.push_attribute(("type", aside_type.as_str()));
272        }
273
274        let side_str = match aside.side {
275            Side::Left => "left",
276            Side::Right => "right",
277        };
278        elem.push_attribute(("side", side_str));
279
280        writer.write_event(Event::Start(elem))?;
281
282        for block in &aside.content {
283            Self::write_block_element(writer, block)?;
284        }
285
286        writer.write_event(Event::End(BytesEnd::new("aside")))?;
287
288        Ok(())
289    }
290
291    /// Write <quote> element
292    fn write_quote<W: std::io::Write>(writer: &mut Writer<W>, quote: &Quote) -> Result<()> {
293        let mut elem = BytesStart::new("quote");
294
295        if let Some(id) = &quote.id {
296            elem.push_attribute(("id", id.as_str()));
297        }
298
299        if let Some(reference) = &quote.reference {
300            elem.push_attribute(("ref", reference.as_str()));
301        }
302
303        if let Some(source) = &quote.source {
304            elem.push_attribute(("source", source.as_str()));
305        }
306
307        writer.write_event(Event::Start(elem))?;
308
309        for block in &quote.content {
310            Self::write_block_element(writer, block)?;
311        }
312
313        writer.write_event(Event::End(BytesEnd::new("quote")))?;
314
315        Ok(())
316    }
317
318    /// Write <list> element
319    fn write_list<W: std::io::Write>(writer: &mut Writer<W>, list: &List) -> Result<()> {
320        let mut elem = BytesStart::new("list");
321
322        if let Some(id) = &list.id {
323            elem.push_attribute(("id", id.as_str()));
324        }
325
326        if let Some(list_type) = &list.list_type {
327            let type_str = match list_type {
328                ListType::Ordered => "ordered",
329                ListType::Unordered => "unordered",
330            };
331            elem.push_attribute(("type", type_str));
332        }
333
334        if let Some(style) = &list.style {
335            let style_str = match style {
336                ListStyle::Numeric => "numeric",
337                ListStyle::Roman => "roman",
338                ListStyle::Alpha => "alpha",
339                ListStyle::Symbolic => "symbolic",
340            };
341            elem.push_attribute(("style", style_str));
342        }
343
344        writer.write_event(Event::Start(elem))?;
345
346        for item in &list.items {
347            Self::write_list_item(writer, item)?;
348        }
349
350        writer.write_event(Event::End(BytesEnd::new("list")))?;
351
352        Ok(())
353    }
354
355    /// Write <item> element
356    fn write_list_item<W: std::io::Write>(writer: &mut Writer<W>, item: &ListItem) -> Result<()> {
357        let mut elem = BytesStart::new("item");
358
359        if let Some(id) = &item.id {
360            elem.push_attribute(("id", id.as_str()));
361        }
362
363        writer.write_event(Event::Start(elem))?;
364
365        match &item.content {
366            ListItemContent::Inline(inlines) => {
367                Self::write_inline_content(writer, inlines)?;
368            }
369            ListItemContent::Block(blocks) => {
370                for block in blocks {
371                    Self::write_block_element(writer, block)?;
372                }
373            }
374        }
375
376        writer.write_event(Event::End(BytesEnd::new("item")))?;
377
378        Ok(())
379    }
380
381    /// Write <table> element
382    fn write_table<W: std::io::Write>(writer: &mut Writer<W>, table: &Table) -> Result<()> {
383        let mut elem = BytesStart::new("table");
384
385        if let Some(id) = &table.id {
386            elem.push_attribute(("id", id.as_str()));
387        }
388
389        if let Some(table_type) = &table.table_type {
390            elem.push_attribute(("type", table_type.as_str()));
391        }
392
393        writer.write_event(Event::Start(elem))?;
394
395        // Header (optional)
396        if let Some(header) = &table.header {
397            Self::write_table_header(writer, header)?;
398        }
399
400        // Body (required)
401        Self::write_table_body(writer, &table.body)?;
402
403        // Footer (optional)
404        if let Some(footer) = &table.footer {
405            Self::write_table_footer(writer, footer)?;
406        }
407
408        writer.write_event(Event::End(BytesEnd::new("table")))?;
409
410        Ok(())
411    }
412
413    /// Write table <header>
414    fn write_table_header<W: std::io::Write>(
415        writer: &mut Writer<W>,
416        header: &TableHeader,
417    ) -> Result<()> {
418        writer.write_event(Event::Start(BytesStart::new("header")))?;
419
420        for row in &header.rows {
421            Self::write_table_row(writer, row, true)?;
422        }
423
424        writer.write_event(Event::End(BytesEnd::new("header")))?;
425
426        Ok(())
427    }
428
429    /// Write table <body>
430    fn write_table_body<W: std::io::Write>(writer: &mut Writer<W>, body: &TableBody) -> Result<()> {
431        writer.write_event(Event::Start(BytesStart::new("body")))?;
432
433        for row in &body.rows {
434            Self::write_table_row(writer, row, false)?;
435        }
436
437        writer.write_event(Event::End(BytesEnd::new("body")))?;
438
439        Ok(())
440    }
441
442    /// Write table <footer>
443    fn write_table_footer<W: std::io::Write>(
444        writer: &mut Writer<W>,
445        footer: &TableFooter,
446    ) -> Result<()> {
447        writer.write_event(Event::Start(BytesStart::new("footer")))?;
448
449        // Caption
450        writer.write_event(Event::Start(BytesStart::new("caption")))?;
451        Self::write_inline_content(writer, &footer.caption.content)?;
452        writer.write_event(Event::End(BytesEnd::new("caption")))?;
453
454        writer.write_event(Event::End(BytesEnd::new("footer")))?;
455
456        Ok(())
457    }
458
459    /// Write table <row>
460    fn write_table_row<W: std::io::Write>(
461        writer: &mut Writer<W>,
462        row: &TableRow,
463        is_header: bool,
464    ) -> Result<()> {
465        writer.write_event(Event::Start(BytesStart::new("row")))?;
466
467        for column in &row.columns {
468            Self::write_table_column(writer, column, is_header)?;
469        }
470
471        writer.write_event(Event::End(BytesEnd::new("row")))?;
472
473        Ok(())
474    }
475
476    /// Write table <column>
477    fn write_table_column<W: std::io::Write>(
478        writer: &mut Writer<W>,
479        column: &TableColumn,
480        is_header: bool,
481    ) -> Result<()> {
482        let mut elem = BytesStart::new("column");
483
484        if is_header {
485            if let Some(sort) = &column.sort {
486                let sort_str = match sort {
487                    SortOrder::Asc => "asc",
488                    SortOrder::Desc => "desc",
489                };
490                elem.push_attribute(("sort", sort_str));
491            }
492        }
493
494        writer.write_event(Event::Start(elem))?;
495        Self::write_table_cell(writer, &column.cell)?;
496        writer.write_event(Event::End(BytesEnd::new("column")))?;
497
498        Ok(())
499    }
500
501    /// Write table <cell>
502    fn write_table_cell<W: std::io::Write>(writer: &mut Writer<W>, cell: &TableCell) -> Result<()> {
503        let mut elem = BytesStart::new("cell");
504
505        if let Some(colspan) = cell.colspan {
506            elem.push_attribute(("colspan", colspan.to_string().as_str()));
507        }
508
509        if let Some(rowspan) = cell.rowspan {
510            elem.push_attribute(("rowspan", rowspan.to_string().as_str()));
511        }
512
513        writer.write_event(Event::Start(elem))?;
514        Self::write_inline_content(writer, &cell.content)?;
515        writer.write_event(Event::End(BytesEnd::new("cell")))?;
516
517        Ok(())
518    }
519
520    /// Write <code> element
521    fn write_code<W: std::io::Write>(writer: &mut Writer<W>, code: &Code) -> Result<()> {
522        let mut elem = BytesStart::new("code");
523
524        if let Some(id) = &code.id {
525            elem.push_attribute(("id", id.as_str()));
526        }
527
528        if let Some(lang) = &code.lang {
529            elem.push_attribute(("lang", lang.as_str()));
530        }
531
532        if let Some(copyable) = code.copyable {
533            elem.push_attribute(("copyable", if copyable { "true" } else { "false" }));
534        }
535
536        writer.write_event(Event::Start(elem))?;
537        writer.write_event(Event::Text(BytesText::new(&code.content)))?;
538        writer.write_event(Event::End(BytesEnd::new("code")))?;
539
540        Ok(())
541    }
542
543    /// Write <break> element (self-closing)
544    fn write_break<W: std::io::Write>(writer: &mut Writer<W>, br: &Break) -> Result<()> {
545        let mut elem = BytesStart::new("break");
546
547        if let Some(break_type) = &br.break_type {
548            elem.push_attribute(("type", break_type.as_str()));
549        }
550
551        writer.write_event(Event::Empty(elem))?;
552
553        Ok(())
554    }
555
556    /// Write <figure> element (reserved for v0.3)
557    fn write_figure<W: std::io::Write>(writer: &mut Writer<W>, fig: &Figure) -> Result<()> {
558        let mut elem = BytesStart::new("figure");
559
560        if let Some(id) = &fig.id {
561            elem.push_attribute(("id", id.as_str()));
562        }
563
564        if let Some(figure_type) = &fig.figure_type {
565            elem.push_attribute(("type", figure_type.as_str()));
566        }
567
568        if let Some(reference) = &fig.reference {
569            elem.push_attribute(("ref", reference.as_str()));
570        }
571
572        // Empty for v0.3
573        writer.write_event(Event::Start(elem))?;
574        writer.write_event(Event::End(BytesEnd::new("figure")))?;
575
576        Ok(())
577    }
578
579    /// Write inline content
580    fn write_inline_content<W: std::io::Write>(
581        writer: &mut Writer<W>,
582        content: &[InlineElement],
583    ) -> Result<()> {
584        for elem in content {
585            Self::write_inline_element(writer, elem)?;
586        }
587
588        Ok(())
589    }
590
591    /// Write an inline element
592    fn write_inline_element<W: std::io::Write>(
593        writer: &mut Writer<W>,
594        elem: &InlineElement,
595    ) -> Result<()> {
596        match elem {
597            InlineElement::Text(text) => {
598                writer.write_event(Event::Text(BytesText::new(text)))?;
599            }
600            InlineElement::Em(em) => Self::write_em(writer, em)?,
601            InlineElement::Bo(bo) => Self::write_bo(writer, bo)?,
602            InlineElement::Un(un) => Self::write_un(writer, un)?,
603            InlineElement::St(st) => Self::write_st(writer, st)?,
604            InlineElement::Snip(snip) => Self::write_snip(writer, snip)?,
605            InlineElement::Key(key) => Self::write_key(writer, key)?,
606            InlineElement::Rf(rf) => Self::write_rf(writer, rf)?,
607            InlineElement::Tg(tg) => Self::write_tg(writer, tg)?,
608            InlineElement::Lk(lk) => Self::write_lk(writer, lk)?,
609            InlineElement::Curr(curr) => Self::write_curr(writer, curr)?,
610            InlineElement::End(end) => Self::write_end(writer, end)?,
611        }
612
613        Ok(())
614    }
615
616    /// Write <em> element
617    fn write_em<W: std::io::Write>(writer: &mut Writer<W>, em: &Em) -> Result<()> {
618        let mut elem = BytesStart::new("em");
619
620        if let Some(em_type) = &em.em_type {
621            let type_str = match em_type {
622                EmphasisType::Stress => "stress",
623                EmphasisType::Contrast => "contrast",
624            };
625            elem.push_attribute(("type", type_str));
626        }
627
628        writer.write_event(Event::Start(elem))?;
629        Self::write_inline_content(writer, &em.content)?;
630        writer.write_event(Event::End(BytesEnd::new("em")))?;
631
632        Ok(())
633    }
634
635    /// Write <bo> element
636    fn write_bo<W: std::io::Write>(writer: &mut Writer<W>, bo: &Bo) -> Result<()> {
637        writer.write_event(Event::Start(BytesStart::new("bo")))?;
638        Self::write_inline_content(writer, &bo.content)?;
639        writer.write_event(Event::End(BytesEnd::new("bo")))?;
640        Ok(())
641    }
642
643    /// Write <un> element
644    fn write_un<W: std::io::Write>(writer: &mut Writer<W>, un: &Un) -> Result<()> {
645        writer.write_event(Event::Start(BytesStart::new("un")))?;
646        Self::write_inline_content(writer, &un.content)?;
647        writer.write_event(Event::End(BytesEnd::new("un")))?;
648        Ok(())
649    }
650
651    /// Write <st> element
652    fn write_st<W: std::io::Write>(writer: &mut Writer<W>, st: &St) -> Result<()> {
653        writer.write_event(Event::Start(BytesStart::new("st")))?;
654        Self::write_inline_content(writer, &st.content)?;
655        writer.write_event(Event::End(BytesEnd::new("st")))?;
656        Ok(())
657    }
658
659    /// Write <snip> element
660    fn write_snip<W: std::io::Write>(writer: &mut Writer<W>, snip: &Snip) -> Result<()> {
661        let mut elem = BytesStart::new("snip");
662
663        if let Some(char) = &snip.char {
664            elem.push_attribute(("char", char.as_str()));
665        }
666
667        writer.write_event(Event::Start(elem))?;
668        writer.write_event(Event::Text(BytesText::new(&snip.content)))?;
669        writer.write_event(Event::End(BytesEnd::new("snip")))?;
670
671        Ok(())
672    }
673
674    /// Write <key> element
675    fn write_key<W: std::io::Write>(writer: &mut Writer<W>, key: &Key) -> Result<()> {
676        writer.write_event(Event::Start(BytesStart::new("key")))?;
677        writer.write_event(Event::Text(BytesText::new(&key.content)))?;
678        writer.write_event(Event::End(BytesEnd::new("key")))?;
679        Ok(())
680    }
681
682    /// Write <rf> element (internal reference)
683    fn write_rf<W: std::io::Write>(writer: &mut Writer<W>, rf: &Rf) -> Result<()> {
684        let mut elem = BytesStart::new("rf");
685        elem.push_attribute(("ref", rf.reference.as_str()));
686
687        if let Some(role) = &rf.role {
688            elem.push_attribute(("role", role.as_str()));
689        }
690
691        if let Some(title) = &rf.title {
692            elem.push_attribute(("title", title.as_str()));
693        }
694
695        writer.write_event(Event::Start(elem))?;
696        writer.write_event(Event::Text(BytesText::new(&rf.content)))?;
697        writer.write_event(Event::End(BytesEnd::new("rf")))?;
698
699        Ok(())
700    }
701
702    /// Write <tg> element (topic tag)
703    fn write_tg<W: std::io::Write>(writer: &mut Writer<W>, tg: &Tg) -> Result<()> {
704        let mut elem = BytesStart::new("tg");
705        elem.push_attribute(("ref", tg.reference.as_str()));
706
707        if let Some(role) = &tg.role {
708            elem.push_attribute(("role", role.as_str()));
709        }
710
711        if let Some(title) = &tg.title {
712            elem.push_attribute(("title", title.as_str()));
713        }
714
715        writer.write_event(Event::Start(elem))?;
716        writer.write_event(Event::Text(BytesText::new(&tg.content)))?;
717        writer.write_event(Event::End(BytesEnd::new("tg")))?;
718
719        Ok(())
720    }
721
722    /// Write <lk> element (external link)
723    fn write_lk<W: std::io::Write>(writer: &mut Writer<W>, lk: &Lk) -> Result<()> {
724        let mut elem = BytesStart::new("lk");
725        elem.push_attribute(("ref", lk.reference.as_str()));
726
727        if let Some(role) = &lk.role {
728            elem.push_attribute(("role", role.as_str()));
729        }
730
731        if let Some(title) = &lk.title {
732            elem.push_attribute(("title", title.as_str()));
733        }
734
735        writer.write_event(Event::Start(elem))?;
736        writer.write_event(Event::Text(BytesText::new(&lk.content)))?;
737        writer.write_event(Event::End(BytesEnd::new("lk")))?;
738
739        Ok(())
740    }
741
742    /// Write <curr> element (currency)
743    fn write_curr<W: std::io::Write>(writer: &mut Writer<W>, curr: &Curr) -> Result<()> {
744        let mut elem = BytesStart::new("curr");
745        elem.push_attribute(("type", curr.currency_type.as_str()));
746
747        if let Some(format) = &curr.format {
748            let format_str = match format {
749                CurrencyFormat::Symbol => "symbol",
750                CurrencyFormat::Code => "code",
751                CurrencyFormat::Name => "name",
752            };
753            elem.push_attribute(("format", format_str));
754        }
755
756        writer.write_event(Event::Start(elem))?;
757        writer.write_event(Event::Text(BytesText::new(&curr.value)))?;
758        writer.write_event(Event::End(BytesEnd::new("curr")))?;
759
760        Ok(())
761    }
762
763    /// Write <end> element (self-closing)
764    fn write_end<W: std::io::Write>(writer: &mut Writer<W>, end: &End) -> Result<()> {
765        let mut elem = BytesStart::new("end");
766
767        if let Some(kind) = &end.kind {
768            let kind_str = match kind {
769                EndKind::Line => "line",
770                EndKind::Verse => "verse",
771                EndKind::Item => "item",
772            };
773            elem.push_attribute(("kind", kind_str));
774        }
775
776        writer.write_event(Event::Empty(elem))?;
777
778        Ok(())
779    }
780
781    /// Write <footer> element
782    fn write_footer<W: std::io::Write>(writer: &mut Writer<W>, footer: &Footer) -> Result<()> {
783        writer.write_event(Event::Start(BytesStart::new("footer")))?;
784
785        // Signatures (optional)
786        if let Some(signatures) = &footer.signatures {
787            Self::write_signatures(writer, signatures)?;
788        }
789
790        // Citations (optional)
791        if let Some(citations) = &footer.citations {
792            Self::write_citations(writer, citations)?;
793        }
794
795        // Annotations (optional)
796        if let Some(annotations) = &footer.annotations {
797            Self::write_annotations(writer, annotations)?;
798        }
799
800        writer.write_event(Event::End(BytesEnd::new("footer")))?;
801
802        Ok(())
803    }
804
805    /// Write <signatures> container
806    fn write_signatures<W: std::io::Write>(
807        writer: &mut Writer<W>,
808        signatures: &Signatures,
809    ) -> Result<()> {
810        writer.write_event(Event::Start(BytesStart::new("signatures")))?;
811
812        for signature in &signatures.signatures {
813            Self::write_signature(writer, signature)?;
814        }
815
816        writer.write_event(Event::End(BytesEnd::new("signatures")))?;
817
818        Ok(())
819    }
820
821    /// Write <signature> element
822    fn write_signature<W: std::io::Write>(
823        writer: &mut Writer<W>,
824        signature: &Signature,
825    ) -> Result<()> {
826        let mut elem = BytesStart::new("signature");
827        elem.push_attribute(("when", signature.when.as_str()));
828
829        if let Some(role) = &signature.role {
830            elem.push_attribute(("role", role.as_str()));
831        }
832
833        if let Some(reference) = &signature.reference {
834            elem.push_attribute(("ref", reference.as_str()));
835        }
836
837        writer.write_event(Event::Start(elem))?;
838        writer.write_event(Event::Text(BytesText::new(&signature.content)))?;
839        writer.write_event(Event::End(BytesEnd::new("signature")))?;
840
841        Ok(())
842    }
843
844    /// Write <citations> container
845    fn write_citations<W: std::io::Write>(
846        writer: &mut Writer<W>,
847        citations: &Citations,
848    ) -> Result<()> {
849        writer.write_event(Event::Start(BytesStart::new("citations")))?;
850
851        for citation in &citations.citations {
852            Self::write_citation(writer, citation)?;
853        }
854
855        writer.write_event(Event::End(BytesEnd::new("citations")))?;
856
857        Ok(())
858    }
859
860    /// Write <citation> element
861    fn write_citation<W: std::io::Write>(
862        writer: &mut Writer<W>,
863        citation: &Citation,
864    ) -> Result<()> {
865        let mut elem = BytesStart::new("citation");
866        elem.push_attribute(("ref", citation.reference.as_str()));
867
868        if let Some(citation_type) = &citation.citation_type {
869            elem.push_attribute(("type", citation_type.as_str()));
870        }
871
872        writer.write_event(Event::Start(elem))?;
873        Self::write_inline_content(writer, &citation.content)?;
874        writer.write_event(Event::End(BytesEnd::new("citation")))?;
875
876        Ok(())
877    }
878
879    /// Write <annotations> container
880    fn write_annotations<W: std::io::Write>(
881        writer: &mut Writer<W>,
882        annotations: &Annotations,
883    ) -> Result<()> {
884        writer.write_event(Event::Start(BytesStart::new("annotations")))?;
885
886        for note in &annotations.notes {
887            Self::write_note(writer, note)?;
888        }
889
890        writer.write_event(Event::End(BytesEnd::new("annotations")))?;
891
892        Ok(())
893    }
894
895    /// Write <note> element
896    fn write_note<W: std::io::Write>(writer: &mut Writer<W>, note: &Note) -> Result<()> {
897        let mut elem = BytesStart::new("note");
898
899        if let Some(id) = &note.id {
900            elem.push_attribute(("id", id.as_str()));
901        }
902
903        if let Some(note_type) = &note.note_type {
904            elem.push_attribute(("type", note_type.as_str()));
905        }
906
907        if let Some(reference) = &note.reference {
908            elem.push_attribute(("ref", reference.as_str()));
909        }
910
911        writer.write_event(Event::Start(elem))?;
912
913        match &note.content {
914            NoteContent::Inline(inlines) => {
915                Self::write_inline_content(writer, inlines)?;
916            }
917            NoteContent::Block(blocks) => {
918                for block in blocks {
919                    Self::write_block_element(writer, block)?;
920                }
921            }
922        }
923
924        writer.write_event(Event::End(BytesEnd::new("note")))?;
925
926        Ok(())
927    }
928
929    /// Write simple text element
930    fn write_text_element<W: std::io::Write>(
931        writer: &mut Writer<W>,
932        tag: &str,
933        text: &str,
934    ) -> Result<()> {
935        writer.write_event(Event::Start(BytesStart::new(tag)))?;
936        writer.write_event(Event::Text(BytesText::new(text)))?;
937        writer.write_event(Event::End(BytesEnd::new(tag)))?;
938        Ok(())
939    }
940}
941
942#[cfg(test)]
943mod tests {
944    use super::*;
945
946    #[test]
947    fn test_generate_minimal_document() {
948        let header = Header::new("Test Document".to_string());
949        let mut body = Body::new();
950        body.add_block(BlockElement::Paragraph(Paragraph::from_text(
951            "Hello, world!".to_string(),
952        )));
953
954        let doc = CmlDocument::new("core".to_string(), header)
955            .with_body(body)
956            .with_footer(Footer::empty());
957
958        let xml = CmlGenerator::generate(&doc).unwrap();
959
960        // Should NOT start with <?xml
961        assert!(!xml.starts_with("<?xml"));
962
963        // Should start with <cml
964        assert!(xml.starts_with("<cml"));
965
966        // Should contain required elements
967        assert!(xml.contains("<header>"));
968        assert!(xml.contains("<title>Test Document</title>"));
969        assert!(xml.contains("<body>"));
970        assert!(xml.contains("<paragraph>Hello, world!</paragraph>"));
971        assert!(xml.contains("<footer>"));
972    }
973
974    #[test]
975    fn test_generate_with_metadata() {
976        let mut header = Header::new("Test".to_string());
977        header.add_author(Author::new("John Doe".to_string()).with_role("editor".to_string()));
978        header.add_date(DateEntry::created("2025-12-22".to_string()));
979        header.add_identifier(Identifier::doi("10.1234/test".to_string()));
980        header.add_meta("status".to_string(), "draft".to_string());
981
982        let mut body = Body::new();
983        body.add_block(BlockElement::Paragraph(Paragraph::from_text(
984            "Content".to_string(),
985        )));
986
987        let doc = CmlDocument::new("core".to_string(), header)
988            .with_body(body)
989            .with_footer(Footer::empty());
990
991        let xml = CmlGenerator::generate(&doc).unwrap();
992
993        assert!(xml.contains("<author role=\"editor\">John Doe</author>"));
994        assert!(xml.contains("<date type=\"created\" when=\"2025-12-22\"/>"));
995        assert!(xml.contains("<identifier scheme=\"doi\">10.1234/test</identifier>"));
996        assert!(xml.contains("<meta name=\"status\" value=\"draft\"/>"));
997    }
998
999    #[test]
1000    fn test_generate_inline_elements() {
1001        let header = Header::new("Test".to_string());
1002
1003        let mut body = Body::new();
1004        body.add_block(BlockElement::Paragraph(Paragraph {
1005            id: None,
1006            paragraph_type: None,
1007            content: vec![
1008                InlineElement::Text("This is ".to_string()),
1009                InlineElement::Em(Em {
1010                    em_type: None,
1011                    content: vec![InlineElement::Text("emphasized".to_string())],
1012                }),
1013                InlineElement::Text(" and ".to_string()),
1014                InlineElement::Bo(Bo {
1015                    content: vec![InlineElement::Text("bold".to_string())],
1016                }),
1017                InlineElement::Text(" text.".to_string()),
1018            ],
1019        }));
1020
1021        let doc = CmlDocument::new("core".to_string(), header)
1022            .with_body(body)
1023            .with_footer(Footer::empty());
1024
1025        let xml = CmlGenerator::generate(&doc).unwrap();
1026
1027        assert!(xml.contains("<em>emphasized</em>"));
1028        assert!(xml.contains("<bo>bold</bo>"));
1029    }
1030
1031    #[test]
1032    fn test_generate_list() {
1033        let header = Header::new("Test".to_string());
1034
1035        let mut body = Body::new();
1036        body.add_block(BlockElement::List(List {
1037            id: None,
1038            list_type: Some(ListType::Ordered),
1039            style: Some(ListStyle::Numeric),
1040            items: vec![
1041                ListItem {
1042                    id: None,
1043                    content: ListItemContent::Inline(vec![InlineElement::Text(
1044                        "First".to_string(),
1045                    )]),
1046                },
1047                ListItem {
1048                    id: None,
1049                    content: ListItemContent::Inline(vec![InlineElement::Text(
1050                        "Second".to_string(),
1051                    )]),
1052                },
1053            ],
1054        }));
1055
1056        let doc = CmlDocument::new("core".to_string(), header)
1057            .with_body(body)
1058            .with_footer(Footer::empty());
1059
1060        let xml = CmlGenerator::generate(&doc).unwrap();
1061
1062        assert!(xml.contains("<list type=\"ordered\" style=\"numeric\">"));
1063        assert!(xml.contains("<item>First</item>"));
1064        assert!(xml.contains("<item>Second</item>"));
1065    }
1066
1067    #[test]
1068    fn test_generate_footer_with_signature() {
1069        let header = Header::new("Test".to_string());
1070
1071        let mut body = Body::new();
1072        body.add_block(BlockElement::Paragraph(Paragraph::from_text(
1073            "Content".to_string(),
1074        )));
1075
1076        let signature = Signature::new("2025-12-22T10:30:00Z".to_string(), "Jane Doe".to_string())
1077            .with_role("author".to_string());
1078
1079        let signatures = Signatures {
1080            signatures: vec![signature],
1081        };
1082
1083        let footer = Footer::empty().with_signatures(signatures);
1084
1085        let doc = CmlDocument::new("core".to_string(), header)
1086            .with_body(body)
1087            .with_footer(footer);
1088
1089        let xml = CmlGenerator::generate(&doc).unwrap();
1090
1091        assert!(xml.contains("<signatures>"));
1092        assert!(xml.contains(
1093            "<signature when=\"2025-12-22T10:30:00Z\" role=\"author\">Jane Doe</signature>"
1094        ));
1095    }
1096
1097    #[test]
1098    fn test_self_closing_elements() {
1099        let mut header = Header::new("Test".to_string());
1100        header.add_date(DateEntry::created("2025-12-22".to_string()));
1101        header.add_meta("key".to_string(), "value".to_string());
1102
1103        let mut body = Body::new();
1104        body.add_block(BlockElement::Break(Break {
1105            break_type: Some("scene".to_string()),
1106        }));
1107
1108        let doc = CmlDocument::new("core".to_string(), header)
1109            .with_body(body)
1110            .with_footer(Footer::empty());
1111
1112        let xml = CmlGenerator::generate(&doc).unwrap();
1113
1114        // Self-closing tags
1115        assert!(xml.contains("<date type=\"created\" when=\"2025-12-22\"/>"));
1116        assert!(xml.contains("<meta name=\"key\" value=\"value\"/>"));
1117        assert!(xml.contains("<break type=\"scene\"/>"));
1118    }
1119}