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 &lt;p&gt; 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("&quot;"),
187            '\'' => escaped.push_str("&#39;"),
188            '&' => escaped.push_str("&amp;"),
189            '<' => escaped.push_str("&lt;"),
190            '>' => escaped.push_str("&gt;"),
191            x => escaped.push(x),
192        }
193    }
194
195    escaped
196}