html_outliner/
outline.rs

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}