use crate::tree::{Doctype, DocumentData, ElementData, Node, NodeRef};
use std::cell::RefCell;
use std::fmt;
use std::ops::Deref;
#[cfg(feature = "safe")]
use std::marker::PhantomData;
#[cfg(feature = "safe")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NodeDataKind {
Element,
Text,
Comment,
ProcessingInstruction,
Doctype,
Document,
DocumentFragment,
}
impl NodeRef {
#[inline]
pub fn into_element_ref(self) -> Option<NodeDataRef<ElementData>> {
NodeDataRef::new_opt(self, Node::as_element)
}
#[inline]
pub fn into_text_ref(self) -> Option<NodeDataRef<RefCell<String>>> {
NodeDataRef::new_opt(self, Node::as_text)
}
#[inline]
pub fn into_comment_ref(self) -> Option<NodeDataRef<RefCell<String>>> {
NodeDataRef::new_opt(self, Node::as_comment)
}
#[inline]
pub fn into_doctype_ref(self) -> Option<NodeDataRef<Doctype>> {
NodeDataRef::new_opt(self, Node::as_doctype)
}
#[inline]
pub fn into_document_ref(self) -> Option<NodeDataRef<DocumentData>> {
NodeDataRef::new_opt(self, Node::as_document)
}
#[inline]
pub fn into_processing_instruction_ref(self) -> Option<NodeDataRef<RefCell<(String, String)>>> {
NodeDataRef::new_opt(self, Node::as_processing_instruction)
}
#[inline]
pub fn into_document_fragment_ref(self) -> Option<NodeDataRef<()>> {
NodeDataRef::new_opt(self, Node::as_document_fragment)
}
}
#[derive(Eq)]
pub struct NodeDataRef<T> {
_keep_alive: NodeRef,
#[cfg(not(feature = "safe"))]
_reference: *const T,
#[cfg(feature = "safe")]
_kind: NodeDataKind,
#[cfg(feature = "safe")]
_phantom: PhantomData<T>,
}
impl<T> NodeDataRef<T> {
#[inline]
pub fn new<F>(rc: NodeRef, f: F) -> NodeDataRef<T>
where
F: FnOnce(&Node) -> &T,
{
#[cfg(not(feature = "safe"))]
{
NodeDataRef {
_reference: f(&rc),
_keep_alive: rc,
}
}
#[cfg(feature = "safe")]
{
let kind = match &rc {
_ if rc.as_element().is_some() => NodeDataKind::Element,
_ if rc.as_text().is_some() => NodeDataKind::Text,
_ if rc.as_comment().is_some() => NodeDataKind::Comment,
_ if rc.as_processing_instruction().is_some() => {
NodeDataKind::ProcessingInstruction
}
_ if rc.as_doctype().is_some() => NodeDataKind::Doctype,
_ if rc.as_document().is_some() => NodeDataKind::Document,
_ if rc.as_document_fragment().is_some() => NodeDataKind::DocumentFragment,
_ => unreachable!("All node types are covered"),
};
drop(f);
NodeDataRef {
_keep_alive: rc,
_kind: kind,
_phantom: PhantomData,
}
}
}
#[inline]
pub fn new_opt<F>(rc: NodeRef, f: F) -> Option<NodeDataRef<T>>
where
F: FnOnce(&Node) -> Option<&T>,
{
#[cfg(not(feature = "safe"))]
{
f(&rc).map(|r| r as *const T).map(move |r| NodeDataRef {
_reference: r,
_keep_alive: rc,
})
}
#[cfg(feature = "safe")]
{
let kind = match &rc {
_ if rc.as_element().is_some() => NodeDataKind::Element,
_ if rc.as_text().is_some() => NodeDataKind::Text,
_ if rc.as_comment().is_some() => NodeDataKind::Comment,
_ if rc.as_processing_instruction().is_some() => {
NodeDataKind::ProcessingInstruction
}
_ if rc.as_doctype().is_some() => NodeDataKind::Doctype,
_ if rc.as_document().is_some() => NodeDataKind::Document,
_ if rc.as_document_fragment().is_some() => NodeDataKind::DocumentFragment,
_ => return None,
};
if f(&rc).is_some() {
Some(NodeDataRef {
_keep_alive: rc,
_kind: kind,
_phantom: PhantomData,
})
} else {
None
}
}
}
#[inline]
pub fn as_node(&self) -> &NodeRef {
&self._keep_alive
}
}
#[cfg(not(feature = "safe"))]
#[allow(unsafe_code)]
impl<T> Deref for NodeDataRef<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self._reference }
}
}
#[cfg(feature = "safe")]
impl Deref for NodeDataRef<ElementData> {
type Target = ElementData;
#[inline]
fn deref(&self) -> &ElementData {
self._keep_alive
.as_element()
.expect("NodeDataRef<ElementData> must contain Element")
}
}
#[cfg(feature = "safe")]
impl Deref for NodeDataRef<RefCell<String>> {
type Target = RefCell<String>;
#[inline]
fn deref(&self) -> &RefCell<String> {
match self._kind {
NodeDataKind::Text => self
._keep_alive
.as_text()
.expect("NodeDataRef with Text kind must contain text"),
NodeDataKind::Comment => self
._keep_alive
.as_comment()
.expect("NodeDataRef with Comment kind must contain comment"),
_ => unreachable!("NodeDataRef<RefCell<String>> must be Text or Comment"),
}
}
}
#[cfg(feature = "safe")]
impl Deref for NodeDataRef<Doctype> {
type Target = Doctype;
#[inline]
fn deref(&self) -> &Doctype {
self._keep_alive
.as_doctype()
.expect("NodeDataRef<Doctype> must contain Doctype")
}
}
#[cfg(feature = "safe")]
impl Deref for NodeDataRef<DocumentData> {
type Target = DocumentData;
#[inline]
fn deref(&self) -> &DocumentData {
self._keep_alive
.as_document()
.expect("NodeDataRef<DocumentData> must contain Document")
}
}
#[cfg(feature = "safe")]
impl Deref for NodeDataRef<RefCell<(String, String)>> {
type Target = RefCell<(String, String)>;
#[inline]
fn deref(&self) -> &RefCell<(String, String)> {
self._keep_alive
.as_processing_instruction()
.expect("NodeDataRef<RefCell<(String, String)>> must contain ProcessingInstruction")
}
}
#[cfg(feature = "safe")]
impl Deref for NodeDataRef<()> {
type Target = ();
#[inline]
fn deref(&self) -> &() {
self._keep_alive
.as_document_fragment()
.expect("NodeDataRef<()> must contain DocumentFragment")
}
}
impl<T> PartialEq for NodeDataRef<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self._keep_alive == other._keep_alive
}
}
impl<T> Clone for NodeDataRef<T> {
#[inline]
fn clone(&self) -> Self {
#[cfg(not(feature = "safe"))]
{
NodeDataRef {
_keep_alive: self._keep_alive.clone(),
_reference: self._reference,
}
}
#[cfg(feature = "safe")]
{
NodeDataRef {
_keep_alive: self._keep_alive.clone(),
_kind: self._kind,
_phantom: PhantomData,
}
}
}
}
#[cfg(not(feature = "safe"))]
impl<T: fmt::Debug> fmt::Debug for NodeDataRef<T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(feature = "safe")]
impl fmt::Debug for NodeDataRef<ElementData> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(feature = "safe")]
impl fmt::Debug for NodeDataRef<RefCell<String>> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(feature = "safe")]
impl fmt::Debug for NodeDataRef<Doctype> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(feature = "safe")]
impl fmt::Debug for NodeDataRef<DocumentData> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(feature = "safe")]
impl fmt::Debug for NodeDataRef<RefCell<(String, String)>> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(feature = "safe")]
impl fmt::Debug for NodeDataRef<()> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&**self, f)
}
}
impl NodeDataRef<ElementData> {
pub fn text_contents(&self) -> String {
self.as_node().text_contents()
}
#[inline]
#[cfg(feature = "namespaces")]
pub fn namespace_uri(&self) -> &html5ever::Namespace {
(**self).namespace_uri()
}
#[inline]
pub fn local_name(&self) -> &html5ever::LocalName {
(**self).local_name()
}
#[inline]
#[cfg(feature = "namespaces")]
pub fn prefix(&self) -> Option<&html5ever::Prefix> {
(**self).prefix()
}
}
#[cfg(test)]
mod tests {
use crate::parser::parse_html;
use crate::traits::*;
#[test]
#[cfg(feature = "namespaces")]
fn node_data_ref_namespace_uri() {
let doc = parse_html().one(r#"<div>Test</div>"#);
let div = doc.select_first("div").unwrap();
assert_eq!(div.namespace_uri().as_ref(), "http://www.w3.org/1999/xhtml");
}
#[test]
fn node_data_ref_local_name() {
let doc = parse_html().one(r#"<span>Content</span>"#);
let span = doc.select_first("span").unwrap();
assert_eq!(span.local_name().as_ref(), "span");
}
#[test]
#[cfg(feature = "namespaces")]
fn node_data_ref_prefix() {
let doc = parse_html().one(r#"<p>Paragraph</p>"#);
let p = doc.select_first("p").unwrap();
assert_eq!(p.prefix(), None);
}
#[test]
#[cfg(feature = "namespaces")]
fn node_data_ref_svg_namespace() {
let svg_html = r#"<!DOCTYPE html>
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg">
<circle r="50"/>
</svg>
</body>
</html>"#;
let doc = parse_html().one(svg_html);
let circle = doc.select_first("circle").unwrap();
assert_eq!(
circle.namespace_uri().as_ref(),
"http://www.w3.org/2000/svg"
);
assert_eq!(circle.local_name().as_ref(), "circle");
assert_eq!(circle.prefix(), None);
}
#[test]
fn into_element_ref_some() {
let doc = parse_html().one(r#"<div>Content</div>"#);
let div_node = doc.select("div").unwrap().next().unwrap().as_node().clone();
let element_ref = div_node.into_element_ref();
assert!(element_ref.is_some());
assert_eq!(element_ref.unwrap().name.local.as_ref(), "div");
}
#[test]
fn into_element_ref_none() {
let doc = parse_html().one(r#"<div>text</div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let text_node = div.as_node().first_child().unwrap();
let element_ref = text_node.into_element_ref();
assert!(element_ref.is_none());
}
#[test]
fn into_text_ref_some() {
let doc = parse_html().one(r#"<div>text content</div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let text_node = div.as_node().first_child().unwrap();
let text_ref = text_node.into_text_ref();
assert!(text_ref.is_some());
assert_eq!(&*text_ref.unwrap().borrow(), "text content");
}
#[test]
fn into_text_ref_none() {
let doc = parse_html().one(r#"<div></div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let text_ref = div.as_node().clone().into_text_ref();
assert!(text_ref.is_none());
}
#[test]
fn into_comment_ref_some() {
let doc = parse_html().one(r#"<!-- comment --><div></div>"#);
let comment_node = doc.first_child().unwrap();
let comment_ref = comment_node.into_comment_ref();
assert!(comment_ref.is_some());
assert_eq!(&*comment_ref.unwrap().borrow(), " comment ");
}
#[test]
fn into_comment_ref_none() {
let doc = parse_html().one(r#"<div></div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let comment_ref = div.as_node().clone().into_comment_ref();
assert!(comment_ref.is_none());
}
#[test]
fn into_doctype_ref_some() {
let doc = parse_html().one(r#"<!DOCTYPE html><html></html>"#);
let doctype_node = doc.first_child().unwrap();
let doctype_ref = doctype_node.into_doctype_ref();
assert!(doctype_ref.is_some());
assert_eq!(&*doctype_ref.unwrap().name, "html");
}
#[test]
fn into_doctype_ref_none() {
let doc = parse_html().one(r#"<div></div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let doctype_ref = div.as_node().clone().into_doctype_ref();
assert!(doctype_ref.is_none());
}
#[test]
fn into_document_ref_some() {
let doc = parse_html().one(r#"<html></html>"#);
let document_ref = doc.into_document_ref();
assert!(document_ref.is_some());
}
#[test]
fn into_document_ref_none() {
let doc = parse_html().one(r#"<div></div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let document_ref = div.as_node().clone().into_document_ref();
assert!(document_ref.is_none());
}
#[test]
fn into_processing_instruction_ref_none() {
let doc = parse_html().one(r#"<div></div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let pi_ref = div.as_node().clone().into_processing_instruction_ref();
assert!(pi_ref.is_none());
}
#[test]
fn into_document_fragment_ref_none() {
let doc = parse_html().one(r#"<div></div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let frag_ref = div.as_node().clone().into_document_fragment_ref();
assert!(frag_ref.is_none());
}
#[test]
fn text_contents() {
let doc = parse_html().one(r#"<div>Hello <b>World</b>!</div>"#);
let div = doc.select("div").unwrap().next().unwrap();
assert_eq!(div.text_contents(), "Hello World!");
}
#[test]
fn text_contents_nested() {
let doc = parse_html().one(r#"<div><p>A</p><span>B<i>C</i></span>D</div>"#);
let div = doc.select("div").unwrap().next().unwrap();
assert_eq!(div.text_contents(), "ABCD");
}
#[test]
fn text_contents_empty() {
let doc = parse_html().one(r#"<div></div>"#);
let div = doc.select("div").unwrap().next().unwrap();
assert_eq!(div.text_contents(), "");
}
#[test]
fn as_node() {
let doc = parse_html().one(r#"<div></div>"#);
let div = doc.select("div").unwrap().next().unwrap();
let node = div.as_node();
assert!(node.as_element().is_some());
}
}