Reorganise, docs, HTML escaping.
This commit is contained in:
parent
7d1e95f262
commit
cc8e7219a2
|
@ -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<String, String>,
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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!(<p>{ text!("{}. Ceci n'est pas une chatte.", i) }</p>)
|
||||
})
|
||||
}
|
||||
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
@ -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 {}
|
|
@ -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<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)>;
|
||||
}
|
||||
// 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<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!{
|
||||
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<Id>,
|
||||
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<Id>,
|
||||
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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Types for attribute values.
|
||||
|
||||
mod class;
|
||||
pub use self::class::Class;
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue