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