Reorganise, docs, HTML escaping.

This commit is contained in:
Bodil Stokke 2018-11-14 18:25:05 +00:00
parent 7d1e95f262
commit cc8e7219a2
9 changed files with 197 additions and 106 deletions

View File

@ -28,16 +28,12 @@ impl Declare {
} }
fn elem_name(&self) -> TokenTree { fn elem_name(&self) -> TokenTree {
Ident::new( Ident::new(&self.name.to_string(), self.name.span()).into()
&format!("Element_{}", self.name.to_string()),
self.name.span(),
)
.into()
} }
fn attr_type_name(&self) -> TokenTree { fn attr_type_name(&self) -> TokenTree {
Ident::new( Ident::new(
&format!("ElementAttrs_{}", self.name.to_string()), &format!("Attrs_{}", self.name.to_string()),
self.name.span(), self.name.span(),
) )
.into() .into()
@ -57,7 +53,7 @@ impl Declare {
let child_name: TokenTree = let child_name: TokenTree =
Ident::new(&format!("child_{}", child.to_string()), child.span()).into(); Ident::new(&format!("child_{}", child.to_string()), child.span()).into();
let child_type: TokenTree = 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(); let child_str = Literal::string(&child.to_string()).into();
(child_name, child_type, child_str) (child_name, child_type, child_str)
}) })
@ -107,7 +103,7 @@ impl Declare {
quote!( quote!(
pub struct $elem_name { pub struct $elem_name {
pub attrs: $attr_type_name, pub attrs: $attr_type_name,
pub data_attributes: std::collections::BTreeMap<String, String>, pub data_attributes: Vec<(String, String)>,
$body $body
} }
) )
@ -131,7 +127,7 @@ impl Declare {
body.extend(quote!( body.extend(quote!(
attrs: $attr_type_name { $attrs }, 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() { for (child_name, _, _) in self.req_children() {
body.extend(quote!( $child_name, )); body.extend(quote!( $child_name, ));
} }
@ -185,7 +181,7 @@ impl Declare {
$req_children $req_children
$opt_children $opt_children
::elements::VNode::Element(::elements::VElement { ::dom::VNode::Element(::dom::VElement {
name: $elem_name, name: $elem_name,
attributes, attributes,
children children
@ -197,8 +193,8 @@ impl Declare {
let elem_name = self.elem_name(); let elem_name = self.elem_name();
let vnode = self.impl_vnode(); let vnode = self.impl_vnode();
quote!( quote!(
impl ::elements::Node for $elem_name { impl ::dom::Node for $elem_name {
fn vnode(&self) -> ::elements::VNode { fn vnode(&self) -> ::dom::VNode {
$vnode $vnode
} }
} }
@ -225,7 +221,7 @@ impl Declare {
} }
quote!( quote!(
impl ::elements::Element for $elem_name { impl ::dom::Element for $elem_name {
fn name() -> &'static str { fn name() -> &'static str {
$name $name
} }
@ -306,7 +302,8 @@ impl Declare {
for (attr_name, _, attr_str) in self.attrs() { for (attr_name, _, attr_str) in self.attrs() {
print_attrs.extend(quote!( print_attrs.extend(quote!(
if let Some(ref value) = self.attrs.$attr_name { 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)?; write!(f, "<{}", $name)?;
$print_attrs $print_attrs
for (key, value) in &self.data_attributes { for (key, value) in &self.data_attributes {
write!(f, " data-{}={:?}", key, value)?; write!(f, " data-{}=\"{}\"", key,
::htmlescape::encode_attribute(&value))?;
} }
$print_children $print_children
} }

View File

@ -21,7 +21,7 @@ impl Node {
Node::Element(el) => el.into_token_stream(), Node::Element(el) => el.into_token_stream(),
Node::Text(text) => { Node::Text(text) => {
let text = TokenTree::Literal(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"), Node::Block(_) => panic!("cannot have a block in this position"),
} }
@ -97,7 +97,7 @@ impl Element {
fn into_token_stream(mut self) -> TokenStream { fn into_token_stream(mut self) -> TokenStream {
let name = self.name; let name = self.name;
let name_str = name.to_string(); 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); let req_names = required_children(&name_str);
if req_names.len() > self.children.len() { if req_names.len() > self.children.len() {
Diagnostic::spanned( Diagnostic::spanned(
@ -164,7 +164,7 @@ impl Element {
.map(|(k, v)| (TokenTree::from(Literal::string(&k)), v.clone())) .map(|(k, v)| (TokenTree::from(Literal::string(&k)), v.clone()))
{ {
body.extend(quote!( body.extend(quote!(
element.data_attributes.insert($key.into(), $value.into()); element.data_attributes.push(($key.into(), $value.into()));
)); ));
} }
body.extend(opt_children); body.extend(opt_children);

View File

@ -11,3 +11,4 @@ mime = "0.3.12"
language-tags = "0.2.2" language-tags = "0.2.2"
enumset = "0.3.12" enumset = "0.3.12"
http = "0.1.13" http = "0.1.13"
htmlescape = "0.3.1"

View File

@ -5,7 +5,7 @@
extern crate typed_html; extern crate typed_html;
extern crate typed_html_macros; extern crate typed_html_macros;
use typed_html::elements::Node; use typed_html::dom::Node;
use typed_html::types::*; use typed_html::types::*;
use typed_html_macros::html; use typed_html_macros::html;
@ -34,6 +34,7 @@ fn main() {
html!(<p>{ text!("{}. Ceci n'est pas une chatte.", i) }</p>) html!(<p>{ text!("{}. Ceci n'est pas une chatte.", i) }</p>)
}) })
} }
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
</body> </body>
</html> </html>
); );

160
typed-html/src/dom.rs Normal file
View File

@ -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!(
/// <p>"But how does she "<em>"eat?"</em></p>
/// ).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<VNode>,
}
/// 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<TextNode>`.
///
/// 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!(
/// <p>{ text!("Hello Joe!") }</p>
/// )
/// ```
///
/// ```no_compile
/// html!(
/// <p>{ text!("Hello {}!", "Robert") }</p>
/// )
/// ```
#[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: Into<String>>(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<TextNode>;
fn into_iter(self) -> Self::IntoIter {
vec![self].into_iter()
}
}
impl IntoIterator for Box<TextNode> {
type Item = Box<TextNode>;
type IntoIter = std::vec::IntoIter<Box<TextNode>>;
fn into_iter(self) -> Self::IntoIter {
vec![self].into_iter()
}
}
impl FlowContent for TextNode {}
impl PhrasingContent for TextNode {}

View File

@ -1,34 +1,11 @@
#![allow(non_camel_case_types)] //! Types for all standard HTML5 elements.
#![allow(dead_code)]
use std::fmt::Display;
use typed_html_macros::declare_elements; use typed_html_macros::declare_elements;
use super::types::*; use dom::{Node, TextNode};
use types::*;
#[derive(Clone, Debug)] // Marker traits for element content groups
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<VNode>,
}
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)>;
}
pub trait MetadataContent: Node {} pub trait MetadataContent: Node {}
pub trait FlowContent: Node {} pub trait FlowContent: Node {}
@ -49,56 +26,6 @@ pub trait SelectContent: Node {}
pub trait TableContent: Node {} pub trait TableContent: Node {}
pub trait TableColumnContent: Node {} pub trait TableColumnContent: Node {}
impl IntoIterator for TextNode {
type Item = TextNode;
type IntoIter = std::vec::IntoIter<TextNode>;
fn into_iter(self) -> Self::IntoIter {
vec![self].into_iter()
}
}
impl IntoIterator for Box<TextNode> {
type Item = Box<TextNode>;
type IntoIter = std::vec::IntoIter<Box<TextNode>>;
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: Into<String>>(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!{ declare_elements!{
html { html {
xmlns: Uri, xmlns: Uri,
@ -188,7 +115,7 @@ declare_elements!{
data { data {
value: String, value: String,
} in [FlowContent, PhrasingContent] with PhrasingContent; } in [FlowContent, PhrasingContent] with PhrasingContent;
datalist in [FlowContent, PhrasingContent] with Element_option; datalist in [FlowContent, PhrasingContent] with option;
del { del {
cite: Uri, cite: Uri,
datetime: Datetime, datetime: Datetime,
@ -302,12 +229,12 @@ declare_elements!{
typemustmatch: bool, typemustmatch: bool,
usemap: String, // TODO should be a fragment starting with '#' usemap: String, // TODO should be a fragment starting with '#'
width: usize, width: usize,
} in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent, FormContent] with Element_param; } in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent, FormContent] with param;
ol { ol {
reversed: bool, reversed: bool,
start: isize, start: isize,
type: OrderedListType, type: OrderedListType,
} in [FlowContent] with Element_li; } in [FlowContent] with li;
output { output {
for: SpacedSet<Id>, for: SpacedSet<Id>,
form: Id, form: Id,
@ -373,7 +300,7 @@ declare_elements!{
time { time {
datetime: Datetime, datetime: Datetime,
} in [FlowContent, PhrasingContent] with PhrasingContent; } in [FlowContent, PhrasingContent] with PhrasingContent;
ul in [FlowContent] with Element_li; ul in [FlowContent] with li;
var in [FlowContent, PhrasingContent] with PhrasingContent; var in [FlowContent, PhrasingContent] with PhrasingContent;
video in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent; video in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent;
wbr in [FlowContent, PhrasingContent]; wbr in [FlowContent, PhrasingContent];
@ -396,7 +323,7 @@ declare_elements!{
}; };
colgroup { colgroup {
span: usize, span: usize,
} in [TableContent] with Element_col; } in [TableContent] with col;
dd in [DescriptionListContent] with FlowContent; dd in [DescriptionListContent] with FlowContent;
dt in [DescriptionListContent] with FlowContent; dt in [DescriptionListContent] with FlowContent;
figcaption with FlowContent; figcaption with FlowContent;
@ -413,7 +340,7 @@ declare_elements!{
optgroup { optgroup {
disabled: bool, disabled: bool,
label: String, label: String,
} in [SelectContent] with Element_option; } in [SelectContent] with option;
param { param {
name: String, name: String,
value: String, value: String,
@ -423,13 +350,13 @@ declare_elements!{
type: Mime, type: Mime,
} in [MediaContent]; } in [MediaContent];
summary with PhrasingContent; summary with PhrasingContent;
tbody in [TableContent] with Element_tr; tbody in [TableContent] with tr;
td { td {
colspan: usize, colspan: usize,
headers: SpacedSet<Id>, headers: SpacedSet<Id>,
rowspan: usize, rowspan: usize,
} in [TableColumnContent] with FlowContent; } in [TableColumnContent] with FlowContent;
tfoot in [TableContent] with Element_tr; tfoot in [TableContent] with tr;
th { th {
abbr: String, abbr: String,
colspan: usize, colspan: usize,
@ -437,7 +364,7 @@ declare_elements!{
rowspan: usize, rowspan: usize,
scope: TableHeaderScope, scope: TableHeaderScope,
} in [TableColumnContent] with FlowContent; } in [TableColumnContent] with FlowContent;
thead in [TableContent] with Element_tr; thead in [TableContent] with tr;
tr in [TableContent] with TableColumnContent; tr in [TableContent] with TableColumnContent;
track { track {
default: bool, default: bool,

View File

@ -5,11 +5,13 @@ extern crate enumset;
#[macro_use] #[macro_use]
extern crate strum_macros; extern crate strum_macros;
pub extern crate htmlescape;
extern crate http; extern crate http;
extern crate language_tags; extern crate language_tags;
extern crate mime; extern crate mime;
extern crate strum; extern crate strum;
extern crate typed_html_macros; extern crate typed_html_macros;
pub mod dom;
pub mod elements; pub mod elements;
pub mod types; pub mod types;

View File

@ -1,3 +1,5 @@
//! Types for attribute values.
mod class; mod class;
pub use self::class::Class; pub use self::class::Class;

View File

@ -6,7 +6,7 @@ extern crate typed_html;
extern crate typed_html_macros; extern crate typed_html_macros;
use stdweb::web::{self, IElement, INode}; use stdweb::web::{self, IElement, INode};
use typed_html::elements::{Node, VNode}; use typed_html::dom::{Node, VNode};
use typed_html_macros::html; use typed_html_macros::html;
fn build( fn build(