Document library
This commit is contained in:
parent
cb2a6da25c
commit
cfd83df713
4 changed files with 325 additions and 27 deletions
195
src/element.rs
195
src/element.rs
|
|
@ -1,7 +1,11 @@
|
|||
use std::collections::{btree_map::Entry, BTreeMap, HashMap};
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/syntax.html#elements-2>
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
/// The kind of an element.
|
||||
///
|
||||
/// Follows the [definitions from the HTML standard][spec].
|
||||
///
|
||||
/// [spec]: https://html.spec.whatwg.org/multipage/syntax.html#elements-2
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ElementKind {
|
||||
Void,
|
||||
Template,
|
||||
|
|
@ -11,27 +15,71 @@ pub enum ElementKind {
|
|||
Normal,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// A single bit of [`Element`] content.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Content {
|
||||
/// A raw string to be rendered without any checks.
|
||||
///
|
||||
/// Can also be constructed using [`Self::raw`].
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This is an escape hatch for including arbitrary text. Using it
|
||||
/// incorrectly may result in security vulnerabilities in the rendered HTML.
|
||||
Raw(String),
|
||||
/// Plain text.
|
||||
///
|
||||
/// Can also be constructed using [`Self::text`].
|
||||
Text(String),
|
||||
/// An HTML comment (`<!-- ... -->`).
|
||||
///
|
||||
/// Can also be constructed using [`Self::comment`].
|
||||
Comment(String),
|
||||
/// A child [`Element`].
|
||||
///
|
||||
/// Can also be constructed using [`Self::element`].
|
||||
Element(Element),
|
||||
}
|
||||
|
||||
impl Content {
|
||||
/// Construct [`Content::Raw`], a raw string to be rendered without any
|
||||
/// checks.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This is an escape hatch for including arbitrary text. Using it
|
||||
/// incorrectly may result in security vulnerabilities in the rendered HTML.
|
||||
pub fn raw(str: impl ToString) -> Self {
|
||||
Self::Raw(str.to_string())
|
||||
}
|
||||
|
||||
/// Construct [`Content::Text`], plain text.
|
||||
pub fn text(str: impl ToString) -> Self {
|
||||
Self::Text(str.to_string())
|
||||
}
|
||||
|
||||
/// Construct [`Content::Comment`], an HTML comment (`<!-- ... -->`).
|
||||
pub fn comment(str: impl ToString) -> Self {
|
||||
Self::Comment(str.to_string())
|
||||
}
|
||||
|
||||
/// Construct [`Content::Element`], a child [`Element`].
|
||||
///
|
||||
/// Instead of calling `Content::element(foo)`, you can also use
|
||||
/// `foo.into()`.
|
||||
pub fn element(e: impl Into<Element>) -> Self {
|
||||
Self::Element(e.into())
|
||||
}
|
||||
|
||||
/// Construct a doctype of the form `<!DOCTYPE html>`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use el::Content;
|
||||
/// let doctype = Content::doctype();
|
||||
/// assert_eq!(doctype, Content::raw("<!DOCTYPE html>"));
|
||||
/// ```
|
||||
pub fn doctype() -> Self {
|
||||
Self::raw("<!DOCTYPE html>")
|
||||
}
|
||||
|
|
@ -55,15 +103,61 @@ impl From<Element> for Content {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// An HTML element.
|
||||
///
|
||||
/// SVG and MathML elements are also modelled using this type.
|
||||
///
|
||||
/// Errors (e.g. illegal characters or an element of [`ElementKind::Void`]
|
||||
/// having children) are deferred until rendering and are not checked during
|
||||
/// element construction. See also [`crate::Render`] and [`crate::Error`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Element {
|
||||
/// The tag name of the element.
|
||||
pub name: String,
|
||||
/// What kind of element this is.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The element kind affects the correctness of the rendered output.
|
||||
/// Choosing an incorrect kind may result in security vulnerabilities in the
|
||||
/// rendered HTML. See [`ElementKind`] for more details.
|
||||
pub kind: ElementKind,
|
||||
/// The attributes (e.g. `id` or `class`) of the element.
|
||||
///
|
||||
/// This map does not take into account case insensitivity of attributes.
|
||||
/// Any attributes contained in the map will appear in the rendered output.
|
||||
pub attributes: BTreeMap<String, String>,
|
||||
/// The children of the element.
|
||||
pub children: Vec<Content>,
|
||||
}
|
||||
|
||||
impl Element {
|
||||
/// Create a new element of a specific [`ElementKind`].
|
||||
///
|
||||
/// See also [`Self::normal`] to create elements of kind
|
||||
/// [`ElementKind::Normal`].
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The element kind affects the correctness of the rendered output.
|
||||
/// Choosing an incorrect kind may result in security vulnerabilities in the
|
||||
/// rendered HTML. See [`ElementKind`] for more details.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use el::{Element, ElementKind, html, svg};
|
||||
///
|
||||
/// let p = Element::new("p", ElementKind::Normal);
|
||||
/// let link = Element::new("link", ElementKind::Void);
|
||||
/// let script = Element::new("script", ElementKind::RawText);
|
||||
/// let svg = Element::new("svg", ElementKind::Foreign);
|
||||
///
|
||||
/// assert_eq!(p, html::p(()));
|
||||
/// assert_eq!(link, html::link(()));
|
||||
/// assert_eq!(script, html::script(()));
|
||||
/// assert_eq!(svg, svg::svg(()));
|
||||
/// ```
|
||||
pub fn new(name: impl ToString, kind: ElementKind) -> Self {
|
||||
let mut name = name.to_string();
|
||||
if kind != ElementKind::Foreign {
|
||||
|
|
@ -78,26 +172,109 @@ impl Element {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a new element of the kind [`ElementKind::Normal`].
|
||||
///
|
||||
/// `Element::normal(foo)` is equivalent to calling `Element::new(foo,
|
||||
/// ElementKind::Normal)`.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The element kind affects the correctness of the rendered output.
|
||||
/// Choosing an incorrect kind may result in security vulnerabilities in the
|
||||
/// rendered HTML. See [`ElementKind`] for more details.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use el::{Element, ElementKind};
|
||||
/// let element = Element::normal("custom");
|
||||
/// assert_eq!(element.kind, ElementKind::Normal);
|
||||
/// ```
|
||||
pub fn normal(name: impl ToString) -> Self {
|
||||
Self::new(name, ElementKind::Normal)
|
||||
}
|
||||
|
||||
/// Add components to the element in-place.
|
||||
///
|
||||
/// To add multiple components, either call this function repeatedly or use
|
||||
/// a type like tuples, arrays, [`Vec`], [`Option`], [`Result`] to combine
|
||||
/// multiple components. See [`ElementComponent`] for more info.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use el::{Attr, html::*};
|
||||
/// let mut element = p(());
|
||||
///
|
||||
/// // Adding single components
|
||||
/// element.add("some text");
|
||||
/// element.add(Attr::class("foo"));
|
||||
///
|
||||
/// // Adding multiple components
|
||||
/// element.add((Attr::id("bar"), " ", em("and"), " some more text"));
|
||||
/// ```
|
||||
pub fn add(&mut self, c: impl ElementComponent) {
|
||||
c.add_to_element(self);
|
||||
}
|
||||
|
||||
/// A more builder-pattern-like version of [`Self::add`].
|
||||
///
|
||||
/// Instead of a mutable reference, this function takes ownership of the
|
||||
/// element before returning it again. This can be more ergonomic in some
|
||||
/// cases.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use el::{Attr, html::*};
|
||||
///
|
||||
/// let element = p(())
|
||||
/// // Adding single components
|
||||
/// .with("some text")
|
||||
/// .with(Attr::class("foo"))
|
||||
/// // Adding multiple components
|
||||
/// .with((Attr::id("bar"), " ", em("and"), " some more text"));
|
||||
/// ```
|
||||
pub fn with(mut self, c: impl ElementComponent) -> Self {
|
||||
self.add(c);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A component can add itself to an [`Element`] by modifying it.
|
||||
///
|
||||
/// A component usually represents either a bit of content or an attribute for
|
||||
/// the element it is being added to. Some components (e.g. tuples, arrays,
|
||||
/// [`Vec`], [`Option`], [`Result`]) consist of further components. This creates
|
||||
/// a flexible API for building [`Element`]s:
|
||||
///
|
||||
/// ```
|
||||
/// use el::{Attr, Render, html::*};
|
||||
/// let p = p((
|
||||
/// Attr::id("foo"),
|
||||
/// Attr::class("bar"),
|
||||
/// Attr::class("baz"),
|
||||
/// "Hello ", em("world"), "!",
|
||||
/// ));
|
||||
/// assert_eq!(
|
||||
/// p.render_to_string().unwrap(),
|
||||
/// r#"<p class="bar baz" id="foo">Hello <em>world</em>!</p>"#,
|
||||
/// );
|
||||
/// ```
|
||||
pub trait ElementComponent {
|
||||
/// Add a component to an element, consuming the component in the process.
|
||||
fn add_to_element(self, element: &mut Element);
|
||||
}
|
||||
|
||||
// Attributes
|
||||
|
||||
/// An element attribute, used during [`Element`] construction.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use el::{Attr, html::*};
|
||||
/// let p = p(Attr::class("foo"));
|
||||
/// assert_eq!(p.attributes["class"], "foo");
|
||||
/// ```
|
||||
pub struct Attr {
|
||||
name: String,
|
||||
value: String,
|
||||
|
|
@ -292,11 +469,11 @@ element_component_tuple!(C1, C2, C3, C4, C5, C6, C7);
|
|||
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8);
|
||||
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9);
|
||||
|
||||
/// An HTML document.
|
||||
/// A full HTML document including doctype.
|
||||
///
|
||||
/// A `Document(el)` is basically the same as `[Content::doctype(), el.into()]`
|
||||
/// for the purposes of the [`crate::Render`] trait.
|
||||
#[derive(Clone)]
|
||||
/// for the purposes of the [`Render`][crate::Render] trait.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Document(pub Element);
|
||||
|
||||
impl From<Element> for Document {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue