//! DOM and virtual DOM types. use std::fmt::Display; use std::marker::PhantomData; use crate::elements::{FlowContent, PhrasingContent}; use crate::OutputType; use htmlescape::encode_minimal; /// A boxed DOM tree, as returned from the `html!` macro. /// /// # Examples /// /// ``` /// # use axohtml::html; /// # use axohtml::dom::DOMTree; /// # fn main() { /// let tree: DOMTree = html!( ///
///

"Hello Joe!"

///
/// ); /// let rendered_tree: String = tree.to_string(); /// # } /// ``` pub type DOMTree = Box>; /// An untyped representation of an HTML node. /// /// This structure is designed to be easily walked in order to render a DOM tree /// or diff against an existing tree. It's the stringly typed version of /// [`Node`][Node]. /// /// It can be constructed from any ['Node'][Node]: /// /// ```no_compile /// html!( ///

"But how does she ""eat?"

/// ).vnode() /// ``` /// /// [Node]: trait.Node.html pub enum VNode<'a, T: OutputType + 'a> { Text(&'a str), UnsafeText(&'a str), Element(VElement<'a, T>), } /// An untyped representation of an HTML element. pub struct VElement<'a, T: OutputType + 'a> { pub name: &'static str, pub attributes: Vec<(&'static str, String)>, pub events: &'a mut T::Events, pub children: Vec>, } /// Trait for rendering a typed HTML node. /// /// All [HTML elements][elements] implement this, in addition to /// [`TextNode`][TextNode]. /// /// It implements [`Display`][Display] for rendering to strings, and the /// [`vnode()`][vnode] method can be used to render a virtual DOM structure. /// /// [Display]: https://doc.rust-lang.org/std/fmt/trait.Display.html /// [TextNode]: struct.TextNode.html /// [elements]: ../elements/index.html /// [vnode]: #tymethod.vnode pub trait Node: Display + Send { /// Render the node into a [`VNode`][VNode] tree. /// /// [VNode]: enum.VNode.html fn vnode(&mut self) -> VNode; } impl IntoIterator for Box> where T: OutputType + Send, { type Item = Box>; type IntoIter = std::vec::IntoIter>>; fn into_iter(self) -> Self::IntoIter { vec![self].into_iter() } } /// Trait for querying a typed HTML element. /// /// All [HTML elements][elements] implement this. /// /// [elements]: ../elements/index.html pub trait Element: Node { /// Get the name of the element. fn name() -> &'static str; /// Get a list of the attribute names for this element. /// /// This includes only the typed attributes, not any `data-` attributes /// defined on this particular element instance. /// /// This is probably not useful unless you're the `html!` macro. fn attribute_names() -> &'static [&'static str]; /// Get a list of the element names of required children for this element. /// /// This is probably not useful unless you're the `html!` macro. fn required_children() -> &'static [&'static str]; /// Get a list of the defined attribute pairs for this element. /// /// This will convert attribute values into strings and return a vector of /// key/value pairs. fn attributes(&self) -> Vec<(&'static str, String)>; } /// An HTML text node. pub struct TextNode(String, PhantomData); /// Macro for creating text nodes. /// /// Returns a boxed text node of type `Box`. /// /// These can be created inside the `html!` macro directly by using string /// literals. This macro is useful for creating text macros inside code blocks. /// /// # Examples /// /// ```no_compile /// html!( ///

{ text!("Hello Joe!") }

/// ) /// ``` /// /// ```no_compile /// html!( ///

{ text!("Hello {}!", "Robert") }

/// ) /// ``` #[macro_export] macro_rules! text { ($t:expr) => { Box::new($crate::dom::TextNode::new($t)) }; ($format:tt, $($tail:expr),*) => { Box::new($crate::dom::TextNode::new(format!($format, $($tail),*))) }; } /// An unsafe HTML text node. /// This is like TextNode, but no escaping will be performed when this node is displayed. pub struct UnsafeTextNode(String, PhantomData); /// Macro for creating unescaped text nodes. /// /// Returns a boxed text node of type `Box`. /// /// This macro is useful for creating text macros inside code blocks that contain HTML /// that you do not want to be escaped. For example, if some other process renders Markdown /// to an HTML string and you want embed that HTML string in a typed-html template, /// you may want to avoid escaping the tags in that HTML string. /// /// # Examples /// /// ```no_compile /// html!( ///

{ unsafe_text!("Hello Joe!") }

/// ) /// ``` /// /// ```no_compile /// html!( ///

{ unsafe_text!("Hello {}!", "Robert") }

/// ) /// ``` /// /// ```no_compile /// html!( ///

{ unsafe_text!("

this text renders unescaped html
") }

/// ) /// ``` #[macro_export] macro_rules! unsafe_text { ($t:expr) => { Box::new($crate::dom::UnsafeTextNode::new($t)) }; ($format:tt, $($tail:tt),*) => { Box::new($crate::dom::UnsafeTextNode::new(format!($format, $($tail),*))) }; } impl TextNode { /// Construct a text node. /// /// The preferred way to construct a text node is with the [`text!()`][text] /// macro. /// /// [text]: ../macro.text.html pub fn new>(s: S) -> Self { TextNode(s.into(), PhantomData) } } impl Display for TextNode { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { f.write_str(&encode_minimal(&self.0)) } } impl Node for TextNode { fn vnode(&'_ mut self) -> VNode<'_, T> { VNode::Text(&self.0) } } impl IntoIterator for TextNode { type Item = TextNode; type IntoIter = std::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { vec![self].into_iter() } } impl IntoIterator for Box> { type Item = Box>; type IntoIter = std::vec::IntoIter>>; fn into_iter(self) -> Self::IntoIter { vec![self].into_iter() } } impl FlowContent for TextNode {} impl PhrasingContent for TextNode {} impl UnsafeTextNode { /// Construct a unsafe text node. /// /// The preferred way to construct a unsafe text node is with the [`unsafe_text!()`][unsafe_text] /// macro. /// /// [unsafe_text]: ../macro.unsafe_text.html pub fn new>(s: S) -> Self { UnsafeTextNode(s.into(), PhantomData) } } impl Display for UnsafeTextNode { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { f.write_str(&self.0) } } impl Node for UnsafeTextNode { fn vnode(&'_ mut self) -> VNode<'_, T> { VNode::UnsafeText(&self.0) } } impl IntoIterator for UnsafeTextNode { type Item = UnsafeTextNode; type IntoIter = std::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { vec![self].into_iter() } } impl IntoIterator for Box> { type Item = Box>; type IntoIter = std::vec::IntoIter>>; fn into_iter(self) -> Self::IntoIter { vec![self].into_iter() } } impl FlowContent for UnsafeTextNode {} impl PhrasingContent for UnsafeTextNode {}