hypertext_garnish/
html.rs1use serde::Deserialize;
2
3#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
4pub struct Attribute {
5 name: String,
6 value: Option<String>,
7}
8
9impl Attribute {
10 pub fn new(name: String, value: String) -> Self {
11 Self { name, value: Some(value) }
12 }
13
14 pub fn toggle(name: String) -> Self {
15 Self { name, value: None }
16 }
17}
18
19impl ToString for Attribute {
20 fn to_string(&self) -> String {
21 match &self.value {
22 Some(value) => {
23 format!("{}=\"{}\"", self.name, value)
24 }
25 None => self.name.to_string()
26 }
27 }
28}
29
30#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
31pub enum Node {
32 Text(String),
33 Comment(String),
34 Element {
35 tag: String,
36 #[serde(default)]
37 attributes: Vec<Attribute>,
38 #[serde(default)]
39 children: Vec<Node>,
40 },
41}
42
43impl Node {
44 pub fn element(tag: String, attributes: Vec<Attribute>, children: Vec<Node>) -> Self {
45 Self::Element {
46 tag,
47 attributes,
48 children,
49 }
50 }
51
52 pub fn text(text: String) -> Self {
53 Self::Text(text)
54 }
55
56 pub fn comment(text: String) -> Self {
57 Self::Comment(text)
58 }
59}
60
61impl ToString for Node {
62 fn to_string(&self) -> String {
63 match self {
64 Node::Text(s) => s.clone(),
65 Node::Comment(s) => format!("<!-- {} -->", s),
66 Node::Element {
67 tag,
68 attributes,
69 children,
70 } => {
71 let child_text = children
72 .iter()
73 .map(|c| c.to_string())
74 .collect::<Vec<String>>()
75 .join("");
76
77 let open_tag = match attributes.is_empty() {
78 true => format!("<{}>", tag),
79 false => {
80 let attribute_text = attributes
81 .iter()
82 .map(Attribute::to_string)
83 .collect::<Vec<String>>()
84 .join(" ");
85 format!("<{} {}>", tag, attribute_text)
86 }
87 };
88 format!("{}{}</{}>", open_tag, child_text, tag)
89 }
90 }
91 }
92}
93
94#[cfg(test)]
95mod to_string {
96 use crate::html::{Attribute, Node};
97
98 #[test]
99 fn single_element() {
100 let element = Node::element("body".to_string(), vec![], vec![]);
101
102 assert_eq!(element.to_string(), "<body></body>");
103 }
104
105 #[test]
106 fn single_element_with_text() {
107 let element = Node::element(
108 "body".to_string(),
109 vec![],
110 vec![Node::text("Some text".to_string())],
111 );
112
113 assert_eq!(element.to_string(), "<body>Some text</body>");
114 }
115
116 #[test]
117 fn attribute() {
118 let attr = Attribute::new("class".to_string(), "my-class".to_string());
119 assert_eq!(attr.to_string(), "class=\"my-class\"")
120 }
121
122 #[test]
123 fn attribute_no_value() {
124 let attr = Attribute::toggle("class".to_string());
125 assert_eq!(attr.to_string(), "class")
126 }
127
128 #[test]
129 fn single_element_with_attributes() {
130 let element = Node::element(
131 "body".to_string(),
132 vec![
133 Attribute::new("class".to_string(), "my-class".to_string()),
134 Attribute::new("width".to_string(), "100".to_string()),
135 ],
136 vec![],
137 );
138
139 assert_eq!(
140 element.to_string(),
141 "<body class=\"my-class\" width=\"100\"></body>"
142 );
143 }
144
145 #[test]
146 fn child_elements() {
147 let element = Node::element(
148 "body".to_string(),
149 vec![],
150 vec![Node::element(
151 "h1".to_string(),
152 vec![],
153 vec![Node::text("Heading".to_string())],
154 )],
155 );
156
157 assert_eq!(element.to_string(), "<body><h1>Heading</h1></body>");
158 }
159
160 #[test]
161 fn child_elements_then_text() {
162 let element = Node::element(
163 "body".to_string(),
164 vec![],
165 vec![
166 Node::element(
167 "h1".to_string(),
168 vec![],
169 vec![Node::text("Heading".to_string())],
170 ),
171 Node::text("Some text".to_string()),
172 ],
173 );
174
175 assert_eq!(
176 element.to_string(),
177 "<body><h1>Heading</h1>Some text</body>"
178 );
179 }
180
181 #[test]
182 fn comment() {
183 let element = Node::comment("Some comments".to_string());
184
185 assert_eq!(element.to_string(), "<!-- Some comments -->");
186 }
187}