lexa_framework/view/markup/
node.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ Copyright: (c) 2023, Mike 'PhiSyX' S. (https://github.com/PhiSyX)         ┃
3// ┃ SPDX-License-Identifier: MPL-2.0                                          ┃
4// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
5// ┃                                                                           ┃
6// ┃  This Source Code Form is subject to the terms of the Mozilla Public      ┃
7// ┃  License, v. 2.0. If a copy of the MPL was not distributed with this      ┃
8// ┃  file, You can obtain one at https://mozilla.org/MPL/2.0/.                ┃
9// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
10
11use core::fmt;
12use std::collections::HashMap;
13use std::path;
14use std::sync::Mutex;
15
16mod comment;
17mod doctype;
18mod element;
19mod fragment;
20mod text;
21
22// ------ //
23// Static //
24// ------ //
25
26lazy_static::lazy_static! {
27	pub static ref MEMOIZE_FILE: Mutex<
28		HashMap<path::PathBuf, String>
29	> = Mutex::new(HashMap::default());
30}
31
32// ----------- //
33// Énumération //
34// ----------- //
35
36pub enum Node {
37	/// Commentaire HTML.
38	Comment(comment::CommentNode),
39	/// Type de document HTML.
40	Doctype(doctype::DoctypeNode),
41	/// Fragments HTML.
42	Fragment(fragment::FragmentNode),
43	/// Elements HTML.
44	Element(element::ElementNode),
45	/// Noeud texte.
46	Text(text::TextNode),
47	/// Noeud texte (json).
48	Json(text::JsonTextNode),
49	/// Noeud texte non sûr
50	UnsafeHtml(text::DangerousTextNode),
51}
52
53// -------------- //
54// Implémentation //
55// -------------- //
56
57// Comment
58impl Node {
59	pub fn create_comment(comment: impl ToString) -> Self {
60		Self::Comment(comment::CommentNode {
61			content: comment.to_string(),
62		})
63	}
64}
65
66// Doctype
67impl Node {
68	pub fn create_doctype(public_identifier: impl ToString) -> Self {
69		Self::Doctype(doctype::DoctypeNode {
70			public_identifier: public_identifier.to_string(),
71		})
72	}
73}
74
75// Fragment
76impl Node {
77	pub fn create_fragment(children: Vec<Node>) -> Self {
78		Self::Fragment(fragment::FragmentNode { children })
79	}
80}
81
82// Element
83impl Node {
84	pub fn create_element(
85		tag_name: impl ToString,
86		attributes: Vec<(String, Option<String>)>,
87		children: Vec<Node>,
88	) -> Self {
89		let children = if children.is_empty() {
90			None
91		} else {
92			Some(children)
93		};
94		Self::Element(element::ElementNode {
95			tag_name: tag_name.to_string(),
96			attributes,
97			children,
98		})
99	}
100
101	pub fn create_void_element(
102		tag_name: impl ToString,
103		attributes: Vec<(String, Option<String>)>,
104	) -> Self {
105		Self::Element(element::ElementNode {
106			tag_name: tag_name.to_string(),
107			attributes,
108			children: None,
109		})
110	}
111}
112
113// Text
114impl Node {
115	pub fn create_text(text: impl ToString) -> Self {
116		Self::Text(text::TextNode {
117			text: text.to_string(),
118		})
119	}
120
121	pub fn create_json(text: impl ToString) -> Self {
122		Self::Json(text::JsonTextNode {
123			text: text.to_string(),
124		})
125	}
126}
127
128// Unsafe HTML
129impl Node {
130	pub fn create_unsafe_html(raw_text: impl ToString) -> Self {
131		Self::UnsafeHtml(text::DangerousTextNode {
132			raw_text: raw_text.to_string(),
133		})
134	}
135
136	pub fn create_unsafe_html_from_file(file: impl AsRef<path::Path>) -> Self {
137		let mut memoize = MEMOIZE_FILE.lock().expect("cache guard");
138
139		if let Some(content_of_file) = memoize.get(file.as_ref()) {
140			return Self::UnsafeHtml(text::DangerousTextNode {
141				raw_text: content_of_file.to_owned(),
142			});
143		}
144
145		let raw_text = std::fs::read_to_string(&file).unwrap_or_else(|_| {
146			panic!("le fichier « {} » n'existe pas.", file.as_ref().display())
147		});
148		memoize.insert(file.as_ref().to_owned(), raw_text.clone());
149		Self::UnsafeHtml(text::DangerousTextNode { raw_text })
150	}
151}
152
153// -------- //
154// Fonction //
155// -------- //
156
157fn with_children(
158	f: &mut fmt::Formatter<'_>,
159	children: &[Node],
160	is_fragment: bool,
161) -> fmt::Result {
162	if f.alternate() {
163		let mut children = children.iter();
164
165		if is_fragment {
166			if let Some(first_child) = children.next() {
167				write!(f, "{first_child:#}")?;
168
169				for child in children {
170					write!(f, "\n{child:#}")?;
171				}
172			}
173		} else {
174			for child in children.map(|child| format!("{child:#}")) {
175				for line in child.lines() {
176					write!(f, "\n\t{line}")?;
177				}
178			}
179			writeln!(f)?;
180		}
181	} else {
182		use std::fmt::Display;
183
184		for child in children {
185			child.fmt(f)?;
186		}
187	}
188
189	Ok(())
190}
191
192// -------------- //
193// Implémentation // -> Interface
194// -------------- //
195
196impl Default for Node {
197	fn default() -> Self {
198		Self::create_fragment(vec![])
199	}
200}
201
202impl fmt::Display for Node {
203	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204		match &self {
205			| Self::Comment(comment) => comment.fmt(f),
206			| Self::Doctype(doctype) => doctype.fmt(f),
207			| Self::Fragment(fragment) => fragment.fmt(f),
208			| Self::Element(element) => element.fmt(f),
209			| Self::Text(text) => text.fmt(f),
210			| Self::Json(text) => text.fmt(f),
211			| Self::UnsafeHtml(danger) => danger.fmt(f),
212		}
213	}
214}
215
216impl<It, F> From<It> for Node
217where
218	It: IntoIterator<Item = F>,
219	F: Into<Self>,
220{
221	fn from(it: It) -> Self {
222		Self::Fragment(it.into())
223	}
224}
225
226impl From<comment::CommentNode> for Node {
227	fn from(comment_node: comment::CommentNode) -> Self {
228		Self::Comment(comment_node)
229	}
230}
231
232impl From<doctype::DoctypeNode> for Node {
233	fn from(doctype_node: doctype::DoctypeNode) -> Self {
234		Self::Doctype(doctype_node)
235	}
236}
237
238impl From<fragment::FragmentNode> for Node {
239	fn from(fragment_node: fragment::FragmentNode) -> Self {
240		Self::Fragment(fragment_node)
241	}
242}
243
244impl From<element::ElementNode> for Node {
245	fn from(element_node: element::ElementNode) -> Self {
246		Self::Element(element_node)
247	}
248}
249
250impl From<text::TextNode> for Node {
251	fn from(text_node: text::TextNode) -> Self {
252		Self::Text(text_node)
253	}
254}