use {
html5ever::{namespace_url, ns, tendril::Tendril, LocalName, QualName},
std::rc::Rc,
};
mod node_creator;
pub use {
markup5ever_rcdom::{Handle, Node, NodeData, RcDom, SerializableHandle},
node_creator::NodeCreator,
};
pub type Nodes = Vec<Rc<Node>>;
pub trait NodeExt {
fn add_class(&self, value: &str);
fn remove_class(&self, value: &str);
fn has_class(&self, value: &str) -> bool;
fn toggle_class(&self, value: &str);
fn add_attribute(&self, name: &str, value: &str);
fn remove_attribute(&self, name: &str);
fn append_child(&self, node: Rc<Node>);
fn append_children(&self, node: &mut Nodes);
fn add_text(&self, value: &str);
}
impl NodeExt for Node {
fn add_class(&self, class_value: &str) {
let attribute_index = find_attribute(self, "class");
let attrs = match &self.data {
NodeData::Element { attrs, .. } => attrs,
_ => return,
};
let mut attrs = attrs.borrow_mut();
match attribute_index {
None => {
attrs.push(NodeCreator::attribute("class", class_value));
}
Some(index) => {
let value = String::from(attrs[index].value.clone());
let mut value = value.split(' ').collect::<Vec<_>>();
if value.contains(&class_value) {
return;
}
value.push(class_value);
attrs[index].value = Tendril::from(value.join(" "));
}
};
}
fn remove_class(&self, class_value: &str) {
let attribute_index = find_attribute(self, "class");
let attrs = match &self.data {
NodeData::Element { attrs, .. } => attrs,
_ => return,
};
let mut attrs = attrs.borrow_mut();
if let Some(index) = attribute_index {
let value = String::from(attrs[index].value.clone());
let mut value = value.split(' ').collect::<Vec<_>>();
value.retain(|x| x != &class_value);
attrs[index].value = Tendril::from(value.join(" "));
};
}
fn has_class(&self, class_value: &str) -> bool {
let attribute_index = find_attribute(self, "class");
let attrs = match &self.data {
NodeData::Element { attrs, .. } => attrs,
_ => return false,
};
let attrs = attrs.borrow();
match attribute_index {
None => false,
Some(index) => attrs[index].value.split(' ').any(|x| x == class_value),
}
}
fn toggle_class(&self, class_value: &str) {
let attribute_index = find_attribute(self, "class");
let attrs = match &self.data {
NodeData::Element { attrs, .. } => attrs,
_ => return,
};
let mut attrs = attrs.borrow_mut();
match attribute_index {
None => {
attrs.push(NodeCreator::attribute("class", class_value));
}
Some(index) => {
let value = String::from(attrs[index].value.clone());
let mut value = value.split(' ').collect::<Vec<_>>();
if value.contains(&class_value) {
return;
}
value.push(class_value);
attrs[index].value = Tendril::from(value.join(" "));
}
};
}
fn append_child(&self, node: Rc<Node>) {
self.children.borrow_mut().push(node);
}
fn append_children(&self, node: &mut Nodes) {
self.children.borrow_mut().append(node);
}
fn add_attribute(&self, name: &str, value: &str) {
let attribute_index = find_attribute(self, name);
let attrs = match &self.data {
NodeData::Element { attrs, .. } => attrs,
_ => return,
};
let mut attrs = attrs.borrow_mut();
if attribute_index.is_none() {
attrs.push(NodeCreator::attribute(name, value));
};
}
fn remove_attribute(&self, name: &str) {
let attribute_index = find_attribute(self, name);
let attrs = match &self.data {
NodeData::Element { attrs, .. } => attrs,
_ => return,
};
let mut attrs = attrs.borrow_mut();
if let Some(index) = attribute_index {
attrs.remove(index);
};
}
fn add_text(&self, value: &str) {
let text_node = NodeCreator::text(value);
self.append_child(text_node);
}
}
fn find_attribute(node: &Node, attribute_name: &str) -> Option<usize> {
let attrs = match &node.data {
NodeData::Element { attrs, .. } => attrs,
_ => return None,
};
for (idx, attr) in attrs.borrow().iter().enumerate() {
if attr.name != QualName::new(None, ns!(), LocalName::from(attribute_name)) {
continue;
}
return Some(idx);
}
None
}
#[test]
fn find_class_attribute_index() {
use crate::dom::NodeCreator;
let class_value = "class-value";
let elem = NodeCreator::element("a", vec![NodeCreator::attribute("class", class_value)]);
assert_eq!(Some(0), find_attribute(&elem, "class"));
let elem = NodeCreator::element(
"a",
vec![
NodeCreator::attribute("href", "/404/Not-Found"),
NodeCreator::attribute("class", class_value),
],
);
assert_eq!(Some(1), find_attribute(&elem, "class"));
let elem = NodeCreator::element("a", vec![]);
assert_eq!(None, find_attribute(&elem, "class"));
}
#[test]
fn has_class() {
use crate::dom::NodeCreator;
let class_value = "has-class-test";
let non_existent_class_value = "non-existing-class-value";
let elem = NodeCreator::element("a", vec![NodeCreator::attribute("class", class_value)]);
assert_eq!(true, elem.has_class(class_value));
assert_eq!(false, elem.has_class(non_existent_class_value));
}
#[test]
fn add_class() {
use crate::dom::NodeCreator;
let class_value = "add-class-test";
let elem = NodeCreator::element("a", vec![]);
elem.add_class(class_value);
assert_eq!(true, elem.has_class(class_value));
}
#[test]
fn add_class_ignore_duplicate() {
use crate::dom::NodeCreator;
let class_value = "add-class-test";
let elem = NodeCreator::element("a", vec![NodeCreator::attribute("class", class_value)]);
elem.add_class(class_value);
let attrs = match &elem.data {
NodeData::Element { attrs, .. } => attrs,
_ => {
assert_eq!(false, true);
return;
}
};
assert_eq!(
true,
*attrs.borrow() == vec![NodeCreator::attribute("class", class_value)]
);
}
#[test]
fn remove_class() {
use crate::dom::NodeCreator;
let class_value = "has-class-test";
let elem = NodeCreator::element(
"a",
vec![
NodeCreator::attribute("class", class_value),
NodeCreator::attribute("class", class_value),
],
);
elem.remove_class(class_value);
assert_eq!(false, elem.has_class(class_value));
let elem = NodeCreator::element("a", vec![]);
elem.remove_class(class_value);
assert_eq!(false, elem.has_class(class_value));
}
#[test]
fn append_child() {
use crate::dom::NodeCreator;
let link = NodeCreator::element("a", vec![]);
let image = NodeCreator::element("img", vec![]);
link.append_child(image);
assert_eq!(link.children.borrow().len(), 1);
}
#[test]
fn add_attribute() {
use crate::dom::NodeCreator;
let name = "href";
let value = "/some/path";
let elem = NodeCreator::element("a", vec![]);
elem.add_attribute(name, value);
let attrs = match &elem.data {
NodeData::Element { attrs, .. } => attrs,
_ => {
assert_eq!(false, true);
return;
}
};
assert_eq!(
true,
*attrs.borrow() == vec![NodeCreator::attribute(name, value)]
);
}
#[test]
fn add_attribute_ignore_duplicate() {
use crate::dom::NodeCreator;
let name = "href";
let value = "/some/path";
let elem = NodeCreator::element("a", vec![NodeCreator::attribute(name, value)]);
elem.add_attribute(name, value);
let attrs = match &elem.data {
NodeData::Element { attrs, .. } => attrs,
_ => {
assert_eq!(false, true);
return;
}
};
assert_eq!(
true,
*attrs.borrow() == vec![NodeCreator::attribute(name, value)]
);
}
#[test]
fn remove_attribute() {
use crate::dom::NodeCreator;
let attribute_name = "href";
let elem = NodeCreator::element(
"a",
vec![
NodeCreator::attribute("href", "/some/value"),
NodeCreator::attribute("rel", "stylesheet"),
],
);
elem.remove_attribute(attribute_name);
let attrs = match &elem.data {
NodeData::Element { attrs, .. } => attrs,
_ => {
assert_eq!(false, true);
return;
}
};
assert_eq!(
true,
*attrs.borrow() == vec![NodeCreator::attribute("rel", "stylesheet"),]
);
let elem = NodeCreator::element("a", vec![]);
elem.remove_attribute(attribute_name);
let attrs = match &elem.data {
NodeData::Element { attrs, .. } => attrs,
_ => {
assert_eq!(false, true);
return;
}
};
assert_eq!(true, *attrs.borrow() == vec![]);
}