use libc::{c_char, c_void};
use std::collections::{HashMap, HashSet};
use std::ffi::{CStr, CString};
use std::ptr;
use std::str;
use crate::bindings::*;
use crate::c_helpers::*;
use crate::tree::namespace::Namespace;
use crate::tree::nodetype::NodeType;
use crate::tree::Document;
use crate::xpath::Context;
#[derive(Debug, Copy, Clone)]
pub struct RoNode(pub(crate) xmlNodePtr);
unsafe impl Sync for RoNode {}
unsafe impl Send for RoNode {}
impl PartialEq for RoNode {
fn eq(&self, other: &RoNode) -> bool {
std::ptr::eq(self.0, other.0)
}
}
impl Eq for RoNode {}
impl RoNode {
pub fn node_ptr(&self) -> xmlNodePtr {
self.0
}
pub fn get_next_sibling(self) -> Option<RoNode> {
let ptr = xmlNextSibling(self.0);
self.ptr_as_option(ptr)
}
pub fn get_prev_sibling(self) -> Option<RoNode> {
let ptr = xmlPrevSibling(self.0);
self.ptr_as_option(ptr)
}
pub fn get_first_child(self) -> Option<RoNode> {
let ptr = xmlGetFirstChild(self.0);
self.ptr_as_option(ptr)
}
pub fn get_last_child(self) -> Option<RoNode> {
let ptr = unsafe { xmlGetLastChild(self.0) };
self.ptr_as_option(ptr)
}
pub fn get_next_element_sibling(&self) -> Option<RoNode> {
match self.get_next_sibling() {
None => None,
Some(child) => {
let mut current_node = child;
while !current_node.is_element_node() {
if let Some(sibling) = current_node.get_next_sibling() {
current_node = sibling;
} else {
break;
}
}
if current_node.is_element_node() {
Some(current_node)
} else {
None
}
}
}
}
pub fn get_prev_element_sibling(&self) -> Option<RoNode> {
match self.get_prev_sibling() {
None => None,
Some(child) => {
let mut current_node = child;
while !current_node.is_element_node() {
if let Some(sibling) = current_node.get_prev_sibling() {
current_node = sibling;
} else {
break;
}
}
if current_node.is_element_node() {
Some(current_node)
} else {
None
}
}
}
}
pub fn get_first_element_child(self) -> Option<RoNode> {
match self.get_first_child() {
None => None,
Some(child) => {
let mut current_node = child;
while !current_node.is_element_node() {
if let Some(sibling) = current_node.get_next_sibling() {
current_node = sibling;
} else {
break;
}
}
if current_node.is_element_node() {
Some(current_node)
} else {
None
}
}
}
}
pub fn get_last_element_child(&self) -> Option<RoNode> {
match self.get_last_child() {
None => None,
Some(child) => {
let mut current_node = child;
while !current_node.is_element_node() {
if let Some(sibling) = current_node.get_prev_sibling() {
current_node = sibling;
} else {
break;
}
}
if current_node.is_element_node() {
Some(current_node)
} else {
None
}
}
}
}
pub fn get_child_nodes(self) -> Vec<RoNode> {
let mut children = Vec::new();
if let Some(first_child) = self.get_first_child() {
children.push(first_child);
while let Some(sibling) = children.last().unwrap().get_next_sibling() {
children.push(sibling)
}
}
children
}
pub fn get_child_elements(self) -> Vec<RoNode> {
self
.get_child_nodes()
.into_iter()
.filter(|n| n.get_type() == Some(NodeType::ElementNode))
.collect::<Vec<RoNode>>()
}
pub fn get_parent(self) -> Option<RoNode> {
let ptr = xmlGetParent(self.0);
self.ptr_as_option(ptr)
}
pub fn get_type(self) -> Option<NodeType> {
NodeType::from_int(xmlGetNodeType(self.0))
}
pub fn is_text_node(self) -> bool {
self.get_type() == Some(NodeType::TextNode)
}
pub fn is_element_node(self) -> bool {
self.get_type() == Some(NodeType::ElementNode)
}
pub fn is_null(self) -> bool {
self.0.is_null()
}
pub fn get_name(self) -> String {
let name_ptr = xmlNodeGetName(self.0);
if name_ptr.is_null() {
return String::new();
} let c_string = unsafe { CStr::from_ptr(name_ptr) };
c_string.to_string_lossy().into_owned()
}
pub fn get_content(self) -> String {
let content_ptr = unsafe { xmlNodeGetContent(self.0) };
if content_ptr.is_null() {
return String::new();
}
let c_string = unsafe { CStr::from_ptr(content_ptr as *const c_char) };
let rust_utf8 = c_string.to_string_lossy().into_owned();
bindgenFree(content_ptr as *mut c_void);
rust_utf8
}
pub fn get_property(self, name: &str) -> Option<String> {
let c_name = CString::new(name).unwrap();
let value_ptr = unsafe { xmlGetProp(self.0, c_name.as_bytes().as_ptr()) };
if value_ptr.is_null() {
return None;
}
let c_value_string = unsafe { CStr::from_ptr(value_ptr as *const c_char) };
let prop_str = c_value_string.to_string_lossy().into_owned();
bindgenFree(value_ptr as *mut c_void);
Some(prop_str)
}
pub fn get_property_ns(self, name: &str, ns: &str) -> Option<String> {
let c_name = CString::new(name).unwrap();
let c_ns = CString::new(ns).unwrap();
let value_ptr =
unsafe { xmlGetNsProp(self.0, c_name.as_bytes().as_ptr(), c_ns.as_bytes().as_ptr()) };
if value_ptr.is_null() {
return None;
}
let c_value_string = unsafe { CStr::from_ptr(value_ptr as *const c_char) };
let prop_str = c_value_string.to_string_lossy().into_owned();
bindgenFree(value_ptr as *mut c_void);
Some(prop_str)
}
pub fn get_property_no_ns(self, name: &str) -> Option<String> {
let c_name = CString::new(name).unwrap();
let value_ptr = unsafe { xmlGetNoNsProp(self.0, c_name.as_bytes().as_ptr()) };
if value_ptr.is_null() {
return None;
}
let c_value_string = unsafe { CStr::from_ptr(value_ptr as *const c_char) };
let prop_str = c_value_string.to_string_lossy().into_owned();
bindgenFree(value_ptr as *mut c_void);
Some(prop_str)
}
pub fn get_property_node(self, name: &str) -> Option<RoNode> {
let c_name = CString::new(name).unwrap();
unsafe {
let attr_node = xmlHasProp(self.0, c_name.as_bytes().as_ptr());
self.ptr_as_option(attr_node as xmlNodePtr)
}
}
pub fn get_property_node_ns(self, name: &str, ns: &str) -> Option<RoNode> {
let c_name = CString::new(name).unwrap();
let c_ns = CString::new(ns).unwrap();
let attr_node =
unsafe { xmlHasNsProp(self.0, c_name.as_bytes().as_ptr(), c_ns.as_bytes().as_ptr()) };
self.ptr_as_option(attr_node as xmlNodePtr)
}
pub fn get_property_node_no_ns(self, name: &str) -> Option<RoNode> {
let c_name = CString::new(name).unwrap();
let attr_node = unsafe { xmlHasNsProp(self.0, c_name.as_bytes().as_ptr(), ptr::null()) };
self.ptr_as_option(attr_node as xmlNodePtr)
}
pub fn get_attribute(self, name: &str) -> Option<String> {
self.get_property(name)
}
pub fn get_attribute_ns(self, name: &str, ns: &str) -> Option<String> {
self.get_property_ns(name, ns)
}
pub fn get_attribute_no_ns(self, name: &str) -> Option<String> {
self.get_property_no_ns(name)
}
pub fn get_attribute_node(self, name: &str) -> Option<RoNode> {
self.get_property_node(name)
}
pub fn get_attribute_node_ns(self, name: &str, ns: &str) -> Option<RoNode> {
self.get_property_node_ns(name, ns)
}
pub fn get_attribute_node_no_ns(self, name: &str) -> Option<RoNode> {
self.get_property_node_no_ns(name)
}
pub fn get_properties(self) -> HashMap<String, String> {
let mut attributes = HashMap::new();
let mut current_prop = xmlGetFirstProperty(self.0);
while !current_prop.is_null() {
let name_ptr = xmlAttrName(current_prop);
let c_name_string = unsafe { CStr::from_ptr(name_ptr) };
let name = c_name_string.to_string_lossy().into_owned();
let value = self.get_property(&name).unwrap_or_default();
attributes.insert(name, value);
current_prop = xmlNextPropertySibling(current_prop);
}
attributes
}
pub fn get_properties_ns(self) -> HashMap<(String, Option<Namespace>), String> {
let mut attributes = HashMap::new();
let mut current_prop = xmlGetFirstProperty(self.0);
while !current_prop.is_null() {
let name_ptr = xmlAttrName(current_prop);
let c_name_string = unsafe { CStr::from_ptr(name_ptr) };
let name = c_name_string.to_string_lossy().into_owned();
let ns_ptr = xmlAttrNs(current_prop);
if ns_ptr.is_null() {
let value = self.get_property_no_ns(&name).unwrap_or_default();
attributes.insert((name, None), value);
} else {
let ns = Namespace { ns_ptr };
let value = self
.get_property_ns(&name, &ns.get_href())
.unwrap_or_default();
attributes.insert((name, Some(ns)), value);
}
current_prop = xmlNextPropertySibling(current_prop);
}
attributes
}
pub fn get_attributes(self) -> HashMap<String, String> {
self.get_properties()
}
pub fn get_attributes_ns(self) -> HashMap<(String, Option<Namespace>), String> {
self.get_properties_ns()
}
pub fn has_property(self, name: &str) -> bool {
let c_name = CString::new(name).unwrap();
let value_ptr = unsafe { xmlHasProp(self.0, c_name.as_bytes().as_ptr()) };
!value_ptr.is_null()
}
pub fn has_property_ns(self, name: &str, ns: &str) -> bool {
let c_name = CString::new(name).unwrap();
let c_ns = CString::new(ns).unwrap();
let value_ptr =
unsafe { xmlHasNsProp(self.0, c_name.as_bytes().as_ptr(), c_ns.as_bytes().as_ptr()) };
!value_ptr.is_null()
}
pub fn has_property_no_ns(self, name: &str) -> bool {
let c_name = CString::new(name).unwrap();
let value_ptr = unsafe { xmlHasNsProp(self.0, c_name.as_bytes().as_ptr(), ptr::null()) };
!value_ptr.is_null()
}
pub fn has_attribute(self, name: &str) -> bool {
self.has_property(name)
}
pub fn has_attribute_ns(self, name: &str, ns: &str) -> bool {
self.has_property_ns(name, ns)
}
pub fn has_attribute_no_ns(self, name: &str) -> bool {
self.has_property_no_ns(name)
}
pub fn get_namespace(self) -> Option<Namespace> {
let ns_ptr = xmlNodeNs(self.0);
if ns_ptr.is_null() {
None
} else {
Some(Namespace { ns_ptr })
}
}
pub fn get_namespaces(self, doc: &Document) -> Vec<Namespace> {
let list_ptr_raw = unsafe { xmlGetNsList(doc.doc_ptr(), self.0) };
if list_ptr_raw.is_null() {
Vec::new()
} else {
let mut namespaces = Vec::new();
let mut ptr_iter = list_ptr_raw as *mut xmlNsPtr;
unsafe {
while !ptr_iter.is_null() && !(*ptr_iter).is_null() {
namespaces.push(Namespace { ns_ptr: *ptr_iter });
ptr_iter = ptr_iter.add(1);
}
}
namespaces
}
}
pub fn get_namespace_declarations(self) -> Vec<Namespace> {
if self.get_type() != Some(NodeType::ElementNode) {
return Vec::new();
}
let mut namespaces = Vec::new();
let mut ns_ptr = xmlNodeNsDeclarations(self.0);
while !ns_ptr.is_null() {
if !xmlNsPrefix(ns_ptr).is_null() || !xmlNsHref(ns_ptr).is_null() {
namespaces.push(Namespace { ns_ptr });
}
ns_ptr = xmlNextNsSibling(ns_ptr);
}
namespaces
}
pub fn lookup_namespace_prefix(self, href: &str) -> Option<String> {
if href.is_empty() {
return None;
}
let c_href = CString::new(href).unwrap();
unsafe {
let ptr_mut = self.0;
let ns_ptr = xmlSearchNsByHref(xmlGetDoc(ptr_mut), ptr_mut, c_href.as_bytes().as_ptr());
if !ns_ptr.is_null() {
let ns = Namespace { ns_ptr };
let ns_prefix = ns.get_prefix();
Some(ns_prefix)
} else {
None
}
}
}
pub fn lookup_namespace_uri(self, prefix: &str) -> Option<String> {
if prefix.is_empty() {
return None;
}
let c_prefix = CString::new(prefix).unwrap();
unsafe {
let ns_ptr = xmlSearchNs(xmlGetDoc(self.0), self.0, c_prefix.as_bytes().as_ptr());
if !ns_ptr.is_null() {
let ns = Namespace { ns_ptr };
let ns_prefix = ns.get_href();
if !ns_prefix.is_empty() {
Some(ns_prefix)
} else {
None
}
} else {
None
}
}
}
pub fn get_class_names(self) -> HashSet<String> {
let mut set = HashSet::new();
if let Some(value) = self.get_property("class") {
for n in value.split(' ') {
set.insert(n.to_owned());
}
}
set
}
pub fn findnodes(self, xpath: &str, owner: &Document) -> Result<Vec<RoNode>, ()> {
let context = Context::new(owner)?;
let evaluated = context.node_evaluate_readonly(xpath, self)?;
Ok(evaluated.get_readonly_nodes_as_vec())
}
pub fn is_unlinked(self) -> bool {
false
}
fn ptr_as_option(self, node_ptr: xmlNodePtr) -> Option<RoNode> {
if node_ptr.is_null() {
None
} else {
Some(RoNode(node_ptr))
}
}
pub fn to_hashable(self) -> usize {
self.0 as usize
}
pub fn null() -> Self {
RoNode(ptr::null_mut())
}
}