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