From cc8e7219a2e9f0c2e77d672e7cb4a44f545f729a Mon Sep 17 00:00:00 2001 From: Bodil Stokke Date: Wed, 14 Nov 2018 18:25:05 +0000 Subject: [PATCH] Reorganise, docs, HTML escaping. --- macros/src/declare.rs | 28 +++---- macros/src/html.rs | 6 +- typed-html/Cargo.toml | 1 + typed-html/src/bin/main.rs | 3 +- typed-html/src/dom.rs | 160 ++++++++++++++++++++++++++++++++++++ typed-html/src/elements.rs | 99 +++------------------- typed-html/src/lib.rs | 2 + typed-html/src/types/mod.rs | 2 + wasm/src/main.rs | 2 +- 9 files changed, 197 insertions(+), 106 deletions(-) create mode 100644 typed-html/src/dom.rs diff --git a/macros/src/declare.rs b/macros/src/declare.rs index 867b1dc..a623e6d 100644 --- a/macros/src/declare.rs +++ b/macros/src/declare.rs @@ -28,16 +28,12 @@ impl Declare { } fn elem_name(&self) -> TokenTree { - Ident::new( - &format!("Element_{}", self.name.to_string()), - self.name.span(), - ) - .into() + Ident::new(&self.name.to_string(), self.name.span()).into() } fn attr_type_name(&self) -> TokenTree { Ident::new( - &format!("ElementAttrs_{}", self.name.to_string()), + &format!("Attrs_{}", self.name.to_string()), self.name.span(), ) .into() @@ -57,7 +53,7 @@ impl Declare { let child_name: TokenTree = Ident::new(&format!("child_{}", child.to_string()), child.span()).into(); let child_type: TokenTree = - Ident::new(&format!("Element_{}", child.to_string()), child.span()).into(); + Ident::new(&format!("{}", child.to_string()), child.span()).into(); let child_str = Literal::string(&child.to_string()).into(); (child_name, child_type, child_str) }) @@ -107,7 +103,7 @@ impl Declare { quote!( pub struct $elem_name { pub attrs: $attr_type_name, - pub data_attributes: std::collections::BTreeMap, + pub data_attributes: Vec<(String, String)>, $body } ) @@ -131,7 +127,7 @@ impl Declare { body.extend(quote!( attrs: $attr_type_name { $attrs }, )); - body.extend(quote!(data_attributes: std::collections::BTreeMap::new(),)); + body.extend(quote!(data_attributes: Vec::new(),)); for (child_name, _, _) in self.req_children() { body.extend(quote!( $child_name, )); } @@ -185,7 +181,7 @@ impl Declare { $req_children $opt_children - ::elements::VNode::Element(::elements::VElement { + ::dom::VNode::Element(::dom::VElement { name: $elem_name, attributes, children @@ -197,8 +193,8 @@ impl Declare { let elem_name = self.elem_name(); let vnode = self.impl_vnode(); quote!( - impl ::elements::Node for $elem_name { - fn vnode(&self) -> ::elements::VNode { + impl ::dom::Node for $elem_name { + fn vnode(&self) -> ::dom::VNode { $vnode } } @@ -225,7 +221,7 @@ impl Declare { } quote!( - impl ::elements::Element for $elem_name { + impl ::dom::Element for $elem_name { fn name() -> &'static str { $name } @@ -306,7 +302,8 @@ impl Declare { for (attr_name, _, attr_str) in self.attrs() { print_attrs.extend(quote!( if let Some(ref value) = self.attrs.$attr_name { - write!(f, " {}={:?}", $attr_str, value.to_string())?; + write!(f, " {}=\"{}\"", $attr_str, + ::htmlescape::encode_attribute(&value.to_string()))?; } )); } @@ -317,7 +314,8 @@ impl Declare { write!(f, "<{}", $name)?; $print_attrs for (key, value) in &self.data_attributes { - write!(f, " data-{}={:?}", key, value)?; + write!(f, " data-{}=\"{}\"", key, + ::htmlescape::encode_attribute(&value))?; } $print_children } diff --git a/macros/src/html.rs b/macros/src/html.rs index 3787e8b..625eb6c 100644 --- a/macros/src/html.rs +++ b/macros/src/html.rs @@ -21,7 +21,7 @@ impl Node { Node::Element(el) => el.into_token_stream(), Node::Text(text) => { let text = TokenTree::Literal(text); - quote!(Box::new(typed_html::elements::TextNode::new($text.to_string()))) + quote!(Box::new(typed_html::dom::TextNode::new($text.to_string()))) } Node::Block(_) => panic!("cannot have a block in this position"), } @@ -97,7 +97,7 @@ impl Element { fn into_token_stream(mut self) -> TokenStream { let name = self.name; let name_str = name.to_string(); - let typename: TokenTree = Ident::new(&format!("Element_{}", &name_str), name.span()).into(); + let typename: TokenTree = Ident::new(&name_str, name.span()).into(); let req_names = required_children(&name_str); if req_names.len() > self.children.len() { Diagnostic::spanned( @@ -164,7 +164,7 @@ impl Element { .map(|(k, v)| (TokenTree::from(Literal::string(&k)), v.clone())) { body.extend(quote!( - element.data_attributes.insert($key.into(), $value.into()); + element.data_attributes.push(($key.into(), $value.into())); )); } body.extend(opt_children); diff --git a/typed-html/Cargo.toml b/typed-html/Cargo.toml index 40f6dd5..8e68f30 100644 --- a/typed-html/Cargo.toml +++ b/typed-html/Cargo.toml @@ -11,3 +11,4 @@ mime = "0.3.12" language-tags = "0.2.2" enumset = "0.3.12" http = "0.1.13" +htmlescape = "0.3.1" diff --git a/typed-html/src/bin/main.rs b/typed-html/src/bin/main.rs index 2a1a235..469e656 100644 --- a/typed-html/src/bin/main.rs +++ b/typed-html/src/bin/main.rs @@ -5,7 +5,7 @@ extern crate typed_html; extern crate typed_html_macros; -use typed_html::elements::Node; +use typed_html::dom::Node; use typed_html::types::*; use typed_html_macros::html; @@ -34,6 +34,7 @@ fn main() { html!(

{ text!("{}. Ceci n'est pas une chatte.", i) }

) }) } +

""

); diff --git a/typed-html/src/dom.rs b/typed-html/src/dom.rs new file mode 100644 index 0000000..ebb8a20 --- /dev/null +++ b/typed-html/src/dom.rs @@ -0,0 +1,160 @@ +//! DOM and virtual DOM types. + +#![allow(non_camel_case_types)] +#![allow(dead_code)] + +use std::fmt::Display; + +use elements::{FlowContent, PhrasingContent}; +use htmlescape::encode_minimal; + +/// 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 +#[derive(Clone, Debug)] +pub enum VNode { + Text(String), + Element(VElement), +} + +/// An untyped representation of an HTML element. +#[derive(Clone, Debug)] +pub struct VElement { + pub name: &'static str, + pub attributes: Vec<(String, String)>, + 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 { + /// Render the node into a [`VNode`][VNode] tree. + /// + /// [VNode]: enum.VNode.html + fn vnode(&self) -> VNode; +} + +/// 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<(String, String)>; +} + +/// An HTML text node. +pub struct TextNode(String); + +/// 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:tt),*) => { + Box::new($crate::dom::TextNode::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()) + } +} + +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(&self) -> VNode { + VNode::Text(self.0.clone()) + } +} +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 {} diff --git a/typed-html/src/elements.rs b/typed-html/src/elements.rs index 95442c2..cdf9a15 100644 --- a/typed-html/src/elements.rs +++ b/typed-html/src/elements.rs @@ -1,34 +1,11 @@ -#![allow(non_camel_case_types)] -#![allow(dead_code)] +//! Types for all standard HTML5 elements. -use std::fmt::Display; use typed_html_macros::declare_elements; -use super::types::*; +use dom::{Node, TextNode}; +use types::*; -#[derive(Clone, Debug)] -pub enum VNode { - Text(String), - Element(VElement), -} - -#[derive(Clone, Debug)] -pub struct VElement { - pub name: &'static str, - pub attributes: Vec<(String, String)>, - pub children: Vec, -} - -pub trait Node: Display { - fn vnode(&self) -> VNode; -} - -pub trait Element: Node { - fn name() -> &'static str; - fn attribute_names() -> &'static [&'static str]; - fn required_children() -> &'static [&'static str]; - fn attributes(&self) -> Vec<(String, String)>; -} +// Marker traits for element content groups pub trait MetadataContent: Node {} pub trait FlowContent: Node {} @@ -49,56 +26,6 @@ pub trait SelectContent: Node {} pub trait TableContent: Node {} pub trait TableColumnContent: Node {} -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() - } -} - -pub struct TextNode(String); - -#[macro_export] -macro_rules! text { - ($t:expr) => { - Box::new($crate::elements::TextNode::new($t)) - }; - ($format:tt, $($tail:tt),*) => { - Box::new($crate::elements::TextNode::new(format!($format, $($tail),*))) - }; -} - -impl TextNode { - pub fn new>(s: S) -> Self { - TextNode(s.into()) - } -} - -impl Display for TextNode { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - self.0.fmt(f) - } -} - -impl Node for TextNode { - fn vnode(&self) -> VNode { - VNode::Text(self.0.clone()) - } -} -impl FlowContent for TextNode {} -impl PhrasingContent for TextNode {} - declare_elements!{ html { xmlns: Uri, @@ -188,7 +115,7 @@ declare_elements!{ data { value: String, } in [FlowContent, PhrasingContent] with PhrasingContent; - datalist in [FlowContent, PhrasingContent] with Element_option; + datalist in [FlowContent, PhrasingContent] with option; del { cite: Uri, datetime: Datetime, @@ -302,12 +229,12 @@ declare_elements!{ typemustmatch: bool, usemap: String, // TODO should be a fragment starting with '#' width: usize, - } in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent, FormContent] with Element_param; + } in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent, FormContent] with param; ol { reversed: bool, start: isize, type: OrderedListType, - } in [FlowContent] with Element_li; + } in [FlowContent] with li; output { for: SpacedSet, form: Id, @@ -373,7 +300,7 @@ declare_elements!{ time { datetime: Datetime, } in [FlowContent, PhrasingContent] with PhrasingContent; - ul in [FlowContent] with Element_li; + ul in [FlowContent] with li; var in [FlowContent, PhrasingContent] with PhrasingContent; video in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent; wbr in [FlowContent, PhrasingContent]; @@ -396,7 +323,7 @@ declare_elements!{ }; colgroup { span: usize, - } in [TableContent] with Element_col; + } in [TableContent] with col; dd in [DescriptionListContent] with FlowContent; dt in [DescriptionListContent] with FlowContent; figcaption with FlowContent; @@ -413,7 +340,7 @@ declare_elements!{ optgroup { disabled: bool, label: String, - } in [SelectContent] with Element_option; + } in [SelectContent] with option; param { name: String, value: String, @@ -423,13 +350,13 @@ declare_elements!{ type: Mime, } in [MediaContent]; summary with PhrasingContent; - tbody in [TableContent] with Element_tr; + tbody in [TableContent] with tr; td { colspan: usize, headers: SpacedSet, rowspan: usize, } in [TableColumnContent] with FlowContent; - tfoot in [TableContent] with Element_tr; + tfoot in [TableContent] with tr; th { abbr: String, colspan: usize, @@ -437,7 +364,7 @@ declare_elements!{ rowspan: usize, scope: TableHeaderScope, } in [TableColumnContent] with FlowContent; - thead in [TableContent] with Element_tr; + thead in [TableContent] with tr; tr in [TableContent] with TableColumnContent; track { default: bool, diff --git a/typed-html/src/lib.rs b/typed-html/src/lib.rs index 8c64ee5..bec4ad0 100644 --- a/typed-html/src/lib.rs +++ b/typed-html/src/lib.rs @@ -5,11 +5,13 @@ extern crate enumset; #[macro_use] extern crate strum_macros; +pub extern crate htmlescape; extern crate http; extern crate language_tags; extern crate mime; extern crate strum; extern crate typed_html_macros; +pub mod dom; pub mod elements; pub mod types; diff --git a/typed-html/src/types/mod.rs b/typed-html/src/types/mod.rs index e425c85..3915b21 100644 --- a/typed-html/src/types/mod.rs +++ b/typed-html/src/types/mod.rs @@ -1,3 +1,5 @@ +//! Types for attribute values. + mod class; pub use self::class::Class; diff --git a/wasm/src/main.rs b/wasm/src/main.rs index 0d317c5..d7ac32c 100644 --- a/wasm/src/main.rs +++ b/wasm/src/main.rs @@ -6,7 +6,7 @@ extern crate typed_html; extern crate typed_html_macros; use stdweb::web::{self, IElement, INode}; -use typed_html::elements::{Node, VNode}; +use typed_html::dom::{Node, VNode}; use typed_html_macros::html; fn build(