html_outliner/
outline_structure.rs1use scraper::{ElementRef, Html};
2
3use crate::{heading::*, sectioning_type::SectioningType};
4
5const SECTIONING_ROOTS: [&str; 7] =
6 ["blockquote", "body", "details", "dialog", "fieldset", "figure", "td"];
7
8#[derive(Debug, Clone)]
10pub struct OutlineStructure {
11 pub sectioning_type: SectioningType,
13 pub heading: Option<Heading>,
15 pub sub_outline_structures: Vec<OutlineStructure>,
17}
18
19impl OutlineStructure {
20 #[inline]
21 pub(crate) fn new(sectioning_type: SectioningType) -> OutlineStructure {
22 OutlineStructure {
23 sectioning_type,
24 heading: None,
25 sub_outline_structures: Vec::new(),
26 }
27 }
28
29 #[inline]
30 pub fn parse_html<S: AsRef<str>>(html: S, max_depth: usize) -> OutlineStructure {
32 let document = Html::parse_document(html.as_ref());
33 let root_element = document.root_element();
34
35 if let Some(outline_structure) =
36 create_outline_structure_finding_body(root_element, 0, max_depth)
37 {
38 outline_structure
39 } else {
40 create_outline_structure(SectioningType::Root, root_element, 0, max_depth)
41 .unwrap_or_else(|| OutlineStructure::new(SectioningType::Root))
42 }
43 }
44}
45
46pub(crate) fn create_outline_structure(
47 sectioning_type: SectioningType,
48 element: ElementRef<'_>,
49 depth: usize,
50 max_depth: usize,
51) -> Option<OutlineStructure> {
52 if depth > max_depth {
53 return None;
54 }
55
56 let mut outline_structure = OutlineStructure::new(sectioning_type);
57
58 let mut find_heading = true;
60
61 for child in element.child_elements() {
62 let local_name = child.value().name();
63
64 if SECTIONING_ROOTS.binary_search(&local_name).is_ok() {
66 continue;
67 }
68
69 if let Some(sub_sectioning_type) = SectioningType::from_sectioning_content_tag(local_name) {
70 if let Some(sub_outline_structure) =
71 create_outline_structure(sub_sectioning_type, child, depth + 1, max_depth)
72 {
73 outline_structure.sub_outline_structures.push(sub_outline_structure);
74 }
75 } else if let Some(heading) = create_heading(child, depth + 1, max_depth) {
76 if find_heading {
77 outline_structure.heading = Some(heading);
78 } else {
79 let mut sub_outline_structure = OutlineStructure::new(SectioningType::Heading);
80
81 sub_outline_structure.heading = Some(heading);
82
83 outline_structure.sub_outline_structures.push(sub_outline_structure);
84 }
85 } else {
86 if let Some(sub_outline_structure) =
87 create_outline_structure(SectioningType::Root, child, depth + 1, max_depth)
88 {
89 let has_sub_outline_structures =
91 !sub_outline_structure.sub_outline_structures.is_empty();
92 let has_heading = sub_outline_structure.heading.is_some();
93
94 if let Some(heading) = sub_outline_structure.heading {
95 if find_heading {
96 outline_structure.heading = Some(heading);
97 } else {
98 let mut sub_outline_structure =
99 OutlineStructure::new(SectioningType::Heading);
100
101 sub_outline_structure.heading = Some(heading);
102
103 outline_structure.sub_outline_structures.push(sub_outline_structure);
104 }
105 }
106
107 for os in sub_outline_structure.sub_outline_structures {
108 outline_structure.sub_outline_structures.push(os);
109 }
110
111 if has_heading || has_sub_outline_structures {
112 find_heading = false;
113 }
114 }
115
116 continue;
117 }
118
119 find_heading = false;
120 }
121
122 Some(outline_structure)
123}
124
125pub(crate) fn create_outline_structure_finding_body(
126 element: ElementRef<'_>,
127 depth: usize,
128 max_depth: usize,
129) -> Option<OutlineStructure> {
130 if depth > max_depth {
131 return None;
132 }
133
134 if element.value().name() == "body" {
135 return Some(
136 create_outline_structure(SectioningType::Body, element, depth, max_depth)
137 .unwrap_or_else(|| OutlineStructure::new(SectioningType::Root)),
138 );
139 }
140
141 for child in element.child_elements() {
142 if let Some(outline_structure) =
143 create_outline_structure_finding_body(child, depth + 1, max_depth)
144 {
145 return Some(outline_structure);
146 }
147 }
148
149 None
150}