1use std::{fmt::Display, str::FromStr, string::ToString};
3
4use serde::{
5 Serialize,
6 ser::{SerializeMap, Serializer},
7};
8
9mod admonition;
10mod anchor;
11mod attributes;
12mod inlines;
13mod lists;
14mod location;
15mod media;
16mod metadata;
17mod section;
18pub(crate) mod substitution;
19mod tables;
20mod title;
21
22pub use admonition::{Admonition, AdmonitionVariant};
23pub use anchor::{Anchor, TocEntry, UNNUMBERED_SECTION_STYLES};
24pub use attributes::{
25 AttributeName, AttributeValue, DocumentAttributes, ElementAttributes, MAX_SECTION_LEVELS,
26 MAX_TOC_LEVELS,
27};
28pub use inlines::*;
29pub use lists::{
30 CalloutList, CalloutListItem, DescriptionList, DescriptionListItem, ListItem,
31 ListItemCheckedStatus, ListLevel, OrderedList, UnorderedList,
32};
33pub use location::*;
34pub use media::{Audio, Image, Source, Video};
35pub use metadata::{BlockMetadata, Role};
36pub use section::*;
37pub use substitution::*;
38pub use tables::{
39 ColumnFormat, ColumnStyle, ColumnWidth, HorizontalAlignment, Table, TableColumn, TableRow,
40 VerticalAlignment,
41};
42pub use title::{Subtitle, Title};
43
44#[derive(Default, Debug, PartialEq)]
46#[non_exhaustive]
47pub struct Document {
48 pub(crate) name: String,
49 pub(crate) r#type: String,
50 pub header: Option<Header>,
51 pub attributes: DocumentAttributes,
52 pub blocks: Vec<Block>,
53 pub footnotes: Vec<Footnote>,
54 pub toc_entries: Vec<TocEntry>,
55 pub location: Location,
56}
57
58#[derive(Debug, PartialEq, Serialize)]
63#[non_exhaustive]
64pub struct Header {
65 #[serde(skip_serializing_if = "BlockMetadata::is_default")]
66 pub metadata: BlockMetadata,
67 #[serde(skip_serializing_if = "Title::is_empty")]
68 pub title: Title,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub subtitle: Option<Subtitle>,
71 #[serde(skip_serializing_if = "Vec::is_empty")]
72 pub authors: Vec<Author>,
73 pub location: Location,
74}
75
76#[derive(Debug, PartialEq, Serialize)]
78#[non_exhaustive]
79pub struct Author {
80 #[serde(rename = "firstname")]
81 pub first_name: String,
82 #[serde(skip_serializing_if = "Option::is_none", rename = "middlename")]
83 pub middle_name: Option<String>,
84 #[serde(rename = "lastname")]
85 pub last_name: String,
86 pub initials: String,
87 #[serde(skip_serializing_if = "Option::is_none", rename = "address")]
88 pub email: Option<String>,
89}
90
91impl Header {
92 #[must_use]
94 pub fn new(title: Title, location: Location) -> Self {
95 Self {
96 metadata: BlockMetadata::default(),
97 title,
98 subtitle: None,
99 authors: Vec::new(),
100 location,
101 }
102 }
103
104 #[must_use]
106 pub fn with_metadata(mut self, metadata: BlockMetadata) -> Self {
107 self.metadata = metadata;
108 self
109 }
110
111 #[must_use]
113 pub fn with_subtitle(mut self, subtitle: Subtitle) -> Self {
114 self.subtitle = Some(subtitle);
115 self
116 }
117
118 #[must_use]
120 pub fn with_authors(mut self, authors: Vec<Author>) -> Self {
121 self.authors = authors;
122 self
123 }
124}
125
126impl Author {
127 #[must_use]
129 pub fn new(first_name: &str, middle_name: Option<&str>, last_name: Option<&str>) -> Self {
130 let initials = Self::generate_initials(first_name, middle_name, last_name);
131 let last_name = last_name.unwrap_or_default().to_string();
132 Self {
133 first_name: first_name.to_string(),
134 middle_name: middle_name.map(ToString::to_string),
135 last_name,
136 initials,
137 email: None,
138 }
139 }
140
141 #[must_use]
143 pub fn with_email(mut self, email: String) -> Self {
144 self.email = Some(email);
145 self
146 }
147
148 fn generate_initials(first: &str, middle: Option<&str>, last: Option<&str>) -> String {
150 let first_initial = first.chars().next().unwrap_or_default().to_string();
151 let middle_initial = middle
152 .map(|m| m.chars().next().unwrap_or_default().to_string())
153 .unwrap_or_default();
154 let last_initial = last
155 .map(|m| m.chars().next().unwrap_or_default().to_string())
156 .unwrap_or_default();
157 first_initial + &middle_initial + &last_initial
158 }
159}
160
161#[derive(Clone, Debug, PartialEq)]
166#[non_exhaustive]
167pub struct Comment {
168 pub content: String,
169 pub location: Location,
170}
171
172#[non_exhaustive]
176#[derive(Clone, Debug, PartialEq, Serialize)]
177#[serde(untagged)]
178pub enum Block {
179 TableOfContents(TableOfContents),
180 Admonition(Admonition),
186 DiscreteHeader(DiscreteHeader),
187 DocumentAttribute(DocumentAttribute),
188 ThematicBreak(ThematicBreak),
189 PageBreak(PageBreak),
190 UnorderedList(UnorderedList),
191 OrderedList(OrderedList),
192 CalloutList(CalloutList),
193 DescriptionList(DescriptionList),
194 Section(Section),
195 DelimitedBlock(DelimitedBlock),
196 Paragraph(Paragraph),
197 Image(Image),
198 Audio(Audio),
199 Video(Video),
200 Comment(Comment),
201}
202
203impl Locateable for Block {
204 fn location(&self) -> &Location {
205 match self {
206 Block::Section(s) => &s.location,
207 Block::Paragraph(p) => &p.location,
208 Block::UnorderedList(l) => &l.location,
209 Block::OrderedList(l) => &l.location,
210 Block::DescriptionList(l) => &l.location,
211 Block::CalloutList(l) => &l.location,
212 Block::DelimitedBlock(d) => &d.location,
213 Block::Admonition(a) => &a.location,
214 Block::TableOfContents(t) => &t.location,
215 Block::DiscreteHeader(h) => &h.location,
216 Block::DocumentAttribute(a) => &a.location,
217 Block::ThematicBreak(tb) => &tb.location,
218 Block::PageBreak(pb) => &pb.location,
219 Block::Image(i) => &i.location,
220 Block::Audio(a) => &a.location,
221 Block::Video(v) => &v.location,
222 Block::Comment(c) => &c.location,
223 }
224 }
225}
226
227#[derive(Clone, Debug, PartialEq)]
232#[non_exhaustive]
233pub struct DocumentAttribute {
234 pub name: AttributeName,
235 pub value: AttributeValue,
236 pub location: Location,
237}
238
239impl Serialize for DocumentAttribute {
240 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
241 where
242 S: Serializer,
243 {
244 let mut state = serializer.serialize_map(None)?;
245 state.serialize_entry("name", &self.name)?;
246 state.serialize_entry("type", "attribute")?;
247 state.serialize_entry("value", &self.value)?;
248 state.serialize_entry("location", &self.location)?;
249 state.end()
250 }
251}
252
253#[derive(Clone, Debug, PartialEq)]
258#[non_exhaustive]
259pub struct DiscreteHeader {
260 pub metadata: BlockMetadata,
261 pub title: Title,
262 pub level: u8,
263 pub location: Location,
264}
265
266#[derive(Clone, Default, Debug, PartialEq)]
268#[non_exhaustive]
269pub struct ThematicBreak {
270 pub anchors: Vec<Anchor>,
271 pub title: Title,
272 pub location: Location,
273}
274
275impl Serialize for ThematicBreak {
276 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
277 where
278 S: Serializer,
279 {
280 let mut state = serializer.serialize_map(None)?;
281 state.serialize_entry("name", "break")?;
282 state.serialize_entry("type", "block")?;
283 state.serialize_entry("variant", "thematic")?;
284 if !self.anchors.is_empty() {
285 state.serialize_entry("anchors", &self.anchors)?;
286 }
287 if !self.title.is_empty() {
288 state.serialize_entry("title", &self.title)?;
289 }
290 state.serialize_entry("location", &self.location)?;
291 state.end()
292 }
293}
294
295#[derive(Clone, Debug, PartialEq)]
297#[non_exhaustive]
298pub struct PageBreak {
299 pub title: Title,
300 pub metadata: BlockMetadata,
301 pub location: Location,
302}
303
304impl Serialize for PageBreak {
305 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
306 where
307 S: Serializer,
308 {
309 let mut state = serializer.serialize_map(None)?;
310 state.serialize_entry("name", "break")?;
311 state.serialize_entry("type", "block")?;
312 state.serialize_entry("variant", "page")?;
313 if !self.title.is_empty() {
314 state.serialize_entry("title", &self.title)?;
315 }
316 if !self.metadata.is_default() {
317 state.serialize_entry("metadata", &self.metadata)?;
318 }
319 state.serialize_entry("location", &self.location)?;
320 state.end()
321 }
322}
323
324impl Serialize for Comment {
325 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
326 where
327 S: Serializer,
328 {
329 let mut state = serializer.serialize_map(None)?;
330 state.serialize_entry("name", "comment")?;
331 state.serialize_entry("type", "block")?;
332 if !self.content.is_empty() {
333 state.serialize_entry("content", &self.content)?;
334 }
335 state.serialize_entry("location", &self.location)?;
336 state.end()
337 }
338}
339
340#[derive(Clone, Debug, PartialEq)]
342#[non_exhaustive]
343pub struct TableOfContents {
344 pub metadata: BlockMetadata,
345 pub location: Location,
346}
347
348impl Serialize for TableOfContents {
349 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
350 where
351 S: Serializer,
352 {
353 let mut state = serializer.serialize_map(None)?;
354 state.serialize_entry("name", "toc")?;
355 state.serialize_entry("type", "block")?;
356 if !self.metadata.is_default() {
357 state.serialize_entry("metadata", &self.metadata)?;
358 }
359 state.serialize_entry("location", &self.location)?;
360 state.end()
361 }
362}
363
364#[derive(Clone, Debug, PartialEq)]
366#[non_exhaustive]
367pub struct Paragraph {
368 pub metadata: BlockMetadata,
369 pub title: Title,
370 pub content: Vec<InlineNode>,
371 pub location: Location,
372}
373
374impl Paragraph {
375 #[must_use]
377 pub fn new(content: Vec<InlineNode>, location: Location) -> Self {
378 Self {
379 metadata: BlockMetadata::default(),
380 title: Title::default(),
381 content,
382 location,
383 }
384 }
385
386 #[must_use]
388 pub fn with_metadata(mut self, metadata: BlockMetadata) -> Self {
389 self.metadata = metadata;
390 self
391 }
392
393 #[must_use]
395 pub fn with_title(mut self, title: Title) -> Self {
396 self.title = title;
397 self
398 }
399}
400
401#[derive(Clone, Debug, PartialEq)]
403#[non_exhaustive]
404pub struct DelimitedBlock {
405 pub metadata: BlockMetadata,
406 pub inner: DelimitedBlockType,
407 pub delimiter: String,
408 pub title: Title,
409 pub location: Location,
410}
411
412impl DelimitedBlock {
413 #[must_use]
415 pub fn new(inner: DelimitedBlockType, delimiter: String, location: Location) -> Self {
416 Self {
417 metadata: BlockMetadata::default(),
418 inner,
419 delimiter,
420 title: Title::default(),
421 location,
422 }
423 }
424
425 #[must_use]
427 pub fn with_metadata(mut self, metadata: BlockMetadata) -> Self {
428 self.metadata = metadata;
429 self
430 }
431
432 #[must_use]
434 pub fn with_title(mut self, title: Title) -> Self {
435 self.title = title;
436 self
437 }
438}
439
440#[derive(Clone, Debug, PartialEq, Serialize)]
442#[serde(rename_all = "lowercase")]
443pub enum StemNotation {
444 Latexmath,
445 Asciimath,
446}
447
448impl Display for StemNotation {
449 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450 match self {
451 StemNotation::Latexmath => write!(f, "latexmath"),
452 StemNotation::Asciimath => write!(f, "asciimath"),
453 }
454 }
455}
456
457impl FromStr for StemNotation {
458 type Err = String;
459
460 fn from_str(s: &str) -> Result<Self, Self::Err> {
461 match s {
462 "latexmath" => Ok(Self::Latexmath),
463 "asciimath" => Ok(Self::Asciimath),
464 _ => Err(format!("unknown stem notation: {s}")),
465 }
466 }
467}
468
469#[derive(Clone, Debug, PartialEq, Serialize)]
471#[non_exhaustive]
472pub struct StemContent {
473 pub content: String,
474 pub notation: StemNotation,
475}
476
477impl StemContent {
478 #[must_use]
480 pub fn new(content: String, notation: StemNotation) -> Self {
481 Self { content, notation }
482 }
483}
484
485#[non_exhaustive]
522#[derive(Clone, Debug, PartialEq, Serialize)]
523#[serde(untagged)]
524pub enum DelimitedBlockType {
525 DelimitedComment(Vec<InlineNode>),
527 DelimitedExample(Vec<Block>),
529 DelimitedListing(Vec<InlineNode>),
531 DelimitedLiteral(Vec<InlineNode>),
533 DelimitedOpen(Vec<Block>),
535 DelimitedSidebar(Vec<Block>),
537 DelimitedTable(Table),
539 DelimitedPass(Vec<InlineNode>),
541 DelimitedQuote(Vec<Block>),
543 DelimitedVerse(Vec<InlineNode>),
545 DelimitedStem(StemContent),
547}
548
549impl DelimitedBlockType {
550 fn name(&self) -> &'static str {
551 match self {
552 DelimitedBlockType::DelimitedComment(_) => "comment",
553 DelimitedBlockType::DelimitedExample(_) => "example",
554 DelimitedBlockType::DelimitedListing(_) => "listing",
555 DelimitedBlockType::DelimitedLiteral(_) => "literal",
556 DelimitedBlockType::DelimitedOpen(_) => "open",
557 DelimitedBlockType::DelimitedSidebar(_) => "sidebar",
558 DelimitedBlockType::DelimitedTable(_) => "table",
559 DelimitedBlockType::DelimitedPass(_) => "pass",
560 DelimitedBlockType::DelimitedQuote(_) => "quote",
561 DelimitedBlockType::DelimitedVerse(_) => "verse",
562 DelimitedBlockType::DelimitedStem(_) => "stem",
563 }
564 }
565}
566
567impl Serialize for Document {
568 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
569 where
570 S: Serializer,
571 {
572 let mut state = serializer.serialize_map(None)?;
573 state.serialize_entry("name", "document")?;
574 state.serialize_entry("type", "block")?;
575 if let Some(header) = &self.header {
576 state.serialize_entry("header", header)?;
577 state.serialize_entry("attributes", &self.attributes)?;
580 } else if !self.attributes.is_empty() {
581 state.serialize_entry("attributes", &self.attributes)?;
582 }
583 if !self.blocks.is_empty() {
584 state.serialize_entry("blocks", &self.blocks)?;
585 }
586 state.serialize_entry("location", &self.location)?;
587 state.end()
588 }
589}
590
591impl Serialize for DelimitedBlock {
592 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
593 where
594 S: Serializer,
595 {
596 let mut state = serializer.serialize_map(None)?;
597 state.serialize_entry("name", self.inner.name())?;
598 state.serialize_entry("type", "block")?;
599 state.serialize_entry("form", "delimited")?;
600 state.serialize_entry("delimiter", &self.delimiter)?;
601 if !self.metadata.is_default() {
602 state.serialize_entry("metadata", &self.metadata)?;
603 }
604
605 match &self.inner {
606 DelimitedBlockType::DelimitedStem(stem) => {
607 state.serialize_entry("content", &stem.content)?;
608 state.serialize_entry("notation", &stem.notation)?;
609 }
610 DelimitedBlockType::DelimitedListing(inner)
611 | DelimitedBlockType::DelimitedLiteral(inner)
612 | DelimitedBlockType::DelimitedPass(inner)
613 | DelimitedBlockType::DelimitedVerse(inner) => {
614 state.serialize_entry("inlines", &inner)?;
615 }
616 DelimitedBlockType::DelimitedTable(inner) => {
617 state.serialize_entry("content", &inner)?;
618 }
619 inner @ (DelimitedBlockType::DelimitedComment(_)
620 | DelimitedBlockType::DelimitedExample(_)
621 | DelimitedBlockType::DelimitedOpen(_)
622 | DelimitedBlockType::DelimitedQuote(_)
623 | DelimitedBlockType::DelimitedSidebar(_)) => {
624 state.serialize_entry("blocks", &inner)?;
625 }
626 }
627 if !self.title.is_empty() {
628 state.serialize_entry("title", &self.title)?;
629 }
630 state.serialize_entry("location", &self.location)?;
631 state.end()
632 }
633}
634
635impl Serialize for DiscreteHeader {
636 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
637 where
638 S: Serializer,
639 {
640 let mut state = serializer.serialize_map(None)?;
641 state.serialize_entry("name", "heading")?;
642 state.serialize_entry("type", "block")?;
643 if !self.title.is_empty() {
644 state.serialize_entry("title", &self.title)?;
645 }
646 state.serialize_entry("level", &self.level)?;
647 if !self.metadata.is_default() {
648 state.serialize_entry("metadata", &self.metadata)?;
649 }
650 state.serialize_entry("location", &self.location)?;
651 state.end()
652 }
653}
654
655impl Serialize for Paragraph {
656 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
657 where
658 S: Serializer,
659 {
660 let mut state = serializer.serialize_map(None)?;
661 state.serialize_entry("name", "paragraph")?;
662 state.serialize_entry("type", "block")?;
663 if !self.title.is_empty() {
664 state.serialize_entry("title", &self.title)?;
665 }
666 state.serialize_entry("inlines", &self.content)?;
667 if !self.metadata.is_default() {
668 state.serialize_entry("metadata", &self.metadata)?;
669 }
670 state.serialize_entry("location", &self.location)?;
671 state.end()
672 }
673}