1use crate::types::*;
8use crate::Result;
9use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
10use quick_xml::Writer;
11use std::io::Cursor;
12
13pub struct CmlGenerator;
15
16impl CmlGenerator {
17 pub fn generate(document: &CmlDocument) -> Result<String> {
21 let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 2);
22
23 Self::write_cml(&mut writer, document)?;
25
26 let result = writer.into_inner().into_inner();
27 Ok(String::from_utf8(result)?)
28 }
29
30 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 Self::write_header(writer, &document.header)?;
45
46 Self::write_body(writer, &document.body)?;
48
49 Self::write_footer(writer, &document.footer)?;
51
52 writer.write_event(Event::End(BytesEnd::new("cml")))?;
53
54 Ok(())
55 }
56
57 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 Self::write_text_element(writer, "title", &header.title)?;
63
64 for author in &header.authors {
66 Self::write_author(writer, author)?;
67 }
68
69 for date in &header.dates {
71 Self::write_date_entry(writer, date)?;
72 }
73
74 for identifier in &header.identifiers {
76 Self::write_identifier(writer, identifier)?;
77 }
78
79 if let Some(version) = &header.version {
81 Self::write_text_element(writer, "version", version)?;
82 }
83
84 if let Some(description) = &header.description {
86 Self::write_text_element(writer, "description", description)?;
87 }
88
89 if let Some(provenance) = &header.provenance {
91 Self::write_text_element(writer, "provenance", provenance)?;
92 }
93
94 if let Some(source) = &header.source {
96 Self::write_text_element(writer, "source", source)?;
97 }
98
99 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 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 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 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 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 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 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 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) = §ion.id {
200 elem.push_attribute(("id", id.as_str()));
201 }
202
203 if let Some(section_type) = §ion.section_type {
204 elem.push_attribute(("type", section_type.as_str()));
205 }
206
207 if let Some(reference) = §ion.reference {
208 elem.push_attribute(("ref", reference.as_str()));
209 }
210
211 writer.write_event(Event::Start(elem))?;
212
213 for block in §ion.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 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) = ¶.id {
227 elem.push_attribute(("id", id.as_str()));
228 }
229
230 if let Some(paragraph_type) = ¶.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, ¶.content)?;
236 writer.write_event(Event::End(BytesEnd::new("paragraph")))?;
237
238 Ok(())
239 }
240
241 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 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 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) = "e.id {
296 elem.push_attribute(("id", id.as_str()));
297 }
298
299 if let Some(reference) = "e.reference {
300 elem.push_attribute(("ref", reference.as_str()));
301 }
302
303 if let Some(source) = "e.source {
304 elem.push_attribute(("source", source.as_str()));
305 }
306
307 writer.write_event(Event::Start(elem))?;
308
309 for block in "e.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 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 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 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 if let Some(header) = &table.header {
397 Self::write_table_header(writer, header)?;
398 }
399
400 Self::write_table_body(writer, &table.body)?;
402
403 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 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 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 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 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 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 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 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 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 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 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 writer.write_event(Event::Start(elem))?;
574 writer.write_event(Event::End(BytesEnd::new("figure")))?;
575
576 Ok(())
577 }
578
579 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 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 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 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 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 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 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 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 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 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 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 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 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 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 if let Some(signatures) = &footer.signatures {
787 Self::write_signatures(writer, signatures)?;
788 }
789
790 if let Some(citations) = &footer.citations {
792 Self::write_citations(writer, citations)?;
793 }
794
795 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 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 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 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 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 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 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) = ¬e.id {
900 elem.push_attribute(("id", id.as_str()));
901 }
902
903 if let Some(note_type) = ¬e.note_type {
904 elem.push_attribute(("type", note_type.as_str()));
905 }
906
907 if let Some(reference) = ¬e.reference {
908 elem.push_attribute(("ref", reference.as_str()));
909 }
910
911 writer.write_event(Event::Start(elem))?;
912
913 match ¬e.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 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 assert!(!xml.starts_with("<?xml"));
962
963 assert!(xml.starts_with("<cml"));
965
966 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 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}