build_html/lib.rs
1//! This library is designed to provide a simple way to generate HTML strings from within Rust
2//! code. To generate HTML, this library uses the builder pattern; calls to add elements are
3//! repeatedly chained together to build up an HTML document. The struct is then flushed to a
4//! string which can be used elsewhere in your program.
5//!
6//! The simplest building block for this library is [`HtmlElement`]. This type allows creating
7//! elements in a structured way and affords the greatest flexibility. However for simpler use
8//! cases, the older [`HtmlContainer`] trait interface can be used, which provides a less verbose,
9//! function-based way to build up HTML strings. Regardless of how your build your HTML, the
10//! [`Html::to_html_string`] method allows you to render it to a `String`.
11//!
12//! The strings generated by this library are unformatted, but are not explicitly minimized.
13//! Whitespace passed into a string will generally be preserved. Note that escaping strings is also
14//! not automatic. You should use the [`escape_html`] function if you are displaying untrusted text.
15//!
16//! # Use Cases
17//! The primary intention of this library is to provide an easy way to build dynamic elements that
18//! can be injected into an HTML page or framework that is written in its own file. The advantage
19//! to this is that it allows you to write the majority of your HTML with modern editor features
20//! such as linting and syntax highlighting. You can use the standard library's `include_str!`
21//! macro to "import" your html file and the `format!` macro to "inject" your new element.
22//!
23//! ```
24//! use build_html::{HtmlElement, HtmlTag, Html};
25//!
26//! let element = HtmlElement::new(HtmlTag::Div)
27//! .with_child(
28//! HtmlElement::new(HtmlTag::ParagraphText)
29//! .with_child("Paragraph Text".into())
30//! .into()
31//! )
32//! .with_child(
33//! HtmlElement::new(HtmlTag::PreformattedText)
34//! .with_child("Preformatted Text".into())
35//! .into()
36//! )
37//! .to_html_string();
38//!
39//! assert_eq!(element, "<div><p>Paragraph Text</p><pre>Preformatted Text</pre></div>");
40//!
41//! ```
42//!
43//! However, if your page is very simple or the entire page is dynamic, you may want to create the
44//! entire thing from within your Rust code. To meet this use case, the library provides the
45//! [`HtmlPage`] struct. This struct implements the [`HtmlContainer`] interface, which can be used
46//! to easily add body content.
47//!
48//! ```
49//! use build_html::{HtmlPage, Html, HtmlContainer};
50//!
51//! let page = HtmlPage::new()
52//! .with_title("TITLE")
53//! .with_paragraph("PARAGRAPH")
54//! .to_html_string();
55//!
56//! assert_eq!(page, concat!(
57//! "<!DOCTYPE html><html>",
58//! "<head><title>TITLE</title></head>",
59//! "<body><p>PARAGRAPH</p></body>",
60//! "</html>"
61//! ));
62//! ```
63//! # `add_` vs `with_`
64//! Throughout this library, there are "pairs" of methods that use the `add_` and `with_` prefixes.
65//! The `add_` methods take a mutable reference and act via side effects, while the `with_` methods
66//! are self-consuming. While it makes the documentation a little noisy, this allows you to build
67//! up relatively complicated logic without having to continually re-assign to the same variable or
68//! create intermediate values:
69//!
70//! ```
71//! # use build_html::{HtmlElement, HtmlTag, Html, HtmlContainer};
72//! let mut root = HtmlElement::new(HtmlTag::Div)
73//! .with_child(HtmlElement::new(HtmlTag::Heading1).with_child("Counts".into()).into());
74//!
75//! for x in 1..=3 {
76//! // Here, we're adding by reference using an `add` method while also building
77//! // our inner element with a `with` method.
78//! root.add_child(HtmlElement::new(HtmlTag::Div).with_paragraph(x).into());
79//! }
80//!
81//! assert_eq!(root.to_html_string(), concat!(
82//! "<div><h1>Counts</h1>",
83//! "<div><p>1</p></div>",
84//! "<div><p>2</p></div>",
85//! "<div><p>3</p></div>",
86//! "</div>"
87//! ));
88//! ```
89//!
90//! # Extensibility
91//! In the event that you require additional tags or types not implemented in this library, you
92//! can achieve this using one of the escape hatches.
93//!
94//! If you are using `HtmlElement` directly, you can use [`HtmlElement::add_child`] with the `Raw`
95//! variant of `HtmlChild`. To make this even simpler, you can use the `into()` function to make
96//! the conversion nearly seamless:
97//!
98//! ```
99//! # use build_html::*;
100//! let tag = HtmlElement::new(HtmlTag::Div).with_child("RAW TEXT".into()).to_html_string();
101//! assert_eq!(tag, "<div>RAW TEXT</div>")
102//! ```
103//!
104//! If you are using the `HtmlContainer` interface, you can make a type implementing the [`Html`]
105//! interface and add it with [`HtmlContainer::add_html`] or add it directly as a string with
106//! [`HtmlContainer::add_raw`]. (Note that `HtmlElement` implements `HtmlContainer`, so these
107//! methods will work for that type too.)
108
109mod attributes;
110mod container;
111mod elements;
112mod html_container;
113mod html_page;
114mod table;
115mod tags;
116
117pub use self::container::{Container, ContainerType};
118pub use self::elements::{HtmlChild, HtmlElement};
119pub use self::html_container::HtmlContainer;
120pub use self::html_page::{HtmlPage, HtmlVersion};
121pub use self::table::{Table, TableCell, TableCellType, TableRow};
122pub use self::tags::HtmlTag;
123
124/// An element that can be converted to an HTML string
125///
126/// This trait is the centerpiece of the entire library: after building up an
127/// HTML structure, usually an [`HtmlPage`], [`to_html_string()`](crate::Html::to_html_string)
128/// is used to flush the structure to a string.
129pub trait Html: std::fmt::Debug {
130 /// Convert this element into an HTML string
131 ///
132 /// This is the method that ultimately flushes each HTML object to a string.
133 ///
134 /// # Example
135 /// ```
136 /// # use build_html::*;
137 /// let html = HtmlElement::new(HtmlTag::Div)
138 /// .with_paragraph("My p element")
139 /// .to_html_string();
140 ///
141 /// assert_eq!(html, "<div><p>My p element</p></div>");
142 /// ```
143 fn to_html_string(&self) -> String;
144}
145
146impl std::fmt::Display for dyn Html {
147 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
148 write!(f, "{}", self.to_html_string())
149 }
150}
151
152impl Html for String {
153 fn to_html_string(&self) -> String {
154 self.clone()
155 }
156}
157
158impl Html for &str {
159 fn to_html_string(&self) -> String {
160 self.to_string()
161 }
162}
163
164/// Escape the provided string.
165///
166/// All HTML special characters will be converted to their escaped versions. The output string
167/// should be safe to insert into an HTML document. Any embedded HTML tags will be rendered as
168/// text. It is important to *always* escape inputs from untrusted sources!
169///
170/// Implementation note: The list of escaped characters is pulled from [Svelte](https://github.com/sveltejs/svelte/blob/master/src/compiler/compile/utils/stringify.ts#L14).
171///
172/// # Example
173/// ```
174/// # use build_html::*;
175/// let html = HtmlElement::new(HtmlTag::Div)
176/// .with_paragraph(escape_html("My <p> element!"))
177/// .to_html_string();
178///
179/// assert_eq!(html, "<div><p>My <p> element!</p></div>");
180///
181/// ```
182pub fn escape_html(data: &str) -> String {
183 let mut escaped = String::with_capacity(data.len());
184 for c in data.chars() {
185 match c {
186 '"' => escaped.push_str("""),
187 '\'' => escaped.push_str("'"),
188 '&' => escaped.push_str("&"),
189 '<' => escaped.push_str("<"),
190 '>' => escaped.push_str(">"),
191 x => escaped.push(x),
192 }
193 }
194
195 escaped
196}