Skip to main content

cml_rs/
types.rs

1//! CML v0.2 types and structures
2//!
3//! This module provides types for CML v0.2 with the structure:
4//! ```xml
5//! <cml profile="domain:profile" version="0.2" encoding="utf-8">
6//!   <header>...</header>
7//!   <body>...</body>
8//!   <footer>...</footer>
9//! </cml>
10//! ```
11
12use serde::{Deserialize, Serialize};
13
14// =============================================================================
15// CML v0.2 Root Document
16// =============================================================================
17
18/// A complete CML v0.2 document
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
20pub struct CmlDocument {
21    /// CML version (always "0.2")
22    pub version: String,
23
24    /// Document encoding (always "utf-8")
25    pub encoding: String,
26
27    /// Profile identifier (e.g., "law:constitution", "code:api", "edu:textbook")
28    /// Format: "domain:profile" or "domain:profile:version"
29    pub profile: String,
30
31    /// Optional document ID
32    pub id: Option<String>,
33
34    /// Document header (metadata) - REQUIRED
35    pub header: Header,
36
37    /// Document body (content) - REQUIRED
38    pub body: Body,
39
40    /// Document footer (signatures, citations, annotations) - REQUIRED (may be empty)
41    pub footer: Footer,
42}
43
44// =============================================================================
45// Header Elements
46// =============================================================================
47
48/// Document header containing metadata
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50pub struct Header {
51    /// Document title (REQUIRED)
52    pub title: String,
53
54    /// Document authors (0 or more)
55    pub authors: Vec<Author>,
56
57    /// Document dates (created, modified, published, etc.) (0 or more)
58    pub dates: Vec<DateEntry>,
59
60    /// Document identifiers (0 or more)
61    pub identifiers: Vec<Identifier>,
62
63    /// Document version (0 or 1)
64    pub version: Option<String>,
65
66    /// Document description (0 or 1)
67    pub description: Option<String>,
68
69    /// Provenance information (0 or 1)
70    pub provenance: Option<String>,
71
72    /// Source information (0 or 1)
73    pub source: Option<String>,
74
75    /// Custom metadata entries (0 or more)
76    pub meta: Vec<MetaEntry>,
77}
78
79/// Author information
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81pub struct Author {
82    /// Author name
83    pub name: String,
84
85    /// Author role (e.g., "editor", "contributor", "author")
86    pub role: Option<String>,
87
88    /// Reference to author entity
89    pub reference: Option<String>,
90}
91
92/// Date entry with type and ISO 8601 timestamp
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94pub struct DateEntry {
95    /// Date type (e.g., "created", "modified", "published", "ratified")
96    pub date_type: String,
97
98    /// ISO 8601 date or datetime
99    /// Formats:
100    /// - Date only: YYYY-MM-DD
101    /// - DateTime: YYYY-MM-DDTHH:MM:SS
102    /// - DateTime with timezone: YYYY-MM-DDTHH:MM:SS±HH:MM or Z
103    pub when: String,
104}
105
106/// Document identifier
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
108pub struct Identifier {
109    /// Identifier scheme (e.g., "continuity", "doi", "isbn")
110    pub scheme: String,
111
112    /// Identifier value
113    pub value: String,
114}
115
116/// Metadata key-value entry
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
118pub struct MetaEntry {
119    /// Metadata field name
120    pub name: String,
121
122    /// Metadata field value
123    pub value: String,
124}
125
126// =============================================================================
127// Body Elements (Block Structure)
128// =============================================================================
129
130/// Document body containing block elements
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
132pub struct Body {
133    /// Block elements (at least one REQUIRED)
134    pub blocks: Vec<BlockElement>,
135}
136
137/// Block-level elements
138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
139pub enum BlockElement {
140    /// Hierarchical content container
141    Section(Section),
142
143    /// Prose text unit
144    Paragraph(Paragraph),
145
146    /// Section title or label
147    Heading(Heading),
148
149    /// Tangential or supplementary content
150    Aside(Aside),
151
152    /// Block quotation from external source
153    Quote(Quote),
154
155    /// Ordered or unordered item collection
156    List(List),
157
158    /// Tabular data presentation
159    Table(Table),
160
161    /// Preformatted code block
162    Code(Code),
163
164    /// Thematic break or visual separator
165    Break(Break),
166
167    /// Self-contained illustration (RESERVED for v0.3)
168    Figure(Figure),
169}
170
171/// Section - hierarchical content container
172#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
173pub struct Section {
174    /// Unique identifier
175    pub id: Option<String>,
176
177    /// Semantic classification (profile-defined)
178    pub section_type: Option<String>,
179
180    /// Reference to related content
181    pub reference: Option<String>,
182
183    /// Nested block elements
184    pub content: Vec<BlockElement>,
185}
186
187/// Paragraph - prose text unit
188#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
189pub struct Paragraph {
190    /// Unique identifier
191    pub id: Option<String>,
192
193    /// Semantic classification
194    pub paragraph_type: Option<String>,
195
196    /// Inline elements and text
197    pub content: Vec<InlineElement>,
198}
199
200/// Heading - section title with size
201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
202pub struct Heading {
203    /// Unique identifier
204    pub id: Option<String>,
205
206    /// Semantic classification (e.g., "subtitle")
207    pub heading_type: Option<String>,
208
209    /// Heading level (REQUIRED, profile defines maximum)
210    pub size: u8,
211
212    /// Inline elements and text
213    pub content: Vec<InlineElement>,
214}
215
216/// Aside - supplementary content
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218pub struct Aside {
219    /// Unique identifier
220    pub id: Option<String>,
221
222    /// Semantic classification (e.g., "note", "warning", "example")
223    pub aside_type: Option<String>,
224
225    /// Positioning hint (REQUIRED)
226    pub side: Side,
227
228    /// Block elements
229    pub content: Vec<BlockElement>,
230}
231
232/// Positioning for aside elements
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
234pub enum Side {
235    Left,
236    Right,
237}
238
239/// Quote - block quotation
240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
241pub struct Quote {
242    /// Unique identifier
243    pub id: Option<String>,
244
245    /// Citation reference
246    pub reference: Option<String>,
247
248    /// Attribution text
249    pub source: Option<String>,
250
251    /// Block elements (typically paragraphs)
252    pub content: Vec<BlockElement>,
253}
254
255/// List - ordered or unordered collection
256#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
257pub struct List {
258    /// Unique identifier
259    pub id: Option<String>,
260
261    /// List type (default: unordered)
262    pub list_type: Option<ListType>,
263
264    /// Numbering style
265    pub style: Option<ListStyle>,
266
267    /// List items (at least one REQUIRED)
268    pub items: Vec<ListItem>,
269}
270
271/// List type
272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
273pub enum ListType {
274    Ordered,
275    Unordered,
276}
277
278/// List numbering style
279#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
280pub enum ListStyle {
281    /// 1, 2, 3...
282    Numeric,
283
284    /// I, II, III...
285    Roman,
286
287    /// a, b, c...
288    Alpha,
289
290    /// •, *, etc.
291    Symbolic,
292}
293
294/// List item
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
296pub struct ListItem {
297    /// Unique identifier
298    pub id: Option<String>,
299
300    /// Item content (inline or block)
301    pub content: ListItemContent,
302}
303
304/// List item content (inline or block)
305#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
306pub enum ListItemContent {
307    /// Simple text content
308    Inline(Vec<InlineElement>),
309
310    /// Complex nested content
311    Block(Vec<BlockElement>),
312}
313
314/// Table - tabular data
315#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
316pub struct Table {
317    /// Unique identifier
318    pub id: Option<String>,
319
320    /// Table classification
321    pub table_type: Option<String>,
322
323    /// Table header rows (optional)
324    pub header: Option<TableHeader>,
325
326    /// Table data rows (REQUIRED)
327    pub body: TableBody,
328
329    /// Table footer with caption (optional)
330    pub footer: Option<TableFooter>,
331}
332
333/// Table header
334#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
335pub struct TableHeader {
336    /// Header rows
337    pub rows: Vec<TableRow>,
338}
339
340/// Table body
341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
342pub struct TableBody {
343    /// Data rows
344    pub rows: Vec<TableRow>,
345}
346
347/// Table footer
348#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
349pub struct TableFooter {
350    /// Caption text
351    pub caption: Caption,
352}
353
354/// Table row
355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
356pub struct TableRow {
357    /// Columns in this row
358    pub columns: Vec<TableColumn>,
359}
360
361/// Table column
362#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
363pub struct TableColumn {
364    /// Sort order (header context only)
365    pub sort: Option<SortOrder>,
366
367    /// Cell content
368    pub cell: TableCell,
369}
370
371/// Sort order for table headers
372#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
373pub enum SortOrder {
374    Asc,
375    Desc,
376}
377
378/// Table cell
379#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
380pub struct TableCell {
381    /// Number of columns to span
382    pub colspan: Option<u32>,
383
384    /// Number of rows to span
385    pub rowspan: Option<u32>,
386
387    /// Inline content
388    pub content: Vec<InlineElement>,
389}
390
391/// Table caption
392#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
393pub struct Caption {
394    /// Inline content
395    pub content: Vec<InlineElement>,
396}
397
398/// Code - preformatted code block
399#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
400pub struct Code {
401    /// Unique identifier
402    pub id: Option<String>,
403
404    /// Programming language identifier
405    pub lang: Option<String>,
406
407    /// Enable copy functionality (default: true)
408    pub copyable: Option<bool>,
409
410    /// Preformatted text (whitespace preserved)
411    pub content: String,
412}
413
414/// Break - thematic separator
415#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
416pub struct Break {
417    /// Break classification (e.g., "scene", "section")
418    pub break_type: Option<String>,
419}
420
421/// Figure - reserved for v0.3
422#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
423pub struct Figure {
424    /// Unique identifier
425    pub id: Option<String>,
426
427    /// Figure classification
428    pub figure_type: Option<String>,
429
430    /// Reference to external resource
431    pub reference: Option<String>,
432    // Note: Content model to be defined in v0.3
433    // Processors SHOULD emit warning when encountering this in v0.2
434}
435
436// =============================================================================
437// Inline Elements
438// =============================================================================
439
440/// Inline-level elements and text
441#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
442pub enum InlineElement {
443    /// Plain text
444    Text(String),
445
446    /// Emphasis (italic)
447    Em(Em),
448
449    /// Bold/strong
450    Bo(Bo),
451
452    /// Underline
453    Un(Un),
454
455    /// Strikethrough
456    St(St),
457
458    /// Inline code snippet
459    Snip(Snip),
460
461    /// Keyboard input notation
462    Key(Key),
463
464    /// Internal reference (pathless identifier)
465    Rf(Rf),
466
467    /// Topic tag
468    Tg(Tg),
469
470    /// External link
471    Lk(Lk),
472
473    /// Currency value
474    Curr(Curr),
475
476    /// Inline break
477    End(End),
478}
479
480/// Emphasis
481#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
482pub struct Em {
483    /// Emphasis type
484    pub em_type: Option<EmphasisType>,
485
486    /// Nested inline elements
487    pub content: Vec<InlineElement>,
488}
489
490/// Emphasis type
491#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
492pub enum EmphasisType {
493    Stress,
494    Contrast,
495}
496
497/// Bold/strong
498#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
499pub struct Bo {
500    /// Nested inline elements
501    pub content: Vec<InlineElement>,
502}
503
504/// Underline
505#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
506pub struct Un {
507    /// Nested inline elements
508    pub content: Vec<InlineElement>,
509}
510
511/// Strikethrough
512#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
513pub struct St {
514    /// Nested inline elements
515    pub content: Vec<InlineElement>,
516}
517
518/// Inline code snippet
519#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
520pub struct Snip {
521    /// Prompt character (e.g., "$", ">")
522    pub char: Option<String>,
523
524    /// Text content (NO NESTING)
525    pub content: String,
526}
527
528/// Keyboard input
529#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
530pub struct Key {
531    /// Text content (NO NESTING)
532    pub content: String,
533}
534
535/// Internal reference (pathless identifier)
536#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
537pub struct Rf {
538    /// Reference identifier (e.g., "president:47", "article:1")
539    /// Format: namespace:identifier
540    pub reference: String,
541
542    /// Reference role or relationship type
543    pub role: Option<String>,
544
545    /// Hover text or description
546    pub title: Option<String>,
547
548    /// Link text (plain text only)
549    pub content: String,
550}
551
552/// Topic tag
553#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
554pub struct Tg {
555    /// Topic identifier (e.g., "constitution", "civil-rights")
556    pub reference: String,
557
558    /// Tag relationship type
559    pub role: Option<String>,
560
561    /// Hover text or description
562    pub title: Option<String>,
563
564    /// Link text (plain text only)
565    pub content: String,
566}
567
568/// External link
569#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
570pub struct Lk {
571    /// Absolute URI (RFC 3986)
572    pub reference: String,
573
574    /// Link relationship type
575    pub role: Option<String>,
576
577    /// Hover text or description
578    pub title: Option<String>,
579
580    /// Link text (plain text only)
581    pub content: String,
582}
583
584/// Currency value (active document element)
585#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
586pub struct Curr {
587    /// ISO 4217 currency code (e.g., "USD", "EUR", "GBP")
588    pub currency_type: String,
589
590    /// Display format hint
591    pub format: Option<CurrencyFormat>,
592
593    /// Numeric value (no currency symbols)
594    pub value: String,
595}
596
597/// Currency display format
598#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
599pub enum CurrencyFormat {
600    /// Show currency symbol ($, €, £)
601    Symbol,
602
603    /// Show currency code (USD, EUR, GBP)
604    Code,
605
606    /// Show currency name (US dollars, euros)
607    Name,
608}
609
610/// Inline break
611#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
612pub struct End {
613    /// Break type
614    pub kind: Option<EndKind>,
615}
616
617/// Inline break kind
618#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
619pub enum EndKind {
620    Line,
621    Verse,
622    Item,
623}
624
625// =============================================================================
626// Footer Elements
627// =============================================================================
628
629/// Document footer
630#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
631pub struct Footer {
632    /// Digital signatures container
633    pub signatures: Option<Signatures>,
634
635    /// Bibliographic references container
636    pub citations: Option<Citations>,
637
638    /// Notes and commentary container
639    pub annotations: Option<Annotations>,
640}
641
642/// Signatures container
643#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
644pub struct Signatures {
645    /// Signature entries
646    pub signatures: Vec<Signature>,
647}
648
649/// Individual signature
650#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
651pub struct Signature {
652    /// ISO 8601 date or datetime (REQUIRED)
653    pub when: String,
654
655    /// Signatory role (e.g., "author", "witness", "editor")
656    pub role: Option<String>,
657
658    /// Reference to person entity
659    pub reference: Option<String>,
660
661    /// Signatory name or inline text
662    pub content: String,
663}
664
665/// Citations container
666#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
667pub struct Citations {
668    /// Citation entries
669    pub citations: Vec<Citation>,
670}
671
672/// Bibliographic citation
673#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
674pub struct Citation {
675    /// Citation identifier (REQUIRED, referenced by rf/tg/lk)
676    pub reference: String,
677
678    /// Citation type (e.g., "book", "article", "website")
679    pub citation_type: Option<String>,
680
681    /// Citation content (inline elements)
682    pub content: Vec<InlineElement>,
683}
684
685/// Annotations container
686#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
687pub struct Annotations {
688    /// Annotation entries
689    pub notes: Vec<Note>,
690}
691
692/// Annotation or note
693#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
694pub struct Note {
695    /// Unique identifier
696    pub id: Option<String>,
697
698    /// Note classification (e.g., "footnote", "endnote", "margin")
699    pub note_type: Option<String>,
700
701    /// Reference to annotated element
702    pub reference: Option<String>,
703
704    /// Note content (block or inline)
705    pub content: NoteContent,
706}
707
708/// Note content (block or inline)
709#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
710pub enum NoteContent {
711    /// Inline content
712    Inline(Vec<InlineElement>),
713
714    /// Block content
715    Block(Vec<BlockElement>),
716}
717
718// =============================================================================
719// Constructors and Builders
720// =============================================================================
721
722impl CmlDocument {
723    /// Create a new CML v0.2 document
724    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    /// Set document ID
737    pub fn with_id(mut self, id: String) -> Self {
738        self.id = Some(id);
739        self
740    }
741
742    /// Set document body
743    pub fn with_body(mut self, body: Body) -> Self {
744        self.body = body;
745        self
746    }
747
748    /// Set document footer
749    pub fn with_footer(mut self, footer: Footer) -> Self {
750        self.footer = footer;
751        self
752    }
753}
754
755impl Header {
756    /// Create a new header with minimal required fields
757    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    /// Add an author
772    pub fn add_author(&mut self, author: Author) {
773        self.authors.push(author);
774    }
775
776    /// Add a date entry
777    pub fn add_date(&mut self, date: DateEntry) {
778        self.dates.push(date);
779    }
780
781    /// Add an identifier
782    pub fn add_identifier(&mut self, identifier: Identifier) {
783        self.identifiers.push(identifier);
784    }
785
786    /// Set version
787    pub fn with_version(mut self, version: String) -> Self {
788        self.version = Some(version);
789        self
790    }
791
792    /// Set description
793    pub fn with_description(mut self, description: String) -> Self {
794        self.description = Some(description);
795        self
796    }
797
798    /// Set provenance
799    pub fn with_provenance(mut self, provenance: String) -> Self {
800        self.provenance = Some(provenance);
801        self
802    }
803
804    /// Set source
805    pub fn with_source(mut self, source: String) -> Self {
806        self.source = Some(source);
807        self
808    }
809
810    /// Add metadata entry
811    pub fn add_meta(&mut self, name: String, value: String) {
812        self.meta.push(MetaEntry { name, value });
813    }
814}
815
816impl Author {
817    /// Create a new author
818    pub fn new(name: String) -> Self {
819        Self {
820            name,
821            role: None,
822            reference: None,
823        }
824    }
825
826    /// Set role
827    pub fn with_role(mut self, role: String) -> Self {
828        self.role = Some(role);
829        self
830    }
831
832    /// Set reference
833    pub fn with_reference(mut self, reference: String) -> Self {
834        self.reference = Some(reference);
835        self
836    }
837}
838
839impl DateEntry {
840    /// Create a new date entry
841    pub fn new(date_type: String, when: String) -> Self {
842        Self { date_type, when }
843    }
844
845    /// Create a "created" date
846    pub fn created(when: String) -> Self {
847        Self::new("created".to_string(), when)
848    }
849
850    /// Create a "modified" date
851    pub fn modified(when: String) -> Self {
852        Self::new("modified".to_string(), when)
853    }
854
855    /// Create a "published" date
856    pub fn published(when: String) -> Self {
857        Self::new("published".to_string(), when)
858    }
859}
860
861impl Identifier {
862    /// Create a new identifier
863    pub fn new(scheme: String, value: String) -> Self {
864        Self { scheme, value }
865    }
866
867    /// Create a Continuity identifier
868    pub fn continuity(value: String) -> Self {
869        Self::new("continuity".to_string(), value)
870    }
871
872    /// Create a DOI identifier
873    pub fn doi(value: String) -> Self {
874        Self::new("doi".to_string(), value)
875    }
876}
877
878impl Body {
879    /// Create an empty body
880    pub fn new() -> Self {
881        Self { blocks: Vec::new() }
882    }
883
884    /// Add a block element
885    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    /// Create an empty footer
898    pub fn empty() -> Self {
899        Self {
900            signatures: None,
901            citations: None,
902            annotations: None,
903        }
904    }
905
906    /// Add signatures
907    pub fn with_signatures(mut self, signatures: Signatures) -> Self {
908        self.signatures = Some(signatures);
909        self
910    }
911
912    /// Add citations
913    pub fn with_citations(mut self, citations: Citations) -> Self {
914        self.citations = Some(citations);
915        self
916    }
917
918    /// Add annotations
919    pub fn with_annotations(mut self, annotations: Annotations) -> Self {
920        self.annotations = Some(annotations);
921        self
922    }
923}
924
925impl Signature {
926    /// Create a new signature
927    pub fn new(when: String, content: String) -> Self {
928        Self {
929            when,
930            role: None,
931            reference: None,
932            content,
933        }
934    }
935
936    /// Set role
937    pub fn with_role(mut self, role: String) -> Self {
938        self.role = Some(role);
939        self
940    }
941
942    /// Set reference
943    pub fn with_reference(mut self, reference: String) -> Self {
944        self.reference = Some(reference);
945        self
946    }
947}
948
949impl Paragraph {
950    /// Create a new paragraph with inline content
951    pub fn new(content: Vec<InlineElement>) -> Self {
952        Self {
953            id: None,
954            paragraph_type: None,
955            content,
956        }
957    }
958
959    /// Create from plain text
960    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    /// Create a new heading
971    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    /// Create from plain text
981    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}