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>(
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 Self::write_header(writer, &document.header)?;
48
49 Self::write_body(writer, &document.body)?;
51
52 Self::write_footer(writer, &document.footer)?;
54
55 writer.write_event(Event::End(BytesEnd::new("cml")))?;
56
57 Ok(())
58 }
59
60 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 Self::write_text_element(writer, "title", &header.title)?;
66
67 for author in &header.authors {
69 Self::write_author(writer, author)?;
70 }
71
72 for date in &header.dates {
74 Self::write_date_entry(writer, date)?;
75 }
76
77 for identifier in &header.identifiers {
79 Self::write_identifier(writer, identifier)?;
80 }
81
82 if let Some(version) = &header.version {
84 Self::write_text_element(writer, "version", version)?;
85 }
86
87 if let Some(description) = &header.description {
89 Self::write_text_element(writer, "description", description)?;
90 }
91
92 if let Some(provenance) = &header.provenance {
94 Self::write_text_element(writer, "provenance", provenance)?;
95 }
96
97 if let Some(source) = &header.source {
99 Self::write_text_element(writer, "source", source)?;
100 }
101
102 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 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 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 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 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 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 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 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) = §ion.id {
212 elem.push_attribute(("id", id.as_str()));
213 }
214
215 if let Some(section_type) = §ion.section_type {
216 elem.push_attribute(("type", section_type.as_str()));
217 }
218
219 if let Some(reference) = §ion.reference {
220 elem.push_attribute(("ref", reference.as_str()));
221 }
222
223 writer.write_event(Event::Start(elem))?;
224
225 for block in §ion.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 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) = ¶.id {
242 elem.push_attribute(("id", id.as_str()));
243 }
244
245 if let Some(paragraph_type) = ¶.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, ¶.content)?;
251 writer.write_event(Event::End(BytesEnd::new("paragraph")))?;
252
253 Ok(())
254 }
255
256 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 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 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) = "e.id {
314 elem.push_attribute(("id", id.as_str()));
315 }
316
317 if let Some(reference) = "e.reference {
318 elem.push_attribute(("ref", reference.as_str()));
319 }
320
321 if let Some(source) = "e.source {
322 elem.push_attribute(("source", source.as_str()));
323 }
324
325 writer.write_event(Event::Start(elem))?;
326
327 for block in "e.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 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 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 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 if let Some(header) = &table.header {
418 Self::write_table_header(writer, header)?;
419 }
420
421 Self::write_table_body(writer, &table.body)?;
423
424 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 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 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 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 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 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 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 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 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 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 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 writer.write_event(Event::Start(elem))?;
601 writer.write_event(Event::End(BytesEnd::new("figure")))?;
602
603 Ok(())
604 }
605
606 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 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 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 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 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 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 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 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 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 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 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 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 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 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 if let Some(signatures) = &footer.signatures {
814 Self::write_signatures(writer, signatures)?;
815 }
816
817 if let Some(citations) = &footer.citations {
819 Self::write_citations(writer, citations)?;
820 }
821
822 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 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 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 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 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 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 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) = ¬e.id {
927 elem.push_attribute(("id", id.as_str()));
928 }
929
930 if let Some(note_type) = ¬e.note_type {
931 elem.push_attribute(("type", note_type.as_str()));
932 }
933
934 if let Some(reference) = ¬e.reference {
935 elem.push_attribute(("ref", reference.as_str()));
936 }
937
938 writer.write_event(Event::Start(elem))?;
939
940 match ¬e.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 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 assert!(!xml.starts_with("<?xml"));
989
990 assert!(xml.starts_with("<cml"));
992
993 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 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}