use ego_tree::iter::Nodes;
use ego_tree::{NodeId, Tree};
use html5ever::serialize::SerializeOpts;
use html5ever::tree_builder::QuirksMode;
use html5ever::QualName;
use html5ever::{driver, serialize};
use tendril::TendrilSink;
use crate::element_ref::ElementRef;
use crate::node::Node;
use crate::selector::Selector;
use self::tree_sink::HtmlBuilder;
lazy_static! {
static ref HTML_SELECTOR: Selector = Selector::parse("html").unwrap();
}
#[derive(Debug, Clone)]
pub struct Html {
pub quirks_mode: QuirksMode,
pub tree: Tree<Node>,
pub lang: String,
}
impl Html {
pub fn new_document() -> Self {
Html {
quirks_mode: QuirksMode::NoQuirks,
tree: Tree::new(Node::Document),
lang: Default::default(),
}
}
pub fn new_fragment() -> Self {
Html {
quirks_mode: QuirksMode::NoQuirks,
tree: Tree::new(Node::Fragment),
lang: Default::default(),
}
}
pub fn parse_document(document: &str) -> Self {
let parser = driver::parse_document(HtmlBuilder::new_document(), Default::default());
parser.one(document)
}
pub fn parse_fragment(fragment: &str) -> Self {
let parser = driver::parse_fragment(
HtmlBuilder::new_fragment(),
Default::default(),
QualName::new(None, ns!(html), local_name!("body")),
Vec::new(),
false,
);
parser.one(fragment)
}
pub fn select<'a, 'b>(&'a self, selector: &'b Selector) -> Select<'a, 'b> {
Select {
inner: self.tree.nodes(),
selector,
}
}
pub fn root_element(&self) -> ElementRef {
let root_node = self
.tree
.root()
.children()
.find(|child| child.value().is_element())
.expect("html node missing");
ElementRef::wrap(root_node).unwrap()
}
pub fn set_language(&mut self, lang: String) {
self.lang = lang;
}
pub fn get_lang(&self) -> &str {
if self.lang.is_empty() {
if let Some(element) = self.select(&HTML_SELECTOR).next() {
if let Some(lang) = element.value().attr("lang") {
return lang;
}
}
&self.lang
} else {
&self.lang
}
}
pub fn html(&self) -> String {
let opts = SerializeOpts {
scripting_enabled: false, traversal_scope: html5ever::serialize::TraversalScope::IncludeNode,
create_missing_parent: false,
};
let mut buf = Vec::new();
let _ = serialize(&mut buf, self, opts);
auto_encoder::auto_encode_bytes(&buf)
}
pub fn remove_node(&mut self, node_id: NodeId) {
if let Some(mut node) = self.tree.get_mut(node_id) {
node.detach();
}
}
}
#[derive(Debug)]
pub struct Select<'a, 'b> {
inner: Nodes<'a, Node>,
selector: &'b Selector,
}
impl<'a, 'b> Iterator for Select<'a, 'b> {
type Item = ElementRef<'a>;
fn next(&mut self) -> Option<ElementRef<'a>> {
for node in self.inner.by_ref() {
if let Some(element) = ElementRef::wrap(node) {
if element.parent().is_some() && self.selector.matches(&element) {
return Some(element);
}
}
}
None
}
}
impl<'a, 'b> DoubleEndedIterator for Select<'a, 'b> {
fn next_back(&mut self) -> Option<Self::Item> {
for node in self.inner.by_ref().rev() {
if let Some(element) = ElementRef::wrap(node) {
if element.parent().is_some() && self.selector.matches(&element) {
return Some(element);
}
}
}
None
}
}
mod serializable;
mod tree_sink;
#[cfg(test)]
mod tests {
use super::Html;
use super::Selector;
#[test]
fn parsed_html_is_send() {
fn assert_send<T: Send>(_: &T) {}
let html = Html::parse_document("<p>hi</p>");
assert_send(&html);
}
#[test]
fn root_element_fragment() {
let html = Html::parse_fragment(r#"<a href="http://github.com">1</a>"#);
let root_ref = html.root_element();
let href = root_ref
.select(&Selector::parse("a").unwrap())
.next()
.unwrap();
assert_eq!(href.inner_html(), "1");
assert_eq!(href.value().attr("href").unwrap(), "http://github.com");
}
#[test]
fn root_element_document_doctype() {
let html = Html::parse_document("<!DOCTYPE html>\n<title>abc</title>");
let root_ref = html.root_element();
let title = root_ref
.select(&Selector::parse("title").unwrap())
.next()
.unwrap();
assert_eq!(title.inner_html(), "abc");
}
#[test]
fn root_element_document_comment() {
let html = Html::parse_document("<!-- comment --><title>abc</title>");
let root_ref = html.root_element();
let title = root_ref
.select(&Selector::parse("title").unwrap())
.next()
.unwrap();
assert_eq!(title.inner_html(), "abc");
}
#[test]
fn select_is_reversible() {
let html = Html::parse_document("<p>element1</p><p>element2</p><p>element3</p>");
let selector = Selector::parse("p").unwrap();
let result: Vec<_> = html
.select(&selector)
.rev()
.map(|e| e.inner_html())
.collect();
assert_eq!(result, vec!["element3", "element2", "element1"]);
}
}