1use std::collections::HashMap;
2
3#[derive(Debug, PartialEq, Eq, Clone, Default)]
5pub enum NodeType {
6 Html,
7 Head,
8 Style,
9 Link,
10 Script,
11 Meta,
12 Title,
13 Body,
14 H1,
15 H2,
16 H3,
17 H4,
18 H5,
19 H6,
20 P,
21 Div,
22 Strong,
23 Em,
24 A,
25 Ul,
26 Ol,
27 Li,
28 Pre,
29 Code,
30 Hr,
31 Br,
32 Blockquote,
33 #[default]
34 Text,
35 Comment,
36 Unknown(String),
37}
38
39impl NodeType {
40 pub fn is_special_tag(&self) -> bool {
41 use NodeType::*;
42 matches!(self, Blockquote | Ul | Ol)
43 }
44
45 pub fn from_tag_str(input: &str) -> Self {
46 use NodeType::*;
47 match input.to_lowercase().as_str() {
48 "html" => Html,
49 "head" => Head,
50 "style" => Style,
51 "link" => Link,
52 "script" => Script,
53 "meta" => Meta,
54 "title" => Title,
55 "body" => Body,
56 "h1" => H1,
57 "h2" => H2,
58 "h3" => H3,
59 "h4" => H4,
60 "h5" => H5,
61 "h6" => H6,
62 "p" => P,
63 "div" => Div,
64 "strong" => Strong,
65 "em" => Em,
66 "a" => A,
67 "ul" => Ul,
68 "ol" => Ol,
69 "li" => Li,
70 "pre" => Pre,
71 "code" => Code,
72 "hr" => Hr,
73 "br" => Br,
74 "blockquote" => Blockquote,
75 unknown => Unknown(unknown.to_string()),
76 }
77 }
78}
79
80#[derive(Debug, PartialEq, Eq, Clone, Default)]
82pub struct Node {
83 pub tag_name: Option<NodeType>,
84 pub value: Option<String>,
85 pub attributes: Option<Attributes>,
86 pub within_special_tag: Option<Vec<NodeType>>,
87 pub children: Vec<Node>,
88}
89
90impl Node {
91 pub fn is_in_special_tag(&self, tags: &[NodeType]) -> bool {
93 if let Some(within_special_tag) = &self.within_special_tag {
94 within_special_tag.iter().any(|tag| tags.contains(tag))
95 } else {
96 false
97 }
98 }
99
100 pub fn leading_spaces(&self) -> String {
103 let ul_or_ol = &[NodeType::Ul, NodeType::Ol];
104 if let Some(within_special_tag) = &self.within_special_tag {
105 " ".repeat(
106 (within_special_tag
107 .iter()
108 .filter(|tag| ul_or_ol.contains(tag))
109 .count()
110 - 1)
111 * 2,
112 )
113 } else {
114 String::new()
115 }
116 }
117
118 pub fn new(
120 tag_name: Option<NodeType>,
121 value: Option<String>,
122 attributes: Option<Attributes>,
123 within_special_tag: Option<Vec<NodeType>>,
124 children: Vec<Node>,
125 ) -> Self {
126 Node {
127 tag_name,
128 value,
129 attributes,
130 within_special_tag,
131 children,
132 }
133 }
134}
135
136#[derive(Debug, PartialEq, Eq, Clone, Default)]
138pub struct Attributes {
139 pub(crate) id: Option<String>,
140 pub(crate) class: Option<String>,
141 pub(crate) href: Option<String>,
142 pub(crate) attributes: HashMap<String, AttributeValues>,
143}
144
145impl Attributes {
146 pub fn new() -> Self {
148 Attributes {
149 id: None,
150 class: None,
151 href: None,
152 attributes: HashMap::new(),
153 }
154 }
155
156 pub fn get(&self, key: &str) -> Option<AttributeValues> {
158 match key {
159 "id" => self.id.as_ref().map(|id| AttributeValues::from(id.clone())),
160 "class" => self
161 .class
162 .as_ref()
163 .map(|class| AttributeValues::from(class.clone())),
164 _ => self.attributes.get(key).cloned(),
165 }
166 }
167
168 pub fn get_id(&self) -> Option<&String> {
170 self.id.as_ref()
171 }
172
173 pub fn get_class(&self) -> Option<&String> {
175 self.class.as_ref()
176 }
177
178 pub fn get_href(&self) -> Option<String> {
180 self.get("href").and_then(|value| match value {
181 AttributeValues::String(href) => Some(href),
182 _ => None,
183 })
184 }
185
186 pub fn contains(&self, key: &str) -> bool {
188 match key {
189 "id" => self.id.is_some(),
190 "class" => self.class.is_some(),
191 _ => self.attributes.contains_key(key),
192 }
193 }
194
195 pub fn insert(&mut self, key: String, value: AttributeValues) {
197 match key.as_str() {
198 "id" => self.id = Some(value.to_string()),
199 "class" => self.class = Some(value.to_string()),
200 _ => {
201 self.attributes.insert(key, value);
202 }
203 }
204 }
205
206 pub fn is_empty(&self) -> bool {
208 self.id.is_none() && self.class.is_none() && self.attributes.is_empty()
209 }
210
211 pub fn from(vec: Vec<(String, AttributeValues)>) -> Self {
213 let mut attributes = Attributes::new();
214 for (key, value) in vec {
215 attributes.insert(key, value);
216 }
217 attributes
218 }
219}
220
221#[derive(Debug, PartialEq, Eq, Clone)]
223pub enum AttributeValues {
224 String(String),
226 Bool(bool),
228 Number(i32),
230}
231
232impl AttributeValues {
233 pub fn from<T: Into<AttributeValues>>(value: T) -> Self {
246 value.into()
247 }
248}
249
250impl From<String> for AttributeValues {
251 fn from(value: String) -> Self {
253 AttributeValues::String(value)
254 }
255}
256
257impl From<&str> for AttributeValues {
258 fn from(value: &str) -> Self {
260 AttributeValues::String(value.to_string())
261 }
262}
263
264impl From<bool> for AttributeValues {
265 fn from(value: bool) -> Self {
267 AttributeValues::Bool(value)
268 }
269}
270
271impl From<i32> for AttributeValues {
272 fn from(value: i32) -> Self {
274 AttributeValues::Number(value)
275 }
276}
277
278impl std::fmt::Display for AttributeValues {
279 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
280 match self {
281 AttributeValues::String(value) => write!(f, "{}", value),
282 AttributeValues::Bool(value) => write!(f, "{}", value),
283 AttributeValues::Number(value) => write!(f, "{}", value),
284 }
285 }
286}
287
288#[derive(Debug, Default)]
289pub struct ToMdConfig {
290 pub ignore_rendering: Vec<NodeType>,
291}