1use std::fmt::{self, Display, Formatter, Write};
2
3use crate::OutlineStructure;
4
5const EXTRA_INDENT_WIDTH: usize = 1;
6
7#[derive(Debug, Clone, Default)]
8pub struct Outline {
9 pub text: Option<String>,
10 pub sub_outlines: Vec<Outline>,
11}
12
13impl Outline {
14 #[inline]
15 pub fn parse_html<S: AsRef<str>>(html: S, max_depth: usize) -> Outline {
16 OutlineStructure::parse_html(html, max_depth).into()
17 }
18}
19
20impl From<OutlineStructure> for Outline {
21 #[inline]
22 fn from(os: OutlineStructure) -> Self {
23 if os.sectioning_type.is_heading() {
24 Outline {
25 text: os.heading.map(|heading| heading.into()),
26 sub_outlines: Vec::new(),
27 }
28 } else {
29 let mut sub_outlines = Vec::new();
30
31 let mut stack = vec![];
32 let mut levels: Vec<u8> = vec![];
33
34 for sub_os in os.sub_outline_structures.into_iter().rev() {
35 if sub_os.sectioning_type.is_heading() {
36 let heading = sub_os.heading.unwrap();
37 let heading_level = heading.get_start_level();
38
39 let mut outline = Outline {
40 text: Some(heading.into()),
41 sub_outlines: Vec::new(),
42 };
43
44 while let Some(level) = levels.pop() {
45 if level > heading_level {
46 outline.sub_outlines.push(stack.pop().unwrap());
47 } else {
48 levels.push(level);
49
50 break;
51 }
52 }
53
54 levels.push(heading_level);
55 stack.push(outline);
56 } else {
57 stack.push(sub_os.into());
58 }
59 }
60
61 let text = if let Some(heading) = os.heading {
62 let heading_level = heading.get_start_level();
63
64 let need_flatten = {
65 let mut b = false;
66
67 for level in levels.iter().copied().rev() {
68 if level >= heading_level {
69 b = true;
70 }
71 }
72
73 b
74 };
75
76 if need_flatten {
77 let mut outline = Outline {
78 text: Some(heading.into()),
79 sub_outlines: Vec::new(),
80 };
81
82 while let Some(level) = levels.pop() {
83 if level > heading_level {
84 outline.sub_outlines.push(stack.pop().unwrap());
85 } else {
86 levels.push(level);
87
88 break;
89 }
90 }
91
92 levels.push(heading_level);
93 stack.push(outline);
94
95 None
96 } else {
97 Some(heading.into())
98 }
99 } else if os.sectioning_type.is_sectioning_content_type() {
100 Some(format!("Untitled {}", os.sectioning_type.as_str()))
101 } else {
102 None
103 };
104
105 while let Some(sub_os) = stack.pop() {
106 sub_outlines.push(sub_os);
107 }
108
109 Outline {
110 text,
111 sub_outlines,
112 }
113 }
114 }
115}
116
117impl Display for Outline {
118 #[inline]
119 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
120 format(f, self, 1, 0)
121 }
122}
123
124fn format(
125 f: &mut Formatter<'_>,
126 outline: &Outline,
127 number: usize,
128 indent: usize,
129) -> Result<(), fmt::Error> {
130 let new_ident = if let Some(text) = outline.text.as_ref() {
131 if indent > 0 {
132 f.write_char('\n')?;
133
134 for _ in 0..indent {
135 f.write_char(' ')?;
136 }
137 } else if number > 1 {
138 f.write_char('\n')?;
139 }
140
141 f.write_fmt(format_args!("{}. ", number))?;
142 f.write_str(text.as_str())?;
143
144 indent + count_digit(outline.sub_outlines.len()) + 2 + EXTRA_INDENT_WIDTH
145 } else {
146 indent
147 };
148
149 for (i, sub_outline) in outline.sub_outlines.iter().enumerate() {
150 format(f, sub_outline, i + 1, new_ident)?;
151 }
152
153 Ok(())
154}
155
156#[inline]
157fn count_digit(n: usize) -> usize {
158 (n as f64).log10().floor() as usize + 1
159}