use crate::parser::NodeRef;
use html5ever::{LocalName, QualName};
use kuchikikiki::{Attributes, ElementData, NodeData};
use std::cell::RefCell;
pub trait NodeExt {
fn element_name(&self) -> Option<&str>;
fn attr_value(&self, name: &str) -> Option<String>;
fn element_children(&self) -> Vec<NodeRef>;
fn first_element_child(&self) -> Option<NodeRef>;
fn next_element_sibling(&self) -> Option<NodeRef>;
fn previous_element_sibling(&self) -> Option<NodeRef>;
fn inner_html(&self) -> String;
fn clone_and_rename_element(self, tag_name: &str) -> NodeRef;
}
pub fn new_html_element(tag_name: &str) -> NodeRef {
let name = QualName::new(None, html5ever::ns!(html), LocalName::from(tag_name));
let attributes = Attributes {
map: Default::default(),
};
NodeRef::new(NodeData::Element(ElementData {
name,
attributes: RefCell::new(attributes),
template_contents: None,
}))
}
impl NodeExt for NodeRef {
fn element_name(&self) -> Option<&str> {
self.as_element().map(|e| e.name.local.as_ref())
}
fn attr_value(&self, name: &str) -> Option<String> {
self.as_element()
.and_then(|e| e.attributes.borrow().get(name).map(|v| v.to_string()))
}
fn element_children(&self) -> Vec<NodeRef> {
self.children()
.filter(|c| c.as_element().is_some())
.collect()
}
fn first_element_child(&self) -> Option<NodeRef> {
self.children().find(|c| c.as_element().is_some())
}
fn next_element_sibling(&self) -> Option<NodeRef> {
let mut sib = self.next_sibling();
while let Some(node) = sib {
if node.as_element().is_some() {
return Some(node);
}
sib = node.next_sibling();
}
None
}
fn previous_element_sibling(&self) -> Option<NodeRef> {
let mut sib = self.previous_sibling();
while let Some(node) = sib {
if node.as_element().is_some() {
return Some(node);
}
sib = node.previous_sibling();
}
None
}
fn inner_html(&self) -> String {
let mut out = String::new();
for child in self.children() {
out.push_str(&child.to_string());
}
out
}
fn clone_and_rename_element(self, tag_name: &str) -> NodeRef {
if self.as_element().is_none() {
return self;
}
let source_tag = self
.element_name()
.unwrap_or("")
.to_lowercase();
let e = self.as_element().unwrap();
let name = QualName::new(None, html5ever::ns!(html), LocalName::from(tag_name));
let new_node = NodeRef::new(NodeData::Element(ElementData {
name,
attributes: RefCell::new(Attributes {
map: e.attributes.borrow().map.clone(),
}),
template_contents: e.template_contents.clone(),
}));
if matches!(source_tag.as_str(), "table" | "th" | "td" | "hr" | "pre") {
if let Some(new_e) = new_node.as_element() {
let mut attrs = new_e.attributes.borrow_mut();
attrs.remove("width");
attrs.remove("height");
}
}
while self.first_child().is_some() {
new_node.append(self.first_child().unwrap());
}
self.insert_before(new_node.clone());
self.detach();
new_node
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse_html;
use std::panic::{catch_unwind, AssertUnwindSafe};
#[test]
fn clone_and_rename_element_non_element_does_not_panic() {
let doc = parse_html("<div>text</div>");
let div = doc.select_first("div").unwrap();
let text_node = div.as_node().first_child().unwrap();
let res = catch_unwind(AssertUnwindSafe(|| {
let _ = text_node.clone().clone_and_rename_element("span");
}));
assert!(res.is_ok());
}
}