use libc::{c_char, c_int};
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fmt;
use std::ptr;
use std::rc::{Rc, Weak};
use std::str;
use crate::bindings::*;
use crate::readonly::RoNode;
use crate::tree::node::Node;
pub(crate) type DocumentRef = Rc<RefCell<_Document>>;
pub(crate) type DocumentWeak = Weak<RefCell<_Document>>;
#[derive(Default, Clone, Copy)]
pub(crate) struct BuildNodePtrHasher;
impl std::hash::BuildHasher for BuildNodePtrHasher {
type Hasher = NodePtrHasher;
#[inline]
fn build_hasher(&self) -> NodePtrHasher {
NodePtrHasher(0)
}
}
#[doc(hidden)]
pub(crate) struct NodePtrHasher(u64);
impl NodePtrHasher {
#[inline]
fn add(&mut self, i: u64) {
self.0 = (self.0.rotate_left(5) ^ i).wrapping_mul(0x517c_c1b7_2722_0a95);
}
}
impl std::hash::Hasher for NodePtrHasher {
#[inline]
fn finish(&self) -> u64 {
self.0
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.add(i as u64);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.add(i);
}
fn write(&mut self, bytes: &[u8]) {
for chunk in bytes.chunks(8) {
let mut buf = [0u8; 8];
buf[..chunk.len()].copy_from_slice(chunk);
self.add(u64::from_le_bytes(buf));
}
}
}
pub(crate) type NodeMap = HashMap<xmlNodePtr, Node, BuildNodePtrHasher>;
#[cfg(test)]
mod node_ptr_hasher_tests {
use super::BuildNodePtrHasher;
use std::collections::HashSet;
use std::hash::{BuildHasher, Hasher};
fn hash_ptr(addr: usize) -> u64 {
let mut h = BuildNodePtrHasher.build_hasher();
h.write_usize(addr);
h.finish()
}
#[test]
fn deterministic() {
assert_eq!(hash_ptr(0x7f00_1234_5000), hash_ptr(0x7f00_1234_5000));
}
#[test]
fn no_collisions_on_aligned_allocator_run() {
let base = 0x7f00_0000_0000usize;
let mut seen = HashSet::new();
for i in 0..10_000usize {
assert!(seen.insert(hash_ptr(base + i * 160)), "collision at i={i}");
}
}
}
#[derive(Debug, Copy, Clone, Default)]
pub struct SaveOptions {
pub format: bool,
pub no_declaration: bool,
pub no_empty_tags: bool,
pub no_xhtml: bool,
pub xhtml: bool,
pub as_xml: bool,
pub as_html: bool,
pub non_significant_whitespace: bool,
}
#[derive(Debug)]
pub(crate) struct _Document {
pub(crate) doc_ptr: xmlDocPtr,
nodes: NodeMap,
}
impl _Document {
pub(crate) fn insert_node(&mut self, node_ptr: xmlNodePtr, node: Node) {
self.nodes.insert(node_ptr, node);
}
pub(crate) fn get_node(&self, node_ptr: xmlNodePtr) -> Option<&Node> {
self.nodes.get(&node_ptr)
}
pub(crate) fn forget_node(&mut self, node_ptr: xmlNodePtr) {
self.nodes.remove(&node_ptr);
}
}
#[derive(Clone)]
pub struct Document(pub(crate) DocumentRef);
impl Drop for _Document {
fn drop(&mut self) {
unsafe {
if !self.doc_ptr.is_null() {
xmlFreeDoc(self.doc_ptr);
}
}
}
}
impl fmt::Display for Document {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string_with_options(SaveOptions::default()))
}
}
impl Document {
pub fn new() -> Result<Self, ()> {
unsafe {
let c_version = CString::new("1.0").unwrap();
let c_version_bytes = c_version.as_bytes();
let doc_ptr = xmlNewDoc(c_version_bytes.as_ptr());
if doc_ptr.is_null() {
Err(())
} else {
let doc = _Document {
doc_ptr,
nodes: NodeMap::default(),
};
Ok(Document(Rc::new(RefCell::new(doc))))
}
}
}
pub fn doc_ptr(&self) -> xmlDocPtr {
self.0.borrow().doc_ptr
}
pub fn new_ptr(doc_ptr: xmlDocPtr) -> Self {
let doc = _Document {
doc_ptr,
nodes: NodeMap::default(),
};
Document(Rc::new(RefCell::new(doc)))
}
pub(crate) fn null_ref() -> DocumentRef {
Rc::new(RefCell::new(_Document {
doc_ptr: ptr::null_mut(),
nodes: NodeMap::default(),
}))
}
pub fn save_file(&self, filename: &str) -> Result<c_int, ()> {
let c_filename = CString::new(filename).unwrap();
unsafe {
let retval = xmlSaveFile(c_filename.as_ptr(), self.doc_ptr());
if retval < 0 {
return Err(());
}
Ok(retval)
}
}
pub(crate) fn register_node(&self, node_ptr: xmlNodePtr) -> Node {
Node::wrap(node_ptr, &self.0)
}
pub fn get_root_element(&self) -> Option<Node> {
unsafe {
let node_ptr = xmlDocGetRootElement(self.doc_ptr());
if node_ptr.is_null() {
None
} else {
Some(self.register_node(node_ptr))
}
}
}
pub fn get_root_readonly(&self) -> Option<RoNode> {
unsafe {
let node_ptr = xmlDocGetRootElement(self.doc_ptr());
if node_ptr.is_null() {
None
} else {
Some(RoNode(node_ptr))
}
}
}
pub fn set_root_element(&mut self, root: &Node) {
unsafe {
xmlDocSetRootElement(self.doc_ptr(), root.node_ptr());
}
root.set_linked();
}
pub fn remove_internal_subset(&mut self) {
unsafe {
let dtd = xmlGetIntSubset(self.doc_ptr());
if !dtd.is_null() {
xmlUnlinkNode(dtd as xmlNodePtr);
xmlFreeDtd(dtd);
}
}
}
fn ptr_as_result(&mut self, node_ptr: xmlNodePtr) -> Result<Node, ()> {
if node_ptr.is_null() {
Err(())
} else {
let node = self.register_node(node_ptr);
Ok(node)
}
}
pub fn import_node(&mut self, node: &mut Node) -> Result<Node, ()> {
if !node.is_unlinked() || node.is_rust_owned() {
return Err(());
}
node
.get_docref()
.upgrade()
.unwrap()
.borrow_mut()
.forget_node(node.node_ptr());
let node_ptr = unsafe { xmlDocCopyNode(node.node_ptr(), self.doc_ptr(), 1) };
node.set_linked();
self.ptr_as_result(node_ptr)
}
pub fn dup_node_into_new_doc(node: &Node) -> Result<Document, ()> {
let copy_ptr = unsafe { xmlCopyNode(node.node_ptr(), 1) };
if copy_ptr.is_null() {
return Err(());
}
let doc_ptr = unsafe {
let c_version = CString::new("1.0").unwrap();
xmlNewDoc(c_version.as_bytes().as_ptr())
};
if doc_ptr.is_null() {
unsafe { xmlFreeNode(copy_ptr) };
return Err(());
}
unsafe {
xmlDocSetRootElement(doc_ptr, copy_ptr);
}
Ok(Document::new_ptr(doc_ptr))
}
pub fn to_string_with_options(&self, options: SaveOptions) -> String {
unsafe {
let buf = xmlBufferCreate();
let c_utf8 = CString::new("UTF-8").unwrap();
let mut xml_options = 0;
if options.format {
xml_options += xmlSaveOption_XML_SAVE_FORMAT;
}
if options.no_declaration {
xml_options += xmlSaveOption_XML_SAVE_NO_DECL;
}
if options.no_empty_tags {
xml_options += xmlSaveOption_XML_SAVE_NO_EMPTY;
}
if options.no_xhtml {
xml_options += xmlSaveOption_XML_SAVE_NO_XHTML;
}
if options.xhtml {
xml_options += xmlSaveOption_XML_SAVE_XHTML;
}
if options.as_xml {
xml_options += xmlSaveOption_XML_SAVE_AS_XML;
}
if options.as_html {
xml_options += xmlSaveOption_XML_SAVE_AS_HTML;
}
if options.non_significant_whitespace {
xml_options += xmlSaveOption_XML_SAVE_WSNONSIG;
}
let save_ctx = xmlSaveToBuffer(buf, c_utf8.as_ptr(), xml_options as i32);
let _size = xmlSaveDoc(save_ctx, self.doc_ptr());
let _size = xmlSaveClose(save_ctx);
let result = xmlBufferContent(buf);
if result.is_null() {
xmlBufferFree(buf);
return String::new();
}
let c_string = CStr::from_ptr(result as *const c_char);
let node_string = c_string.to_string_lossy().into_owned();
xmlBufferFree(buf);
node_string
}
}
pub fn node_to_string(&self, node: &Node) -> String {
unsafe {
let buf = xmlBufferCreate();
xmlNodeDump(
buf,
self.doc_ptr(),
node.node_ptr(),
1, 0,
);
let result = xmlBufferContent(buf);
if result.is_null() {
xmlBufferFree(buf);
return String::new();
}
let c_string = CStr::from_ptr(result as *const c_char);
let node_string = c_string.to_string_lossy().into_owned();
xmlBufferFree(buf);
node_string
}
}
pub fn ronode_to_string(&self, node: &RoNode) -> String {
unsafe {
let buf = xmlBufferCreate();
xmlNodeDump(
buf,
self.doc_ptr(),
node.node_ptr(),
1, 0,
);
let result = xmlBufferContent(buf);
if result.is_null() {
xmlBufferFree(buf);
return String::new();
}
let c_string = CStr::from_ptr(result as *const c_char);
let node_string = c_string.to_string_lossy().into_owned();
xmlBufferFree(buf);
node_string
}
}
pub fn create_processing_instruction(&mut self, name: &str, content: &str) -> Result<Node, ()> {
unsafe {
let c_name = CString::new(name).unwrap();
let c_name_bytes = c_name.as_bytes();
let c_content = CString::new(content).unwrap();
let c_content_bytes = c_content.as_bytes();
let node_ptr: xmlNodePtr = xmlNewDocPI(
self.doc_ptr(),
c_name_bytes.as_ptr(),
c_content_bytes.as_ptr(),
);
if node_ptr.is_null() {
Err(())
} else {
Ok(self.register_node(node_ptr))
}
}
}
pub fn as_node(&self) -> Node {
self.register_node(self.doc_ptr() as xmlNodePtr)
}
pub fn dup(&self) -> Result<Self, ()> {
let doc_ptr = unsafe { xmlCopyDoc(self.doc_ptr(), 1) };
if doc_ptr.is_null() {
Err(())
} else {
let doc = _Document {
doc_ptr,
nodes: NodeMap::default(),
};
Ok(Document(Rc::new(RefCell::new(doc))))
}
}
pub fn dup_from(&mut self, source: &Self) -> Result<(), ()> {
if !self.doc_ptr().is_null() {
return Err(());
}
let doc_ptr = unsafe { xmlCopyDoc(source.doc_ptr(), 1) };
if doc_ptr.is_null() {
return Err(());
}
self.0.borrow_mut().doc_ptr = doc_ptr;
Ok(())
}
}
mod c14n;