1#[derive(Debug, Clone, PartialEq)]
6pub enum Inline {
7 Text(String),
8 Emphasis(Vec<Inline>),
9 Strong(Vec<Inline>),
10 Cite(Vec<Inline>),
11 Literal(String),
12 Code(String),
14 Image {
15 uri: String,
16 alt: Option<String>,
17 },
18 Reference(RefTarget, Vec<Inline>),
20 PendingRef(PendingRef),
22 FootnoteRef(FootnoteRef),
23 CitationRef(CitationRef),
24 SubstitutionRef(SubstitutionRef),
25 InlineTarget(String),
26 Problematic(Problematic),
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub struct Problematic {
32 pub text: String,
33 pub id: Option<usize>,
35}
36
37#[derive(Debug, Clone, PartialEq)]
39pub struct PendingRef {
40 pub text: Vec<Inline>,
41 pub target: String,
42 pub anonymous: bool,
43 pub raw: String,
45}
46
47#[derive(Debug, Clone, PartialEq)]
49pub struct FootnoteRef {
50 pub label: String,
51 pub auto: bool,
53 pub symbol: bool,
54 pub raw: String,
56}
57
58#[derive(Debug, Clone, PartialEq)]
60pub struct CitationRef {
61 pub label: String,
62 pub raw: String,
64}
65
66#[derive(Debug, Clone, PartialEq)]
68pub struct SubstitutionRef {
69 pub name: String,
70 pub raw: String,
72}
73
74#[derive(Debug, Clone, PartialEq)]
76pub enum RefTarget {
77 External(String),
78 Internal(String),
79}
80
81#[derive(Debug, Clone, PartialEq)]
84pub enum LineRow {
85 Line(Vec<Inline>),
86 Nested(Vec<LineRow>),
87}
88
89#[derive(Debug, Clone, PartialEq)]
91pub enum Block {
92 Paragraph(Vec<Inline>),
93 Section(Section),
94 BulletList(BulletList),
95 EnumList(EnumList),
96 DefinitionList(Vec<DefItem>),
97 FieldList(Vec<Field>),
98 BlockQuote(BlockQuote),
99 LiteralBlock(LiteralBlock),
100 LineBlock(Vec<LineRow>),
101 Table(Table),
102 Admonition(Admonition),
103 Topic(Topic),
104 Rubric(Vec<Inline>),
105 Comment(String),
106 HyperlinkTarget(HyperlinkTarget),
107 Footnote(Footnote),
108 Citation(Citation),
109 SubstitutionDef(SubstitutionDef),
110 Transition,
111 SystemMessage(SystemMessage),
112 Raw(Raw),
113 Image(Image),
114 Figure(Figure),
115 PendingContents {
117 depth: Option<u32>,
118 title: Option<Vec<Inline>>,
119 },
120 ParsedLiteral(Vec<Vec<Inline>>),
122 Container(Container),
124 OptionList(Vec<OptGroup>),
126}
127
128#[derive(Debug, Clone, PartialEq)]
129pub struct Section {
130 pub title: Vec<Inline>,
131 pub level: u8,
132 pub ids: Vec<String>,
133 pub children: Vec<Block>,
134 pub classes: Vec<String>,
135}
136
137#[derive(Debug, Clone, PartialEq)]
138pub struct BulletList {
139 pub bullet: char,
140 pub items: Vec<Vec<Block>>,
141}
142#[derive(Debug, Clone, PartialEq)]
143pub struct OptName {
144 pub name: String,
145 pub arg: Option<String>,
146 pub sep: Option<char>,
148}
149#[derive(Debug, Clone, PartialEq)]
150pub struct OptGroup {
151 pub opts: Vec<OptName>,
152 pub body: Vec<Block>,
153}
154
155#[derive(Debug, Clone, PartialEq, Copy)]
156pub enum EnumFormat {
157 Arabic,
158 LowerAlpha,
159 UpperAlpha,
160 LowerRoman,
161 UpperRoman,
162 Auto,
163}
164
165#[derive(Debug, Clone, PartialEq)]
166pub struct EnumList {
167 pub format: EnumFormat,
168 pub start: u32,
169 pub items: Vec<Vec<Block>>,
170}
171
172#[derive(Debug, Clone, PartialEq)]
173pub struct DefItem {
174 pub term: Vec<Inline>,
175 pub definition: Vec<Block>,
176}
177
178#[derive(Debug, Clone, PartialEq)]
179pub struct Field {
180 pub name: Vec<Inline>,
181 pub body: Vec<Block>,
182}
183
184#[derive(Debug, Clone, PartialEq)]
185pub struct BlockQuote {
186 pub children: Vec<Block>,
187 pub attribution: Option<Vec<Inline>>,
188}
189
190#[derive(Debug, Clone, PartialEq)]
191pub struct LiteralBlock {
192 pub lines: Vec<String>,
193 pub language: Option<String>,
195}
196
197#[derive(Debug, Clone, PartialEq)]
198pub struct TableCell {
199 pub blocks: Vec<Block>,
200 pub colspan: u32,
201}
202
203#[derive(Debug, Clone, PartialEq)]
204pub struct Table {
205 pub head: Vec<Vec<TableCell>>,
206 pub body: Vec<Vec<TableCell>>,
207 pub widths: Vec<u32>,
208 pub caption: Vec<Inline>,
210}
211
212#[derive(Debug, Clone, PartialEq, Copy)]
213pub enum AdmonKind {
214 Admonition,
215 Attention,
216 Caution,
217 Danger,
218 Error,
219 Hint,
220 Important,
221 Note,
222 Tip,
223 Warning,
224}
225
226impl AdmonKind {
227 pub fn from_directive(name: &str) -> Option<Self> {
228 match name {
229 "admonition" => Some(Self::Admonition),
230 "attention" => Some(Self::Attention),
231 "caution" => Some(Self::Caution),
232 "danger" => Some(Self::Danger),
233 "error" => Some(Self::Error),
234 "hint" => Some(Self::Hint),
235 "important" => Some(Self::Important),
236 "note" => Some(Self::Note),
237 "tip" => Some(Self::Tip),
238 "warning" => Some(Self::Warning),
239 _ => None,
240 }
241 }
242
243 pub fn css_class(self) -> &'static str {
244 match self {
245 Self::Admonition => "admonition",
246 Self::Attention => "attention",
247 Self::Caution => "caution",
248 Self::Danger => "danger",
249 Self::Error => "error",
250 Self::Hint => "hint",
251 Self::Important => "important",
252 Self::Note => "note",
253 Self::Tip => "tip",
254 Self::Warning => "warning",
255 }
256 }
257
258 pub fn default_title(self) -> Option<&'static str> {
259 match self {
261 Self::Admonition => None,
262 Self::Attention => Some("Attention!"),
263 Self::Caution => Some("Caution!"),
264 Self::Danger => Some("!DANGER!"),
265 Self::Error => Some("Error"),
266 Self::Hint => Some("Hint"),
267 Self::Important => Some("Important"),
268 Self::Note => Some("Note"),
269 Self::Tip => Some("Tip"),
270 Self::Warning => Some("Warning"),
271 }
272 }
273}
274
275#[derive(Debug, Clone, PartialEq)]
276pub struct Admonition {
277 pub kind: AdmonKind,
278 pub title: Option<Vec<Inline>>,
279 pub children: Vec<Block>,
280}
281
282#[derive(Debug, Clone, PartialEq)]
283pub struct Topic {
284 pub title: Vec<Inline>,
285 pub children: Vec<Block>,
286 pub classes: Vec<String>,
287}
288
289#[derive(Debug, Clone, PartialEq)]
290pub struct HyperlinkTarget {
291 pub name: String,
292 pub uri: String,
293 pub anonymous: bool,
294}
295
296#[derive(Debug, Clone, PartialEq)]
297pub struct Footnote {
298 pub name: String,
299 pub auto_numbered: bool,
300 pub symbol: bool,
301 pub number: Option<u32>,
303 pub children: Vec<Block>,
304}
305
306#[derive(Debug, Clone, PartialEq)]
307pub struct Citation {
308 pub name: String,
309 pub label: String,
311 pub children: Vec<Block>,
312}
313
314#[derive(Debug, Clone, PartialEq)]
315pub struct SubstitutionDef {
316 pub name: String,
317 pub replacement: Vec<Block>,
318}
319
320#[derive(Debug, Clone, PartialEq)]
321pub struct SystemMessage {
322 pub level: u8,
323 pub text: String,
324 pub line: Option<usize>,
325 pub id: Option<usize>,
329}
330
331#[derive(Debug, Clone, PartialEq)]
332pub struct Raw {
333 pub format: String,
334 pub text: String,
335}
336
337#[derive(Debug, Clone, PartialEq)]
338pub struct Image {
339 pub uri: String,
340 pub alt: Option<String>,
341}
342
343#[derive(Debug, Clone, PartialEq)]
344pub struct Figure {
345 pub uri: String,
346 pub alt: Option<String>,
347 pub caption: Vec<Block>,
348}
349#[derive(Debug, Clone, PartialEq)]
350pub struct Container {
351 pub classes: Vec<String>,
352 pub children: Vec<Block>,
353}
354
355#[derive(Debug, Clone, PartialEq, Default)]
357pub struct Document {
358 pub children: Vec<Block>,
359 pub title: Option<Vec<Inline>>,
360 pub subtitle: Option<Vec<Inline>>,
361 pub header: Vec<Block>,
363 pub footer: Vec<Block>,
364 pub next_id: usize,
366 pub messages: Vec<SystemMessage>,
368 pub meta: Vec<(String, String)>,
370}
371
372pub fn slugify(text: &str) -> String {
375 let mut out = String::with_capacity(text.len());
376 let mut need_dash = false;
377 for c in text.to_lowercase().chars() {
378 if c.is_alphanumeric() {
379 if need_dash && !out.is_empty() {
380 out.push('-');
381 }
382 need_dash = false;
383 out.push(c);
384 } else {
385 need_dash = true;
386 }
387 }
388 out
389}
390
391pub fn inline_text(inlines: &[Inline]) -> String {
393 let mut s = String::new();
394 for i in inlines {
395 match i {
396 Inline::Text(t) | Inline::Literal(t) | Inline::Code(t) => s.push_str(t),
397 Inline::Problematic(p) => s.push_str(&p.text),
398 Inline::Emphasis(c) | Inline::Strong(c) | Inline::Cite(c) => {
399 s.push_str(&inline_text(c))
400 }
401 Inline::Reference(_, c) => s.push_str(&inline_text(c)),
402 Inline::PendingRef(p) => s.push_str(&inline_text(&p.text)),
403 Inline::FootnoteRef(f) => {
404 s.push_str(&f.label);
407 }
408 Inline::CitationRef(c) => s.push_str(&c.label),
409 Inline::SubstitutionRef(n) => {
410 s.push('|');
411 s.push_str(&n.name);
412 s.push('|');
413 }
414 Inline::Image { alt, .. } => {
415 if let Some(a) = alt {
416 s.push_str(a);
417 }
418 }
419 Inline::InlineTarget(_) => {}
420 }
421 }
422 s
423}
424
425pub fn block_text(b: &Block, out: &mut String) {
427 match b {
428 Block::Paragraph(c) => {
429 out.push_str(&inline_text(c));
430 out.push('\n');
431 }
432 Block::Section(s) => {
433 out.push_str(&inline_text(&s.title));
434 out.push('\n');
435 for c in &s.children {
436 block_text(c, out);
437 }
438 }
439 Block::BulletList(l) => {
440 for item in &l.items {
441 for b in item {
442 block_text(b, out);
443 }
444 }
445 }
446 Block::EnumList(l) => {
447 for item in &l.items {
448 for b in item {
449 block_text(b, out);
450 }
451 }
452 }
453 Block::DefinitionList(items) => {
454 for it in items {
455 out.push_str(&inline_text(&it.term));
456 out.push('\n');
457 for b in &it.definition {
458 block_text(b, out);
459 }
460 }
461 }
462 Block::FieldList(fields) => {
463 for f in fields {
464 out.push_str(&inline_text(&f.name));
465 out.push('\n');
466 for b in &f.body {
467 block_text(b, out);
468 }
469 }
470 }
471 Block::OptionList(groups) => {
472 for g in groups {
473 let mut first = true;
474 for o in &g.opts {
475 if !first {
476 out.push_str(", ");
477 }
478 first = false;
479 out.push_str(&o.name);
480 if let Some(a) = &o.arg {
481 out.push(o.sep.unwrap_or(' '));
482 out.push_str(a);
483 }
484 }
485 out.push('\n');
486 for b in &g.body {
487 block_text(b, out);
488 }
489 }
490 }
491 Block::BlockQuote(q) => {
492 for c in &q.children {
493 block_text(c, out);
494 }
495 if let Some(a) = &q.attribution {
496 out.push_str(&inline_text(a));
497 out.push('\n');
498 }
499 }
500 Block::LiteralBlock(lit) => {
501 for l in &lit.lines {
502 out.push_str(l);
503 out.push('\n');
504 }
505 }
506 Block::LineBlock(rows) => {
507 fn text(rows: &[LineRow], out: &mut String) {
508 for r in rows {
509 match r {
510 LineRow::Line(l) => {
511 out.push_str(&inline_text(l));
512 out.push('\n');
513 }
514 LineRow::Nested(n) => text(n, out),
515 }
516 }
517 }
518 text(rows, out);
519 }
520 Block::Table(t) => {
521 if !t.caption.is_empty() {
522 out.push_str(&inline_text(&t.caption));
523 out.push('\n');
524 }
525 for row in t.head.iter().chain(t.body.iter()) {
526 for cell in row {
527 for b in &cell.blocks {
528 block_text(b, out);
529 }
530 }
531 }
532 }
533 Block::Admonition(a) => {
534 if let Some(t) = &a.title {
535 out.push_str(&inline_text(t));
536 out.push('\n');
537 }
538 for c in &a.children {
539 block_text(c, out);
540 }
541 }
542 Block::Topic(t) => {
543 if !t.title.is_empty() {
544 out.push_str(&inline_text(&t.title));
545 out.push('\n');
546 }
547 for c in &t.children {
548 block_text(c, out);
549 }
550 }
551 Block::Container(c) => {
552 for b in &c.children {
553 block_text(b, out);
554 }
555 }
556 Block::ParsedLiteral(lines) => {
557 for l in lines {
558 out.push_str(&inline_text(l));
559 out.push('\n');
560 }
561 }
562 Block::Rubric(c) => {
563 out.push_str(&inline_text(c));
564 out.push('\n');
565 }
566 Block::Footnote(f) => {
567 let label = if f.symbol {
568 "*".to_string()
569 } else if let Some(n) = f.number {
570 n.to_string()
571 } else {
572 f.name.clone()
573 };
574 out.push_str(&label);
575 out.push('\n');
576 for c in &f.children {
577 block_text(c, out);
578 }
579 }
580 Block::Citation(c) => {
581 out.push_str(&c.label);
582 out.push('\n');
583 for b in &c.children {
584 block_text(b, out);
585 }
586 }
587 Block::SubstitutionDef(s) => {
588 for b in &s.replacement {
589 block_text(b, out);
590 }
591 }
592 Block::Image(img) => {
593 if let Some(a) = &img.alt {
594 out.push_str(a);
595 out.push('\n');
596 }
597 }
598 Block::Figure(f) => {
599 if let Some(a) = &f.alt {
600 out.push_str(a);
601 out.push('\n');
602 }
603 for b in &f.caption {
604 block_text(b, out);
605 }
606 }
607 Block::Comment(c) => {
608 if !c.trim().is_empty() {
609 out.push_str(c.trim());
610 out.push('\n');
611 }
612 }
613 Block::Transition => {}
614 Block::HyperlinkTarget(_) | Block::PendingContents { .. } => {}
615 Block::SystemMessage(m) => {
616 let typename = match m.level {
618 1 => "INFO",
619 2 => "WARNING",
620 3 => "ERROR",
621 4 => "SEVERE",
622 _ => "UNKNOWN",
623 };
624 match m.line {
625 Some(n) => out.push_str(&format!("<string>:{n}: ({typename}/{}) ", m.level)),
626 None => out.push_str(&format!("<string>: ({typename}/{}) ", m.level)),
627 }
628 out.push_str(&m.text);
629 out.push('\n');
630 }
631 Block::Raw(r) => {
632 out.push_str(&r.text);
633 out.push('\n');
634 }
635 }
636}
637
638impl Document {
639 pub fn astext(&self) -> String {
640 let mut out = String::new();
641 if let Some(t) = &self.title {
643 out.push_str(&inline_text(t));
644 out.push('\n');
645 }
646 if let Some(s) = &self.subtitle {
647 out.push_str(&inline_text(s));
648 out.push('\n');
649 }
650 for c in &self.header {
651 block_text(c, &mut out);
652 }
653 for c in &self.footer {
654 block_text(c, &mut out);
655 }
656 for c in &self.children {
657 block_text(c, &mut out);
658 }
659 out
660 }
661}