1use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
20pub struct CmlDocument {
21 pub version: String,
23
24 pub encoding: String,
26
27 pub profile: String,
30
31 pub id: Option<String>,
33
34 pub header: Header,
36
37 pub body: Body,
39
40 pub footer: Footer,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50pub struct Header {
51 pub title: String,
53
54 pub authors: Vec<Author>,
56
57 pub dates: Vec<DateEntry>,
59
60 pub identifiers: Vec<Identifier>,
62
63 pub version: Option<String>,
65
66 pub description: Option<String>,
68
69 pub provenance: Option<String>,
71
72 pub source: Option<String>,
74
75 pub meta: Vec<MetaEntry>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81pub struct Author {
82 pub name: String,
84
85 pub role: Option<String>,
87
88 pub reference: Option<String>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94pub struct DateEntry {
95 pub date_type: String,
97
98 pub when: String,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
108pub struct Identifier {
109 pub scheme: String,
111
112 pub value: String,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
118pub struct MetaEntry {
119 pub name: String,
121
122 pub value: String,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
132pub struct Body {
133 pub blocks: Vec<BlockElement>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
139pub enum BlockElement {
140 Section(Section),
142
143 Paragraph(Paragraph),
145
146 Heading(Heading),
148
149 Aside(Aside),
151
152 Quote(Quote),
154
155 List(List),
157
158 Table(Table),
160
161 Code(Code),
163
164 Break(Break),
166
167 Figure(Figure),
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
173pub struct Section {
174 pub id: Option<String>,
176
177 pub section_type: Option<String>,
179
180 pub reference: Option<String>,
182
183 pub content: Vec<BlockElement>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
189pub struct Paragraph {
190 pub id: Option<String>,
192
193 pub paragraph_type: Option<String>,
195
196 pub content: Vec<InlineElement>,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
202pub struct Heading {
203 pub id: Option<String>,
205
206 pub heading_type: Option<String>,
208
209 pub size: u8,
211
212 pub content: Vec<InlineElement>,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218pub struct Aside {
219 pub id: Option<String>,
221
222 pub aside_type: Option<String>,
224
225 pub side: Side,
227
228 pub content: Vec<BlockElement>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
234pub enum Side {
235 Left,
236 Right,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
241pub struct Quote {
242 pub id: Option<String>,
244
245 pub reference: Option<String>,
247
248 pub source: Option<String>,
250
251 pub content: Vec<BlockElement>,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
257pub struct List {
258 pub id: Option<String>,
260
261 pub list_type: Option<ListType>,
263
264 pub style: Option<ListStyle>,
266
267 pub items: Vec<ListItem>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
273pub enum ListType {
274 Ordered,
275 Unordered,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
280pub enum ListStyle {
281 Numeric,
283
284 Roman,
286
287 Alpha,
289
290 Symbolic,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
296pub struct ListItem {
297 pub id: Option<String>,
299
300 pub content: ListItemContent,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
306pub enum ListItemContent {
307 Inline(Vec<InlineElement>),
309
310 Block(Vec<BlockElement>),
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
316pub struct Table {
317 pub id: Option<String>,
319
320 pub table_type: Option<String>,
322
323 pub header: Option<TableHeader>,
325
326 pub body: TableBody,
328
329 pub footer: Option<TableFooter>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
335pub struct TableHeader {
336 pub rows: Vec<TableRow>,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
342pub struct TableBody {
343 pub rows: Vec<TableRow>,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
349pub struct TableFooter {
350 pub caption: Caption,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
356pub struct TableRow {
357 pub columns: Vec<TableColumn>,
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
363pub struct TableColumn {
364 pub sort: Option<SortOrder>,
366
367 pub cell: TableCell,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
373pub enum SortOrder {
374 Asc,
375 Desc,
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
380pub struct TableCell {
381 pub colspan: Option<u32>,
383
384 pub rowspan: Option<u32>,
386
387 pub content: Vec<InlineElement>,
389}
390
391#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
393pub struct Caption {
394 pub content: Vec<InlineElement>,
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
400pub struct Code {
401 pub id: Option<String>,
403
404 pub lang: Option<String>,
406
407 pub copyable: Option<bool>,
409
410 pub content: String,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
416pub struct Break {
417 pub break_type: Option<String>,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
423pub struct Figure {
424 pub id: Option<String>,
426
427 pub figure_type: Option<String>,
429
430 pub reference: Option<String>,
432 }
435
436#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
442pub enum InlineElement {
443 Text(String),
445
446 Em(Em),
448
449 Bo(Bo),
451
452 Un(Un),
454
455 St(St),
457
458 Snip(Snip),
460
461 Key(Key),
463
464 Rf(Rf),
466
467 Tg(Tg),
469
470 Lk(Lk),
472
473 Curr(Curr),
475
476 End(End),
478}
479
480#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
482pub struct Em {
483 pub em_type: Option<EmphasisType>,
485
486 pub content: Vec<InlineElement>,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
492pub enum EmphasisType {
493 Stress,
494 Contrast,
495}
496
497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
499pub struct Bo {
500 pub content: Vec<InlineElement>,
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
506pub struct Un {
507 pub content: Vec<InlineElement>,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
513pub struct St {
514 pub content: Vec<InlineElement>,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
520pub struct Snip {
521 pub char: Option<String>,
523
524 pub content: String,
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
530pub struct Key {
531 pub content: String,
533}
534
535#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
537pub struct Rf {
538 pub reference: String,
541
542 pub role: Option<String>,
544
545 pub title: Option<String>,
547
548 pub content: String,
550}
551
552#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
554pub struct Tg {
555 pub reference: String,
557
558 pub role: Option<String>,
560
561 pub title: Option<String>,
563
564 pub content: String,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
570pub struct Lk {
571 pub reference: String,
573
574 pub role: Option<String>,
576
577 pub title: Option<String>,
579
580 pub content: String,
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
586pub struct Curr {
587 pub currency_type: String,
589
590 pub format: Option<CurrencyFormat>,
592
593 pub value: String,
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
599pub enum CurrencyFormat {
600 Symbol,
602
603 Code,
605
606 Name,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
612pub struct End {
613 pub kind: Option<EndKind>,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
619pub enum EndKind {
620 Line,
621 Verse,
622 Item,
623}
624
625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
631pub struct Footer {
632 pub signatures: Option<Signatures>,
634
635 pub citations: Option<Citations>,
637
638 pub annotations: Option<Annotations>,
640}
641
642#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
644pub struct Signatures {
645 pub signatures: Vec<Signature>,
647}
648
649#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
651pub struct Signature {
652 pub when: String,
654
655 pub role: Option<String>,
657
658 pub reference: Option<String>,
660
661 pub content: String,
663}
664
665#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
667pub struct Citations {
668 pub citations: Vec<Citation>,
670}
671
672#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
674pub struct Citation {
675 pub reference: String,
677
678 pub citation_type: Option<String>,
680
681 pub content: Vec<InlineElement>,
683}
684
685#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
687pub struct Annotations {
688 pub notes: Vec<Note>,
690}
691
692#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
694pub struct Note {
695 pub id: Option<String>,
697
698 pub note_type: Option<String>,
700
701 pub reference: Option<String>,
703
704 pub content: NoteContent,
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
710pub enum NoteContent {
711 Inline(Vec<InlineElement>),
713
714 Block(Vec<BlockElement>),
716}
717
718impl CmlDocument {
723 pub fn new(profile: String, header: Header) -> Self {
725 Self {
726 version: "0.2".to_string(),
727 encoding: "utf-8".to_string(),
728 profile,
729 id: None,
730 header,
731 body: Body { blocks: Vec::new() },
732 footer: Footer::empty(),
733 }
734 }
735
736 pub fn with_id(mut self, id: String) -> Self {
738 self.id = Some(id);
739 self
740 }
741
742 pub fn with_body(mut self, body: Body) -> Self {
744 self.body = body;
745 self
746 }
747
748 pub fn with_footer(mut self, footer: Footer) -> Self {
750 self.footer = footer;
751 self
752 }
753}
754
755impl Header {
756 pub fn new(title: String) -> Self {
758 Self {
759 title,
760 authors: Vec::new(),
761 dates: Vec::new(),
762 identifiers: Vec::new(),
763 version: None,
764 description: None,
765 provenance: None,
766 source: None,
767 meta: Vec::new(),
768 }
769 }
770
771 pub fn add_author(&mut self, author: Author) {
773 self.authors.push(author);
774 }
775
776 pub fn add_date(&mut self, date: DateEntry) {
778 self.dates.push(date);
779 }
780
781 pub fn add_identifier(&mut self, identifier: Identifier) {
783 self.identifiers.push(identifier);
784 }
785
786 pub fn with_version(mut self, version: String) -> Self {
788 self.version = Some(version);
789 self
790 }
791
792 pub fn with_description(mut self, description: String) -> Self {
794 self.description = Some(description);
795 self
796 }
797
798 pub fn with_provenance(mut self, provenance: String) -> Self {
800 self.provenance = Some(provenance);
801 self
802 }
803
804 pub fn with_source(mut self, source: String) -> Self {
806 self.source = Some(source);
807 self
808 }
809
810 pub fn add_meta(&mut self, name: String, value: String) {
812 self.meta.push(MetaEntry { name, value });
813 }
814}
815
816impl Author {
817 pub fn new(name: String) -> Self {
819 Self {
820 name,
821 role: None,
822 reference: None,
823 }
824 }
825
826 pub fn with_role(mut self, role: String) -> Self {
828 self.role = Some(role);
829 self
830 }
831
832 pub fn with_reference(mut self, reference: String) -> Self {
834 self.reference = Some(reference);
835 self
836 }
837}
838
839impl DateEntry {
840 pub fn new(date_type: String, when: String) -> Self {
842 Self { date_type, when }
843 }
844
845 pub fn created(when: String) -> Self {
847 Self::new("created".to_string(), when)
848 }
849
850 pub fn modified(when: String) -> Self {
852 Self::new("modified".to_string(), when)
853 }
854
855 pub fn published(when: String) -> Self {
857 Self::new("published".to_string(), when)
858 }
859}
860
861impl Identifier {
862 pub fn new(scheme: String, value: String) -> Self {
864 Self { scheme, value }
865 }
866
867 pub fn continuity(value: String) -> Self {
869 Self::new("continuity".to_string(), value)
870 }
871
872 pub fn doi(value: String) -> Self {
874 Self::new("doi".to_string(), value)
875 }
876}
877
878impl Body {
879 pub fn new() -> Self {
881 Self { blocks: Vec::new() }
882 }
883
884 pub fn add_block(&mut self, block: BlockElement) {
886 self.blocks.push(block);
887 }
888}
889
890impl Default for Body {
891 fn default() -> Self {
892 Self::new()
893 }
894}
895
896impl Footer {
897 pub fn empty() -> Self {
899 Self {
900 signatures: None,
901 citations: None,
902 annotations: None,
903 }
904 }
905
906 pub fn with_signatures(mut self, signatures: Signatures) -> Self {
908 self.signatures = Some(signatures);
909 self
910 }
911
912 pub fn with_citations(mut self, citations: Citations) -> Self {
914 self.citations = Some(citations);
915 self
916 }
917
918 pub fn with_annotations(mut self, annotations: Annotations) -> Self {
920 self.annotations = Some(annotations);
921 self
922 }
923}
924
925impl Signature {
926 pub fn new(when: String, content: String) -> Self {
928 Self {
929 when,
930 role: None,
931 reference: None,
932 content,
933 }
934 }
935
936 pub fn with_role(mut self, role: String) -> Self {
938 self.role = Some(role);
939 self
940 }
941
942 pub fn with_reference(mut self, reference: String) -> Self {
944 self.reference = Some(reference);
945 self
946 }
947}
948
949impl Paragraph {
950 pub fn new(content: Vec<InlineElement>) -> Self {
952 Self {
953 id: None,
954 paragraph_type: None,
955 content,
956 }
957 }
958
959 pub fn from_text(text: String) -> Self {
961 Self {
962 id: None,
963 paragraph_type: None,
964 content: vec![InlineElement::Text(text)],
965 }
966 }
967}
968
969impl Heading {
970 pub fn new(size: u8, content: Vec<InlineElement>) -> Self {
972 Self {
973 id: None,
974 heading_type: None,
975 size,
976 content,
977 }
978 }
979
980 pub fn from_text(size: u8, text: String) -> Self {
982 Self {
983 id: None,
984 heading_type: None,
985 size,
986 content: vec![InlineElement::Text(text)],
987 }
988 }
989}
990
991#[cfg(test)]
992mod tests {
993 use super::*;
994
995 #[test]
996 fn test_minimal_document() {
997 let header = Header::new("Test Document".to_string());
998 let doc = CmlDocument::new("core".to_string(), header);
999
1000 assert_eq!(doc.version, "0.2");
1001 assert_eq!(doc.encoding, "utf-8");
1002 assert_eq!(doc.profile, "core");
1003 assert_eq!(doc.header.title, "Test Document");
1004 }
1005
1006 #[test]
1007 fn test_header_with_metadata() {
1008 let mut header = Header::new("Test".to_string());
1009 header.add_author(Author::new("John Doe".to_string()));
1010 header.add_date(DateEntry::created("2025-12-22".to_string()));
1011 header.add_identifier(Identifier::continuity("test-doc-123".to_string()));
1012 header.add_meta("status".to_string(), "draft".to_string());
1013
1014 assert_eq!(header.authors.len(), 1);
1015 assert_eq!(header.dates.len(), 1);
1016 assert_eq!(header.identifiers.len(), 1);
1017 assert_eq!(header.meta.len(), 1);
1018 }
1019
1020 #[test]
1021 fn test_body_with_blocks() {
1022 let mut body = Body::new();
1023 body.add_block(BlockElement::Paragraph(Paragraph::from_text(
1024 "Hello, world!".to_string(),
1025 )));
1026 body.add_block(BlockElement::Heading(Heading::from_text(
1027 1,
1028 "Title".to_string(),
1029 )));
1030
1031 assert_eq!(body.blocks.len(), 2);
1032 }
1033
1034 #[test]
1035 fn test_footer_with_signatures() {
1036 let signature = Signature::new("2025-12-22T10:30:00Z".to_string(), "John Doe".to_string())
1037 .with_role("author".to_string());
1038
1039 let signatures = Signatures {
1040 signatures: vec![signature],
1041 };
1042
1043 let footer = Footer::empty().with_signatures(signatures);
1044
1045 assert!(footer.signatures.is_some());
1046 assert_eq!(footer.signatures.unwrap().signatures.len(), 1);
1047 }
1048
1049 #[test]
1050 fn test_inline_elements() {
1051 let inline = vec![
1052 InlineElement::Text("This is ".to_string()),
1053 InlineElement::Em(Em {
1054 em_type: None,
1055 content: vec![InlineElement::Text("emphasized".to_string())],
1056 }),
1057 InlineElement::Text(" text.".to_string()),
1058 ];
1059
1060 assert_eq!(inline.len(), 3);
1061 }
1062
1063 #[test]
1064 fn test_list_structure() {
1065 let list = List {
1066 id: None,
1067 list_type: Some(ListType::Ordered),
1068 style: Some(ListStyle::Numeric),
1069 items: vec![
1070 ListItem {
1071 id: None,
1072 content: ListItemContent::Inline(vec![InlineElement::Text(
1073 "First".to_string(),
1074 )]),
1075 },
1076 ListItem {
1077 id: None,
1078 content: ListItemContent::Inline(vec![InlineElement::Text(
1079 "Second".to_string(),
1080 )]),
1081 },
1082 ],
1083 };
1084
1085 assert_eq!(list.items.len(), 2);
1086 }
1087
1088 #[test]
1089 fn test_table_structure() {
1090 let table = Table {
1091 id: None,
1092 table_type: None,
1093 header: None,
1094 body: TableBody {
1095 rows: vec![TableRow {
1096 columns: vec![TableColumn {
1097 sort: None,
1098 cell: TableCell {
1099 colspan: None,
1100 rowspan: None,
1101 content: vec![InlineElement::Text("Cell".to_string())],
1102 },
1103 }],
1104 }],
1105 },
1106 footer: None,
1107 };
1108
1109 assert_eq!(table.body.rows.len(), 1);
1110 }
1111}