// This file is part of html5ever_ext. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/html5ever_ext/master/COPYRIGHT. No part of predicator, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
// Copyright © 2017 The developers of html5ever_ext. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/html5ever_ext/master/COPYRIGHT.
/// Represents the structure of nodes unattached to a DOM.
/// Designed to make it easy to create an entire graph of nodes before adding it.
#[derive(Debug, Clone)]
pub struct UnattachedNode
{
/// Local name of this node
pub local_name: LocalName,
/// Attributes of this node
pub attributes: Vec,
/// The children of this node
pub children: Vec>,
}
impl<'a> From<&'a str> for UnattachedNode
{
#[inline(always)]
fn from(local_name: &'a str) -> Self
{
LocalName::from(local_name).empty_node()
}
}
impl From for UnattachedNode
{
#[inline(always)]
fn from(local_name: LocalName) -> Self
{
local_name.empty_node()
}
}
impl UnattachedNodeExt for UnattachedNode
{
fn to_rc_dom(self) -> RcDom
{
let mut rc_dom = RcDom::default();
self.attach_to_document_node(&mut rc_dom);
rc_dom
}
}
impl UnattachedNode
{
/// Creates a HTML RcDom from this node as the 'html' root with a HTML5 DOCTYPE preceding it.
/// Panics if the node is not called 'html'.
#[inline(always)]
pub fn to_html5_rc_dom(self) -> RcDom
{
let mut rc_dom = RcDom::default();
rc_dom.create_html5_document(self);
rc_dom
}
/// Creates a HTML document as a String from this node as the 'html' root with a HTML5 DOCTYPE preceding it.
/// Panics if the node is not called 'html'.
#[inline(always)]
pub fn to_html5_document(self, html_head_and_body_tags_are_optional: bool) -> String
{
self.to_html5_rc_dom().minify_to_string(html_head_and_body_tags_are_optional)
}
/// Represents an empty element, such as
#[inline(always)]
pub fn empty(local_name: LocalName) -> Self
{
Self::with_attributes(local_name, vec![])
}
/// Represents an empty element with just attributes, such as
#[inline(always)]
pub fn with_attributes(local_name: LocalName, attributes: Vec) -> Self
{
Self
{
local_name,
attributes,
children: vec![],
}
}
/// Represents an empty element with just attributes and text, such as hello
#[inline(always)]
pub fn with_attributes_and_text>(local_name: LocalName, attributes: Vec, text: S) -> Self
{
Self
{
local_name,
attributes,
children: vec!
[
Left(text.into()),
],
}
}
/// Represents an empty element with just text, such as hello
#[inline(always)]
pub fn with_text>(local_name: LocalName, text: S) -> Self
{
Self
{
local_name,
attributes: vec![],
children: vec!
[
Left(text.into()),
],
}
}
/// Add an empty attribute.
#[inline(always)]
pub fn with_empty_attribute(self, name: &str) -> Self
{
self.with_attribute(LocalName::from(name).empty_attribute())
}
/// Add an id attribute.
#[inline(always)]
pub fn with_id_attribute(self, id: &str) -> Self
{
if id.is_empty()
{
return self;
}
self.with_attribute(local_name!("id").attribute(id))
}
/// Add a title attribute.
#[inline(always)]
pub fn with_title_attribute(self, title: &str) -> Self
{
if title.is_empty()
{
return self;
}
self.with_attribute(local_name!("title").attribute(title))
}
//noinspection SpellCheckingInspection
/// Add an accesskey attribute.
#[inline(always)]
pub fn with_accesskey_attribute(self, accesskey: &str) -> Self
{
if accesskey.is_empty()
{
return self;
}
self.with_attribute(local_name!("accesskey").attribute(accesskey))
}
//noinspection SpellCheckingInspection
/// Add a lang attribute.
#[inline(always)]
pub fn with_lang_attribute(self, lang: &str) -> Self
{
self.with_attribute(local_name!("lang").attribute(lang))
}
//noinspection SpellCheckingInspection
/// Add a contenteditable attribute.
#[inline(always)]
pub fn with_contenteditable_attribute(self, editable: bool) -> Self
{
let value = if editable
{
""
}
else
{
"false"
};
self.with_attribute(local_name!("contenteditable").attribute(value))
}
//noinspection SpellCheckingInspection
/// Add a spellcheck attribute.
#[inline(always)]
pub fn with_spellcheck_attribute(self, spellcheck: bool) -> Self
{
let value = if spellcheck
{
"true"
}
else
{
"false"
};
self.with_attribute(LocalName::from("spellcheck").attribute(value))
}
//noinspection SpellCheckingInspection
/// Add a tabindex attribute.
#[inline(always)]
pub fn with_tabindex_attribute(self, tabindex: i32) -> Self
{
self.with_attribute(local_name!("tabindex").attribute(&format!("{}", tabindex)))
}
//noinspection SpellCheckingInspection
/// Add a hidden attribute.
#[inline(always)]
pub fn with_hidden_attribute(self, hidden: bool) -> Self
{
if hidden
{
self.with_attribute(local_name!("hidden").empty_attribute())
}
else
{
self
}
}
//noinspection SpellCheckingInspection
/// Add a contextmenu attribute.
#[inline(always)]
pub fn with_contextmenu_attribute(self, id: &str) -> Self
{
if id.is_empty()
{
return self;
}
self.with_attribute(local_name!("contextmenu").attribute(id))
}
/// Add a data attribute.
#[inline(always)]
pub fn with_data_attribute(self, name: &str, value: &str) -> Self
{
const PREFIX: &'static str = "data-";
let mut attribute_name = String::with_capacity(PREFIX.len() + name.len());
attribute_name.push_str(PREFIX);
attribute_name.push_str(name);
let local_name = LocalName::from(attribute_name.as_str());
self.with_attribute(local_name.attribute(value))
}
/// Add a draggable attribute.
#[inline(always)]
pub fn with_draggable_attribute(self, draggable: Draggable) -> Self
{
self.with_attribute(local_name!("draggable").attribute(draggable.to_str()))
}
/// Add a dir attribute.
#[inline(always)]
pub fn with_dir_attribute(self, dir: Dir) -> Self
{
self.with_attribute(local_name!("dir").attribute(dir.to_str()))
}
/// Add a href attribute.
#[inline(always)]
pub fn with_href_attribute(self, href: &str) -> Self
{
self.with_attribute(local_name!("href").attribute(href))
}
/// Add a class attribute.
#[inline(always)]
pub fn with_class_attribute(self, classes: &str) -> Self
{
self.with_attribute(local_name!("class").attribute(classes))
}
/// Add a class attribute.
#[inline(always)]
pub fn with_role_attribute(self, role: AriaRole) -> Self
{
self.with_attribute(local_name!("role").attribute(role.to_str()))
}
/// Add a class attribute from classes
#[inline(always)]
pub fn with_class_attribute_from_classes>(self, classes: &[S]) -> Self
{
let mut class_string = String::new();
let mut after_first = false;
for class in classes.iter()
{
let value = class.deref();
if !value.is_empty()
{
if after_first
{
class_string.push(' ');
}
else
{
after_first = true;
}
class_string.push_str(value);
}
}
self.with_class_attribute(&class_string)
}
/// Add classes irrespective of whether a class attribute exists or not.
#[inline(always)]
pub fn with_classes>(mut self, classes: &[S]) -> Self
{
for class in classes.iter()
{
let value = class.deref();
if !value.is_empty()
{
self = self.with_class(value)
}
}
self
}
/// Add a class attribute if it does not exist, or appends to an existing class attribute if it does
#[inline(always)]
pub fn with_class(mut self, value: &str) -> Self
{
if value.is_empty()
{
return self;
}
let class_local_name = local_name!("class");
let mut found = false;
for attribute in self.attributes.iter_mut()
{
if attribute.name.is_only_local(&class_local_name)
{
if attribute.value.len32() != 0
{
attribute.value.push_char(' ');
}
attribute.value.push_slice(value);
found = true;
break;
}
}
if found
{
self
}
else
{
self.with_class_attribute(value)
}
}
/// Add an attribute.
#[inline(always)]
pub fn with_attribute(mut self, attribute: Attribute) -> Self
{
self.attributes.push(attribute);
self
}
/// Add a child element.
#[inline(always)]
pub fn with_child_element(mut self, child_element: UnattachedNode) -> Self
{
self.children.push(Right(child_element));
self
}
/// Add a child text. If there is an existing child text element, appends its text to it.
#[inline(always)]
pub fn with_child_text>(mut self, child_text: S) -> Self
{
let child_text = child_text.into();
if self.children.is_empty()
{
self.children.push(Left(child_text));
}
else
{
// Odd design to avoid double-mutable borrow of self.children
let unmatched = match self.children.last_mut().unwrap().as_mut().left()
{
Some(existing_text) =>
{
existing_text.push_str(&child_text);
true
}
None => false,
};
if unmatched
{
self.children.push(Left(child_text));
}
}
self
}
/// Attach this node as a child of the document_node (ie as a root node)
#[inline(always)]
pub fn attach_to_document_node(self, rc_dom: &mut RcDom) -> Rc
{
let node = rc_dom.append_new_element_to_document_node(self.local_name.qual_name(), self.attributes);
Self::process_children(rc_dom, self.children, node)
}
/// Attach this node as a child of parent_node
#[inline(always)]
pub fn attach_to_parent_node(self, rc_dom: &mut RcDom, parent_node: &Rc) -> Rc
{
let node = rc_dom.append_new_element_to_parent_node(parent_node, self.local_name.qual_name(), self.attributes);
Self::process_children(rc_dom, self.children, node)
}
/// Attach this node before the sibling_node
#[inline(always)]
pub fn attach_to_before_sibling_node(self, rc_dom: &mut RcDom, sibling_node: &Rc) -> Rc
{
let node = rc_dom.append_new_element_before_sibling_node(sibling_node, self.local_name.qual_name(), self.attributes);
Self::process_children(rc_dom, self.children, node)
}
#[inline(always)]
fn process_children(rc_dom: &mut RcDom, mut children: Vec>, parent_node: Rc) -> Rc
{
for child in children.drain(..)
{
match child
{
Left(text) => parent_node.append_text(rc_dom, &text),
Right(child) =>
{
child.attach_to_parent_node(rc_dom, &parent_node);
}
}
}
parent_node
}
}