use crate::abi::structs::_xmlNode;
use crate::abi::types::xmlChar;
use std::cmp::Ordering;
use std::ptr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XPathType {
NodeSet,
String,
Number,
Boolean,
Point,
Range,
LocationSet,
Users,
XsltTree,
Undefined,
}
#[derive(Debug, Clone, Copy)]
pub struct XPathNode(pub *mut _xmlNode);
impl PartialEq for XPathNode {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for XPathNode {}
impl PartialOrd for XPathNode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for XPathNode {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl std::hash::Hash for XPathNode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
#[derive(Debug, Clone)]
pub struct NodeSet {
nodes: Vec<XPathNode>,
}
impl NodeSet {
pub fn new() -> Self {
Self { nodes: Vec::new() }
}
pub fn singleton(node: *mut _xmlNode) -> Self {
Self {
nodes: vec![XPathNode(node)],
}
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn iter(&self) -> impl Iterator<Item = *mut _xmlNode> + '_ {
self.nodes.iter().map(|n| n.0)
}
pub fn get(&self, index: usize) -> Option<*mut _xmlNode> {
self.nodes.get(index).map(|n| n.0)
}
pub fn first(&self) -> Option<*mut _xmlNode> {
self.nodes.first().map(|n| n.0)
}
pub fn last(&self) -> Option<*mut _xmlNode> {
self.nodes.last().map(|n| n.0)
}
pub fn contains(&self, node: *mut _xmlNode) -> bool {
self.nodes.iter().any(|n| n.0 == node)
}
pub fn push(&mut self, node: *mut _xmlNode) {
if !self.nodes.iter().any(|n| n.0 == node) {
self.nodes.push(XPathNode(node));
self.sort();
}
}
pub fn extend(&mut self, other: &NodeSet) {
for node in other.iter() {
self.push(node);
}
}
pub fn sort(&mut self) {
self.nodes.sort_by(|a, b| {
a.0.cmp(&b.0)
});
self.nodes.dedup();
}
pub unsafe fn to_raw(&self) -> *mut crate::abi::structs::_xmlNodeSet {
let node_max = self.nodes.len();
let node_tab = if node_max > 0 {
let ptr =
crate::abi::allocator::xmlMalloc(node_max * std::mem::size_of::<*mut _xmlNode>())
as *mut *mut _xmlNode;
if ptr.is_null() {
return ptr::null_mut();
}
for (i, node) in self.nodes.iter().enumerate() {
ptr::write(ptr.add(i), node.0);
}
ptr
} else {
ptr::null_mut()
};
let raw = crate::abi::allocator::xmlMalloc(std::mem::size_of::<
crate::abi::structs::_xmlNodeSet,
>()) as *mut crate::abi::structs::_xmlNodeSet;
if raw.is_null() {
if !node_tab.is_null() {
crate::abi::allocator::xmlFree(node_tab as *mut _);
}
return ptr::null_mut();
}
ptr::write(
raw,
crate::abi::structs::_xmlNodeSet {
nodeNr: node_max as std::os::raw::c_int,
nodeMax: node_max as std::os::raw::c_int,
nodeTab: node_tab,
},
);
raw
}
}
impl Default for NodeSet {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum XPathValue {
NodeSet(NodeSet),
String(String),
Number(f64),
Boolean(bool),
}
impl XPathValue {
pub fn xpath_type(&self) -> XPathType {
match self {
XPathValue::NodeSet(_) => XPathType::NodeSet,
XPathValue::String(_) => XPathType::String,
XPathValue::Number(_) => XPathType::Number,
XPathValue::Boolean(_) => XPathType::Boolean,
}
}
pub fn as_boolean(&self) -> bool {
match self {
XPathValue::NodeSet(ns) => !ns.is_empty(),
XPathValue::String(s) => !s.is_empty(),
XPathValue::Number(n) => *n != 0.0 && !n.is_nan(),
XPathValue::Boolean(b) => *b,
}
}
pub fn as_number(&self) -> f64 {
match self {
XPathValue::NodeSet(ns) => {
if let Some(node) = ns.first() {
let s = node_string_value(node);
string_to_number(&s)
} else {
f64::NAN
}
}
XPathValue::String(s) => string_to_number(s),
XPathValue::Number(n) => *n,
XPathValue::Boolean(true) => 1.0,
XPathValue::Boolean(false) => 0.0,
}
}
pub fn as_string(&self) -> String {
match self {
XPathValue::NodeSet(ns) => {
if let Some(node) = ns.first() {
node_string_value(node)
} else {
String::new()
}
}
XPathValue::String(s) => s.clone(),
XPathValue::Number(n) => number_to_string(*n),
XPathValue::Boolean(true) => "true".to_string(),
XPathValue::Boolean(false) => "false".to_string(),
}
}
pub fn as_node_set(&self) -> &NodeSet {
match self {
XPathValue::NodeSet(ns) => ns,
_ => panic!("XPathValue is not a node-set"),
}
}
pub fn as_node_set_mut(&mut self) -> &mut NodeSet {
match self {
XPathValue::NodeSet(ns) => ns,
_ => panic!("XPathValue is not a node-set"),
}
}
}
pub fn node_string_value(node: *mut _xmlNode) -> String {
if node.is_null() {
return String::new();
}
unsafe {
let node_ref = &*node;
match node_ref.type_ {
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
| 20 => {}
_ => return String::new(),
}
if node_ref.type_ == 1 || node_ref.type_ == 9 || node_ref.type_ == 13 {
if node_ref.type_ == 13 {
if !node_ref.content.is_null() {
return crate::xml::string::xmlstr_to_string(node_ref.content);
}
return String::new();
}
let mut result = String::new();
collect_text(&mut result, node);
return result;
}
if node_ref.type_ == 3 || node_ref.type_ == 4 {
if !node_ref.content.is_null() {
return crate::xml::string::xmlstr_to_string(node_ref.content);
}
return String::new();
}
if node_ref.type_ == 7 {
if !node_ref.content.is_null() {
return crate::xml::string::xmlstr_to_string(node_ref.content);
}
return String::new();
}
String::new()
}
}
unsafe fn collect_text(result: &mut String, node: *mut _xmlNode) {
if node.is_null() {
return;
}
let node_ref = &*node;
if node_ref.type_ == 3 || node_ref.type_ == 4 {
if !node_ref.content.is_null() {
result.push_str(&crate::xml::string::xmlstr_to_string(node_ref.content));
}
return;
}
if node_ref.type_ == 1 || node_ref.type_ == 9 || node_ref.type_ == 19 {
let mut child = node_ref.children;
while !child.is_null() {
collect_text(result, child);
child = (*child).next;
}
}
}
pub fn string_to_number(s: &str) -> f64 {
let s = s.trim();
if s.is_empty() {
return f64::NAN;
}
match s {
"NaN" => return f64::NAN,
"Infinity" | "INF" => return f64::INFINITY,
"-Infinity" | "-INF" => return f64::NEG_INFINITY,
_ => {}
}
if let Ok(n) = s.parse::<f64>() {
n
} else {
f64::NAN
}
}
pub fn number_to_string(n: f64) -> String {
if n.is_nan() {
return "NaN".to_string();
}
if n.is_infinite() {
if n.is_sign_negative() {
return "-Infinity".to_string();
}
return "Infinity".to_string();
}
if n == 0.0 {
return "0".to_string();
}
if n.fract() == 0.0 && n.is_finite() {
if n.abs() < 1e16 {
return format!("{:.0}", n);
}
}
let s = format!("{:.15}", n);
let trimmed = s.trim_end_matches('0');
if trimmed.ends_with('.') {
format!("{}0", trimmed)
} else {
trimmed.to_string()
}
}
pub unsafe fn compare_document_order(a: *mut _xmlNode, b: *mut _xmlNode) -> Ordering {
if a.is_null() && b.is_null() {
return Ordering::Equal;
}
if a.is_null() {
return Ordering::Less;
}
if b.is_null() {
return Ordering::Greater;
}
if a == b {
return Ordering::Equal;
}
let depth_a = node_depth(a);
let depth_b = node_depth(b);
if depth_a < depth_b {
let mut n = b;
for _ in 0..(depth_b - depth_a) {
n = (*n).parent;
if n.is_null() {
break;
}
}
if n == a {
return Ordering::Less;
}
} else if depth_b < depth_a {
let mut n = a;
for _ in 0..(depth_a - depth_b) {
n = (*n).parent;
if n.is_null() {
break;
}
}
if n == b {
return Ordering::Greater;
}
}
let mut parent_a = a;
let mut parent_b = b;
let mut d_a = depth_a;
let mut d_b = depth_b;
while d_a > d_b {
parent_a = (*parent_a).parent;
d_a -= 1;
}
while d_b > d_a {
parent_b = (*parent_b).parent;
d_b -= 1;
}
while (*parent_a).parent != (*parent_b).parent {
parent_a = (*parent_a).parent;
parent_b = (*parent_b).parent;
if parent_a.is_null() || parent_b.is_null() {
return a.cmp(&b);
}
}
let mut n = (*parent_a).parent;
if n.is_null() {
return a.cmp(&b);
}
let mut child = (*n).children;
while !child.is_null() {
if child == parent_a {
return Ordering::Less;
}
if child == parent_b {
return Ordering::Greater;
}
child = (*child).next;
}
a.cmp(&b)
}
unsafe fn node_depth(node: *mut _xmlNode) -> usize {
let mut depth = 0;
let mut n = node;
while !(*n).parent.is_null() {
depth += 1;
n = (*n).parent;
}
depth
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_to_number() {
assert!(string_to_number("").is_nan());
assert!(string_to_number("NaN").is_nan());
assert_eq!(string_to_number("42"), 42.0);
assert_eq!(string_to_number("-42"), -42.0);
assert_eq!(string_to_number("3.14"), 3.14);
assert_eq!(string_to_number(" 42 "), 42.0);
assert!(string_to_number("true").is_nan());
assert!(string_to_number("false").is_nan());
assert_eq!(string_to_number("0"), 0.0);
}
#[test]
fn test_number_to_string() {
assert_eq!(number_to_string(f64::NAN), "NaN");
assert_eq!(number_to_string(f64::INFINITY), "Infinity");
assert_eq!(number_to_string(f64::NEG_INFINITY), "-Infinity");
assert_eq!(number_to_string(0.0), "0");
assert_eq!(number_to_string(-0.0), "0");
assert_eq!(number_to_string(42.0), "42");
assert_eq!(number_to_string(3.14), "3.14");
}
#[test]
fn test_value_conversions() {
let v = XPathValue::Number(42.0);
assert_eq!(v.as_number(), 42.0);
assert_eq!(v.as_string(), "42");
assert_eq!(v.as_boolean(), true);
let v = XPathValue::Number(0.0);
assert_eq!(v.as_boolean(), false);
let v = XPathValue::Number(f64::NAN);
assert_eq!(v.as_boolean(), false);
let v = XPathValue::String("hello".into());
assert_eq!(v.as_string(), "hello");
assert_eq!(v.as_boolean(), true);
let v = XPathValue::String("".into());
assert_eq!(v.as_boolean(), false);
let v = XPathValue::Boolean(true);
assert_eq!(v.as_number(), 1.0);
assert_eq!(v.as_string(), "true");
let v = XPathValue::Boolean(false);
assert_eq!(v.as_number(), 0.0);
assert_eq!(v.as_string(), "false");
}
#[test]
fn test_node_set() {
let mut ns = NodeSet::new();
assert!(ns.is_empty());
assert_eq!(ns.len(), 0);
}
}