1use std::{collections::HashMap, fmt::Display, str::FromStr, string::ToString};
3
4use bumpalo::Bump;
5use serde::{
6 Serialize,
7 ser::{SerializeMap, Serializer},
8};
9
10mod admonition;
11mod anchor;
12pub(crate) use anchor::{BibliographyLabel, SectionReference};
13mod attributes;
14mod attribution;
15pub(crate) mod caption;
16mod inlines;
17mod lists;
18mod location;
19mod media;
20mod metadata;
21pub(crate) mod section;
22pub(crate) mod substitution;
23mod tables;
24mod title;
25
26pub use admonition::{Admonition, AdmonitionVariant};
27pub use anchor::{Anchor, Reference, TocEntry, UNNUMBERED_SECTION_STYLES};
28pub(crate) use attributes::RawAttributes;
29pub(crate) use attributes::{
30 AttributeInlineFragment, DocumentAttributeStatus, default_document_attribute_values,
31};
32pub use attributes::{
33 AttributeName, AttributeValue, DocumentAttributeAssignment, DocumentAttributeValue,
34 DocumentAttributes, ElementAttributes, MAX_SECTION_LEVELS, MAX_TOC_LEVELS, strip_quotes,
35};
36pub use attribution::{Attribution, CiteTitle};
37pub use caption::{Caption, CaptionKind};
38pub use inlines::*;
39pub use lists::{
40 CalloutList, CalloutListItem, DescriptionList, DescriptionListItem, ListItem,
41 ListItemCheckedStatus, ListLevel, OrderedList, UnorderedList,
42};
43pub use location::*;
44pub use media::{Audio, Image, Source, SourceUrl, Video};
45pub(crate) use metadata::PositionalAttribute;
46pub use metadata::{BlockMetadata, Role};
47pub use section::*;
48pub use substitution::*;
49pub use tables::{
50 ColumnFormat, ColumnStyle, ColumnWidth, HorizontalAlignment, Table, TableColumn, TableFrame,
51 TableGrid, TablePresentation, TableRow, TableStripes, VerticalAlignment,
52};
53pub use title::{Subtitle, Title};
54
55#[derive(Default, Debug, PartialEq)]
57#[non_exhaustive]
58pub struct Document<'a> {
59 pub header: Option<Header<'a>>,
60 pub attributes: DocumentAttributes<'a>,
63 pub blocks: Vec<Block<'a>>,
64 pub footnotes: Vec<Footnote<'a>>,
65 pub toc_entries: Vec<TocEntry<'a>>,
66 pub references: HashMap<&'a str, Reference<'a>>,
74 pub location: Location,
75}
76
77impl Document<'_> {
78 pub fn renumber_sections(&mut self) {
89 let is_book = matches!(
90 self.attributes.get("doctype"),
91 Some(value) if value.as_str() == Some("book")
92 );
93 section::renumber_sections(
94 &mut self.blocks,
95 &mut self.toc_entries,
96 &mut self.references,
97 is_book,
98 );
99 let section_names = self
100 .references
101 .iter()
102 .filter_map(|(target, reference)| Some((*target, reference.section.as_ref()?.name)))
103 .collect::<HashMap<_, _>>();
104 crate::grammar::walk_document_inline_nodes_mut(self, &mut |inline| {
105 if let InlineNode::Macro(InlineMacro::CrossReference(xref)) = inline {
106 xref.refresh_signifier(section_names.get(xref.target).copied());
107 }
108 });
109 }
110
111 pub fn renumber_captions(&mut self) {
119 caption::renumber_captions(&mut self.blocks);
120 }
121
122 #[must_use]
128 pub fn highest_caption_number(&self, kind: CaptionKind) -> u32 {
129 caption::highest_caption_number(&self.blocks, kind)
130 }
131}
132
133#[derive(Debug, PartialEq, Serialize)]
138#[non_exhaustive]
139pub struct Header<'a> {
140 #[serde(skip_serializing_if = "BlockMetadata::is_default")]
141 pub metadata: BlockMetadata<'a>,
142 #[serde(skip_serializing_if = "Title::is_empty")]
143 pub title: Title<'a>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub subtitle: Option<Subtitle<'a>>,
146 #[serde(skip_serializing_if = "Vec::is_empty")]
147 pub authors: Vec<Author<'a>>,
148 pub location: Location,
149}
150
151#[derive(Debug, PartialEq, Serialize)]
153#[non_exhaustive]
154pub struct Author<'a> {
155 #[serde(rename = "firstname")]
156 pub first_name: &'a str,
157 #[serde(skip_serializing_if = "Option::is_none", rename = "middlename")]
158 pub middle_name: Option<&'a str>,
159 #[serde(rename = "lastname")]
160 pub last_name: &'a str,
161 pub initials: &'a str,
162 #[serde(skip_serializing_if = "Option::is_none", rename = "address")]
163 pub email: Option<&'a str>,
164}
165
166impl<'a> Header<'a> {
167 #[must_use]
169 pub fn new(title: Title<'a>, location: Location) -> Self {
170 Self {
171 metadata: BlockMetadata::default(),
172 title,
173 subtitle: None,
174 authors: Vec::new(),
175 location,
176 }
177 }
178
179 #[must_use]
181 pub fn with_metadata(mut self, metadata: BlockMetadata<'a>) -> Self {
182 self.metadata = metadata;
183 self
184 }
185
186 #[must_use]
188 pub fn with_subtitle(mut self, subtitle: Subtitle<'a>) -> Self {
189 self.subtitle = Some(subtitle);
190 self
191 }
192
193 #[must_use]
195 pub fn with_authors(mut self, authors: Vec<Author<'a>>) -> Self {
196 self.authors = authors;
197 self
198 }
199}
200
201impl<'a> Author<'a> {
202 #[must_use]
206 pub fn from_parts(
207 first_name: &'a str,
208 middle_name: Option<&'a str>,
209 last_name: &'a str,
210 initials: &'a str,
211 ) -> Self {
212 Self {
213 first_name,
214 middle_name,
215 last_name,
216 initials,
217 email: None,
218 }
219 }
220
221 #[must_use]
224 pub(crate) fn new(
225 arena: &'a Bump,
226 first_name: &'a str,
227 middle_name: Option<&'a str>,
228 last_name: Option<&'a str>,
229 ) -> Self {
230 let first_processed = first_name.replace('_', " ");
231 let middle_processed = middle_name.map(|m| m.replace('_', " "));
232 let last_processed = last_name.map(|l| l.replace('_', " "));
233
234 let initials = Self::generate_initials(
235 &first_processed,
236 middle_processed.as_deref(),
237 last_processed.as_deref(),
238 );
239
240 Self {
241 first_name: if first_processed == first_name {
242 first_name
243 } else {
244 arena.alloc_str(&first_processed)
245 },
246 middle_name: middle_name
247 .zip(middle_processed.as_ref())
248 .map(|(orig, proc)| {
249 if proc == orig {
250 orig
251 } else {
252 &*arena.alloc_str(proc)
253 }
254 }),
255 last_name: last_name
256 .zip(last_processed.as_ref())
257 .map_or("", |(orig, proc)| {
258 if proc == orig {
259 orig
260 } else {
261 arena.alloc_str(proc)
262 }
263 }),
264 initials: arena.alloc_str(&initials),
265 email: None,
266 }
267 }
268
269 #[must_use]
271 pub fn with_email(mut self, email: &'a str) -> Self {
272 self.email = Some(email);
273 self
274 }
275
276 fn generate_initials(first: &str, middle: Option<&str>, last: Option<&str>) -> String {
278 let first_initial = first.chars().next().unwrap_or_default().to_string();
279 let middle_initial = middle
280 .map(|m| m.chars().next().unwrap_or_default().to_string())
281 .unwrap_or_default();
282 let last_initial = last
283 .map(|m| m.chars().next().unwrap_or_default().to_string())
284 .unwrap_or_default();
285 first_initial + &middle_initial + &last_initial
286 }
287}
288
289#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
295#[serde(rename_all = "snake_case")]
296#[non_exhaustive]
297pub enum CommentKind {
298 #[default]
300 Line,
301 Paragraph,
303}
304
305#[derive(Clone, Debug, PartialEq)]
310#[non_exhaustive]
311pub struct Comment<'a> {
312 pub kind: CommentKind,
313 pub content: &'a str,
314 pub location: Location,
315}
316
317#[allow(clippy::large_enum_variant)]
324#[non_exhaustive]
325#[derive(Clone, Debug, PartialEq, Serialize)]
326#[serde(untagged)]
327pub enum Block<'a> {
328 TableOfContents(TableOfContents<'a>),
329 Admonition(Admonition<'a>),
335 DiscreteHeader(DiscreteHeader<'a>),
336 DocumentAttribute(DocumentAttribute<'a>),
337 ThematicBreak(ThematicBreak<'a>),
338 PageBreak(PageBreak<'a>),
339 UnorderedList(UnorderedList<'a>),
340 OrderedList(OrderedList<'a>),
341 CalloutList(CalloutList<'a>),
342 DescriptionList(DescriptionList<'a>),
343 Section(Section<'a>),
344 DelimitedBlock(DelimitedBlock<'a>),
345 Paragraph(Paragraph<'a>),
346 Image(Image<'a>),
347 Audio(Audio<'a>),
348 Video(Video<'a>),
349 Comment(Comment<'a>),
350}
351
352impl<'a> Block<'a> {
353 #[must_use]
355 pub fn location(&self) -> &Location {
356 match self {
357 Block::Section(s) => &s.location,
358 Block::Paragraph(p) => &p.location,
359 Block::UnorderedList(l) => &l.location,
360 Block::OrderedList(l) => &l.location,
361 Block::DescriptionList(l) => &l.location,
362 Block::CalloutList(l) => &l.location,
363 Block::DelimitedBlock(d) => &d.location,
364 Block::Admonition(a) => &a.location,
365 Block::TableOfContents(t) => &t.location,
366 Block::DiscreteHeader(h) => &h.location,
367 Block::DocumentAttribute(a) => &a.location,
368 Block::ThematicBreak(tb) => &tb.location,
369 Block::PageBreak(pb) => &pb.location,
370 Block::Image(i) => &i.location,
371 Block::Audio(a) => &a.location,
372 Block::Video(v) => &v.location,
373 Block::Comment(c) => &c.location,
374 }
375 }
376
377 #[must_use]
379 pub fn metadata(&self) -> Option<&BlockMetadata<'a>> {
380 match self {
381 Block::Section(block) => Some(&block.metadata),
382 Block::DelimitedBlock(block) => Some(&block.metadata),
383 Block::Admonition(block) => Some(&block.metadata),
384 Block::DiscreteHeader(block) => Some(&block.metadata),
385 Block::PageBreak(block) => Some(&block.metadata),
386 Block::Paragraph(block) => Some(&block.metadata),
387 Block::Image(block) => Some(&block.metadata),
388 Block::Audio(block) => Some(&block.metadata),
389 Block::Video(block) => Some(&block.metadata),
390 Block::UnorderedList(block) => Some(&block.metadata),
391 Block::OrderedList(block) => Some(&block.metadata),
392 Block::CalloutList(block) => Some(&block.metadata),
393 Block::DescriptionList(block) => Some(&block.metadata),
394 Block::TableOfContents(block) => Some(&block.metadata),
395 Block::ThematicBreak(_) | Block::DocumentAttribute(_) | Block::Comment(_) => None,
396 }
397 }
398
399 pub(crate) fn metadata_mut(&mut self) -> Option<&mut BlockMetadata<'a>> {
401 match self {
402 Block::Section(block) => Some(&mut block.metadata),
403 Block::DelimitedBlock(block) => Some(&mut block.metadata),
404 Block::Admonition(block) => Some(&mut block.metadata),
405 Block::DiscreteHeader(block) => Some(&mut block.metadata),
406 Block::PageBreak(block) => Some(&mut block.metadata),
407 Block::Paragraph(block) => Some(&mut block.metadata),
408 Block::Image(block) => Some(&mut block.metadata),
409 Block::Audio(block) => Some(&mut block.metadata),
410 Block::Video(block) => Some(&mut block.metadata),
411 Block::UnorderedList(block) => Some(&mut block.metadata),
412 Block::OrderedList(block) => Some(&mut block.metadata),
413 Block::CalloutList(block) => Some(&mut block.metadata),
414 Block::DescriptionList(block) => Some(&mut block.metadata),
415 Block::TableOfContents(block) => Some(&mut block.metadata),
416 Block::ThematicBreak(_) | Block::DocumentAttribute(_) | Block::Comment(_) => None,
417 }
418 }
419
420 pub(crate) fn anchor(&self) -> Option<&Anchor<'a>> {
423 match self {
424 Block::Section(b) => b.metadata.id_anchor(),
425 Block::DelimitedBlock(b) => b.metadata.id_anchor(),
426 Block::Admonition(b) => b.metadata.id_anchor(),
427 Block::DiscreteHeader(b) => b.metadata.id_anchor(),
428 Block::PageBreak(b) => b.metadata.id_anchor(),
429 Block::Paragraph(b) => b.metadata.id_anchor(),
430 Block::Image(b) => b.metadata.id_anchor(),
431 Block::Audio(b) => b.metadata.id_anchor(),
432 Block::Video(b) => b.metadata.id_anchor(),
433 Block::UnorderedList(b) => b.metadata.id_anchor(),
434 Block::OrderedList(b) => b.metadata.id_anchor(),
435 Block::CalloutList(b) => b.metadata.id_anchor(),
436 Block::DescriptionList(b) => b.metadata.id_anchor(),
437 Block::TableOfContents(b) => b.metadata.id_anchor(),
438 Block::ThematicBreak(b) => b.anchors.first(),
439 Block::DocumentAttribute(_) | Block::Comment(_) => None,
440 }
441 }
442
443 pub(crate) fn title(&self) -> Option<&Title<'a>> {
446 let title = match self {
447 Block::Section(b) => &b.title,
448 Block::DelimitedBlock(b) => &b.title,
449 Block::Admonition(b) => &b.title,
450 Block::DiscreteHeader(b) => &b.title,
451 Block::PageBreak(b) => &b.title,
452 Block::Paragraph(b) => &b.title,
453 Block::Image(b) => &b.title,
454 Block::Audio(b) => &b.title,
455 Block::Video(b) => &b.title,
456 Block::UnorderedList(b) => &b.title,
457 Block::OrderedList(b) => &b.title,
458 Block::CalloutList(b) => &b.title,
459 Block::DescriptionList(b) => &b.title,
460 Block::ThematicBreak(b) => &b.title,
461 Block::TableOfContents(_) | Block::DocumentAttribute(_) | Block::Comment(_) => {
462 return None;
463 }
464 };
465 (!title.is_empty()).then_some(title)
466 }
467}
468
469impl Block<'_> {
470 pub(crate) fn location_mut(&mut self) -> &mut Location {
473 match self {
474 Block::Section(s) => &mut s.location,
475 Block::Paragraph(p) => &mut p.location,
476 Block::UnorderedList(l) => &mut l.location,
477 Block::OrderedList(l) => &mut l.location,
478 Block::DescriptionList(l) => &mut l.location,
479 Block::CalloutList(l) => &mut l.location,
480 Block::DelimitedBlock(d) => &mut d.location,
481 Block::Admonition(a) => &mut a.location,
482 Block::TableOfContents(t) => &mut t.location,
483 Block::DiscreteHeader(h) => &mut h.location,
484 Block::DocumentAttribute(a) => &mut a.location,
485 Block::ThematicBreak(tb) => &mut tb.location,
486 Block::PageBreak(pb) => &mut pb.location,
487 Block::Image(i) => &mut i.location,
488 Block::Audio(a) => &mut a.location,
489 Block::Video(v) => &mut v.location,
490 Block::Comment(c) => &mut c.location,
491 }
492 }
493}
494
495#[derive(Clone, PartialEq)]
497#[non_exhaustive]
498pub struct DocumentAttribute<'a> {
499 pub name: AttributeName<'a>,
500 pub location: Location,
501 assignment: DocumentAttributeAssignment<'a>,
502 accepted: bool,
503}
504
505impl<'a> DocumentAttribute<'a> {
506 pub(crate) fn accepted(
507 name: AttributeName<'a>,
508 applied: DocumentAttributeAssignment<'a>,
509 location: Location,
510 ) -> Self {
511 Self {
512 name,
513 location,
514 assignment: applied,
515 accepted: true,
516 }
517 }
518
519 pub(crate) fn rejected(name: AttributeName<'a>, location: Location) -> Self {
520 Self {
521 name,
522 location,
523 assignment: DocumentAttributeAssignment::Unset,
524 accepted: false,
525 }
526 }
527
528 pub(crate) const fn is_accepted(&self) -> bool {
529 self.accepted
530 }
531
532 #[must_use]
534 pub const fn assignment(&self) -> &DocumentAttributeAssignment<'a> {
535 &self.assignment
536 }
537}
538
539impl std::fmt::Debug for DocumentAttribute<'_> {
540 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
541 formatter
542 .debug_struct("DocumentAttribute")
543 .field("name", &self.name)
544 .field("assignment", &self.assignment)
545 .field("location", &self.location)
546 .field("accepted", &self.accepted)
547 .finish()
548 }
549}
550
551impl Serialize for DocumentAttribute<'_> {
552 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
553 where
554 S: Serializer,
555 {
556 let mut state = serializer.serialize_map(None)?;
557 state.serialize_entry("name", &self.name)?;
558 state.serialize_entry("type", "attribute")?;
559 state.serialize_entry("value", &self.assignment.serialized_value(false))?;
560 state.serialize_entry("location", &self.location)?;
561 state.end()
562 }
563}
564
565#[derive(Clone, Debug, PartialEq)]
570#[non_exhaustive]
571pub struct DiscreteHeader<'a> {
572 pub metadata: BlockMetadata<'a>,
573 pub title: Title<'a>,
574 pub level: u8,
575 pub location: Location,
576}
577
578#[derive(Clone, Default, Debug, PartialEq)]
580#[non_exhaustive]
581pub struct ThematicBreak<'a> {
582 pub anchors: Vec<Anchor<'a>>,
583 pub title: Title<'a>,
584 pub location: Location,
585}
586
587impl Serialize for ThematicBreak<'_> {
588 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
589 where
590 S: Serializer,
591 {
592 let mut state = serializer.serialize_map(None)?;
593 state.serialize_entry("name", "break")?;
594 state.serialize_entry("type", "block")?;
595 state.serialize_entry("variant", "thematic")?;
596 if !self.anchors.is_empty() {
597 state.serialize_entry("anchors", &self.anchors)?;
598 }
599 if !self.title.is_empty() {
600 state.serialize_entry("title", &self.title)?;
601 }
602 state.serialize_entry("location", &self.location)?;
603 state.end()
604 }
605}
606
607#[derive(Clone, Debug, PartialEq)]
609#[non_exhaustive]
610pub struct PageBreak<'a> {
611 pub title: Title<'a>,
612 pub metadata: BlockMetadata<'a>,
613 pub location: Location,
614}
615
616impl Serialize for PageBreak<'_> {
617 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
618 where
619 S: Serializer,
620 {
621 let mut state = serializer.serialize_map(None)?;
622 state.serialize_entry("name", "break")?;
623 state.serialize_entry("type", "block")?;
624 state.serialize_entry("variant", "page")?;
625 if !self.title.is_empty() {
626 state.serialize_entry("title", &self.title)?;
627 }
628 if !self.metadata.is_default() {
629 state.serialize_entry("metadata", &self.metadata)?;
630 }
631 state.serialize_entry("location", &self.location)?;
632 state.end()
633 }
634}
635
636impl Serialize for Comment<'_> {
637 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
638 where
639 S: Serializer,
640 {
641 let mut state = serializer.serialize_map(None)?;
642 state.serialize_entry("name", "comment")?;
643 state.serialize_entry("type", "block")?;
644 if self.kind != CommentKind::Line {
646 state.serialize_entry("variant", &self.kind)?;
647 }
648 if !self.content.is_empty() {
649 state.serialize_entry("content", &self.content)?;
650 }
651 state.serialize_entry("location", &self.location)?;
652 state.end()
653 }
654}
655
656#[derive(Clone, Debug, PartialEq)]
658#[non_exhaustive]
659pub struct TableOfContents<'a> {
660 pub metadata: BlockMetadata<'a>,
661 pub location: Location,
662}
663
664impl Serialize for TableOfContents<'_> {
665 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
666 where
667 S: Serializer,
668 {
669 let mut state = serializer.serialize_map(None)?;
670 state.serialize_entry("name", "toc")?;
671 state.serialize_entry("type", "block")?;
672 if !self.metadata.is_default() {
673 state.serialize_entry("metadata", &self.metadata)?;
674 }
675 state.serialize_entry("location", &self.location)?;
676 state.end()
677 }
678}
679
680#[derive(Clone, Debug)]
682#[non_exhaustive]
683pub struct Paragraph<'a> {
684 pub metadata: BlockMetadata<'a>,
685 pub title: Title<'a>,
686 pub content: Vec<InlineNode<'a>>,
687 pub location: Location,
688 pub(crate) source_text: Option<&'a str>,
689}
690
691impl PartialEq for Paragraph<'_> {
692 fn eq(&self, other: &Self) -> bool {
693 self.metadata == other.metadata
694 && self.title == other.title
695 && self.content == other.content
696 && self.location == other.location
697 }
698}
699
700impl<'a> Paragraph<'a> {
701 #[must_use]
703 pub fn new(content: Vec<InlineNode<'a>>, location: Location) -> Self {
704 Self {
705 metadata: BlockMetadata::default(),
706 title: Title::default(),
707 content,
708 location,
709 source_text: None,
710 }
711 }
712
713 #[must_use]
718 pub fn source_text(&self) -> Option<&'a str> {
719 self.source_text
720 }
721
722 #[must_use]
724 pub fn with_metadata(mut self, metadata: BlockMetadata<'a>) -> Self {
725 self.metadata = metadata;
726 self
727 }
728
729 #[must_use]
731 pub fn with_title(mut self, title: Title<'a>) -> Self {
732 self.title = title;
733 self
734 }
735}
736
737#[derive(Clone, Debug)]
739#[non_exhaustive]
740pub struct DelimitedBlock<'a> {
741 pub metadata: BlockMetadata<'a>,
742 pub inner: DelimitedBlockType<'a>,
743 pub delimiter: &'a str,
744 pub title: Title<'a>,
745 pub location: Location,
746 pub open_delimiter_location: Option<Location>,
747 pub close_delimiter_location: Option<Location>,
748 pub(crate) source_text: Option<&'a str>,
749}
750
751impl PartialEq for DelimitedBlock<'_> {
752 fn eq(&self, other: &Self) -> bool {
753 self.metadata == other.metadata
754 && self.inner == other.inner
755 && self.delimiter == other.delimiter
756 && self.title == other.title
757 && self.location == other.location
758 && self.open_delimiter_location == other.open_delimiter_location
759 && self.close_delimiter_location == other.close_delimiter_location
760 }
761}
762
763impl<'a> DelimitedBlock<'a> {
764 #[must_use]
766 pub fn new(inner: DelimitedBlockType<'a>, delimiter: &'a str, location: Location) -> Self {
767 Self {
768 metadata: BlockMetadata::default(),
769 inner,
770 delimiter,
771 title: Title::default(),
772 location,
773 open_delimiter_location: None,
774 close_delimiter_location: None,
775 source_text: None,
776 }
777 }
778
779 #[must_use]
785 pub fn source_text(&self) -> Option<&'a str> {
786 self.source_text
787 }
788
789 #[must_use]
791 pub fn with_metadata(mut self, metadata: BlockMetadata<'a>) -> Self {
792 self.metadata = metadata;
793 self
794 }
795
796 #[must_use]
798 pub fn with_title(mut self, title: Title<'a>) -> Self {
799 self.title = title;
800 self
801 }
802}
803
804#[derive(Clone, Debug, PartialEq, Serialize)]
806#[serde(rename_all = "lowercase")]
807pub enum StemNotation {
808 Latexmath,
809 Asciimath,
810}
811
812impl Display for StemNotation {
813 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
814 match self {
815 StemNotation::Latexmath => write!(f, "latexmath"),
816 StemNotation::Asciimath => write!(f, "asciimath"),
817 }
818 }
819}
820
821impl FromStr for StemNotation {
822 type Err = String;
823
824 fn from_str(s: &str) -> Result<Self, Self::Err> {
825 match s {
826 "latexmath" => Ok(Self::Latexmath),
827 "asciimath" => Ok(Self::Asciimath),
828 _ => Err(format!("unknown stem notation: {s}")),
829 }
830 }
831}
832
833#[derive(Clone, Debug, PartialEq, Serialize)]
835#[non_exhaustive]
836pub struct StemContent<'a> {
837 pub content: &'a str,
838 pub notation: StemNotation,
839}
840
841impl<'a> StemContent<'a> {
842 #[must_use]
844 pub fn new(content: &'a str, notation: StemNotation) -> Self {
845 Self { content, notation }
846 }
847}
848
849#[non_exhaustive]
886#[derive(Clone, Debug, PartialEq, Serialize)]
887#[serde(untagged)]
888pub enum DelimitedBlockType<'a> {
889 DelimitedComment(Vec<InlineNode<'a>>),
891 DelimitedExample(Vec<Block<'a>>),
893 DelimitedListing(Vec<InlineNode<'a>>),
895 DelimitedLiteral(Vec<InlineNode<'a>>),
897 DelimitedOpen(Vec<Block<'a>>),
899 DelimitedSidebar(Vec<Block<'a>>),
901 DelimitedTable(Table<'a>),
903 DelimitedPass(Vec<InlineNode<'a>>),
905 DelimitedQuote(Vec<Block<'a>>),
907 DelimitedVerse(Vec<InlineNode<'a>>),
909 DelimitedStem(StemContent<'a>),
911}
912
913impl DelimitedBlockType<'_> {
914 fn name(&self) -> &'static str {
915 match self {
916 DelimitedBlockType::DelimitedComment(_) => "comment",
917 DelimitedBlockType::DelimitedExample(_) => "example",
918 DelimitedBlockType::DelimitedListing(_) => "listing",
919 DelimitedBlockType::DelimitedLiteral(_) => "literal",
920 DelimitedBlockType::DelimitedOpen(_) => "open",
921 DelimitedBlockType::DelimitedSidebar(_) => "sidebar",
922 DelimitedBlockType::DelimitedTable(_) => "table",
923 DelimitedBlockType::DelimitedPass(_) => "pass",
924 DelimitedBlockType::DelimitedQuote(_) => "quote",
925 DelimitedBlockType::DelimitedVerse(_) => "verse",
926 DelimitedBlockType::DelimitedStem(_) => "stem",
927 }
928 }
929}
930
931impl Serialize for Document<'_> {
932 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
933 where
934 S: Serializer,
935 {
936 let mut state = serializer.serialize_map(None)?;
937 state.serialize_entry("name", "document")?;
938 state.serialize_entry("type", "block")?;
939 if let Some(header) = &self.header {
940 state.serialize_entry("header", header)?;
941 state.serialize_entry("attributes", &self.attributes)?;
944 } else if !self.attributes.serialization_is_empty() {
945 state.serialize_entry("attributes", &self.attributes)?;
946 }
947 if !self.blocks.is_empty() {
948 state.serialize_entry("blocks", &self.blocks)?;
949 }
950 state.serialize_entry("location", &self.location)?;
951 state.end()
952 }
953}
954
955impl Serialize for DelimitedBlock<'_> {
956 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
957 where
958 S: Serializer,
959 {
960 let mut state = serializer.serialize_map(None)?;
961 state.serialize_entry("name", self.inner.name())?;
962 state.serialize_entry("type", "block")?;
963 state.serialize_entry("form", "delimited")?;
964 state.serialize_entry("delimiter", &self.delimiter)?;
965 if !self.metadata.is_default() {
966 state.serialize_entry("metadata", &self.metadata)?;
967 }
968
969 match &self.inner {
970 DelimitedBlockType::DelimitedStem(stem) => {
971 state.serialize_entry("content", &stem.content)?;
972 state.serialize_entry("notation", &stem.notation)?;
973 }
974 DelimitedBlockType::DelimitedListing(inner)
975 | DelimitedBlockType::DelimitedLiteral(inner)
976 | DelimitedBlockType::DelimitedPass(inner)
977 | DelimitedBlockType::DelimitedVerse(inner) => {
978 state.serialize_entry("inlines", &inner)?;
979 }
980 DelimitedBlockType::DelimitedTable(inner) => {
981 state.serialize_entry("content", &inner)?;
982 }
983 inner @ (DelimitedBlockType::DelimitedComment(_)
984 | DelimitedBlockType::DelimitedExample(_)
985 | DelimitedBlockType::DelimitedOpen(_)
986 | DelimitedBlockType::DelimitedQuote(_)
987 | DelimitedBlockType::DelimitedSidebar(_)) => {
988 state.serialize_entry("blocks", &inner)?;
989 }
990 }
991 if !self.title.is_empty() {
992 state.serialize_entry("title", &self.title)?;
993 }
994 state.serialize_entry("location", &self.location)?;
995 state.end()
996 }
997}
998
999impl Serialize for DiscreteHeader<'_> {
1000 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1001 where
1002 S: Serializer,
1003 {
1004 let mut state = serializer.serialize_map(None)?;
1005 state.serialize_entry("name", "heading")?;
1006 state.serialize_entry("type", "block")?;
1007 if !self.title.is_empty() {
1008 state.serialize_entry("title", &self.title)?;
1009 }
1010 state.serialize_entry("level", &self.level)?;
1011 if !self.metadata.is_default() {
1012 state.serialize_entry("metadata", &self.metadata)?;
1013 }
1014 state.serialize_entry("location", &self.location)?;
1015 state.end()
1016 }
1017}
1018
1019impl Serialize for Paragraph<'_> {
1020 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1021 where
1022 S: Serializer,
1023 {
1024 let mut state = serializer.serialize_map(None)?;
1025 state.serialize_entry("name", "paragraph")?;
1026 state.serialize_entry("type", "block")?;
1027 if !self.title.is_empty() {
1028 state.serialize_entry("title", &self.title)?;
1029 }
1030 state.serialize_entry("inlines", &self.content)?;
1031 if !self.metadata.is_default() {
1032 state.serialize_entry("metadata", &self.metadata)?;
1033 }
1034 state.serialize_entry("location", &self.location)?;
1035 state.end()
1036 }
1037}