1use crate::abi::structs::_xmlNode;
16use std::cmp::Ordering;
17use std::ptr;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum XPathType {
26 NodeSet,
28 String,
30 Number,
32 Boolean,
34 Point,
36 Range,
38 LocationSet,
40 Users,
42 XsltTree,
44 Undefined,
46}
47
48#[derive(Debug, Clone, Copy)]
57pub struct XPathNode(pub *mut _xmlNode);
58
59impl PartialEq for XPathNode {
60 fn eq(&self, other: &Self) -> bool {
61 self.0 == other.0
62 }
63}
64
65impl Eq for XPathNode {}
66
67impl PartialOrd for XPathNode {
68 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
69 Some(self.cmp(other))
70 }
71}
72
73impl Ord for XPathNode {
74 fn cmp(&self, other: &Self) -> Ordering {
75 self.0.cmp(&other.0)
78 }
79}
80
81impl std::hash::Hash for XPathNode {
82 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
83 self.0.hash(state);
84 }
85}
86
87#[derive(Debug, Clone)]
92pub struct NodeSet {
93 nodes: Vec<XPathNode>,
94}
95
96impl NodeSet {
97 pub const fn new() -> Self {
99 Self { nodes: Vec::new() }
100 }
101
102 pub fn singleton(node: *mut _xmlNode) -> Self {
104 Self {
105 nodes: vec![XPathNode(node)],
106 }
107 }
108
109 pub const fn is_empty(&self) -> bool {
111 self.nodes.is_empty()
112 }
113
114 pub const fn len(&self) -> usize {
116 self.nodes.len()
117 }
118
119 pub fn iter(&self) -> impl Iterator<Item = *mut _xmlNode> + '_ {
121 self.nodes.iter().map(|n| n.0)
122 }
123
124 pub fn get(&self, index: usize) -> Option<*mut _xmlNode> {
126 self.nodes.get(index).map(|n| n.0)
127 }
128
129 pub fn first(&self) -> Option<*mut _xmlNode> {
131 self.nodes.first().map(|n| n.0)
132 }
133
134 pub fn last(&self) -> Option<*mut _xmlNode> {
136 self.nodes.last().map(|n| n.0)
137 }
138
139 pub fn contains(&self, node: *mut _xmlNode) -> bool {
141 self.nodes.iter().any(|n| n.0 == node)
142 }
143
144 pub fn push(&mut self, node: *mut _xmlNode) {
146 if !self.nodes.iter().any(|n| n.0 == node) {
147 self.nodes.push(XPathNode(node));
148 self.sort();
149 }
150 }
151
152 pub fn extend(&mut self, other: &NodeSet) {
154 for node in other.iter() {
155 self.push(node);
156 }
157 }
158
159 pub fn sort(&mut self) {
170 self.nodes
171 .sort_by(|a, b| unsafe { compare_document_order(a.0, b.0) });
172 self.nodes.dedup();
173 }
174
175 pub unsafe fn to_raw(&self) -> *mut crate::abi::structs::_xmlNodeSet {
190 let node_max = self.nodes.len();
191 let node_tab = if node_max > 0 {
192 let ptr = crate::abi::allocator::xmlMallocImpl(
193 node_max * std::mem::size_of::<*mut _xmlNode>(),
194 ) as *mut *mut _xmlNode;
195 if ptr.is_null() {
196 return ptr::null_mut();
197 }
198 for (i, node) in self.nodes.iter().enumerate() {
199 ptr::write(ptr.add(i), node.0);
200 }
201 ptr
202 } else {
203 ptr::null_mut()
204 };
205
206 let raw = crate::abi::allocator::xmlMallocImpl(std::mem::size_of::<
207 crate::abi::structs::_xmlNodeSet,
208 >()) as *mut crate::abi::structs::_xmlNodeSet;
209 if raw.is_null() {
210 if !node_tab.is_null() {
211 crate::abi::allocator::xmlFreeImpl(node_tab as *mut _);
212 }
213 return ptr::null_mut();
214 }
215 ptr::write(
216 raw,
217 crate::abi::structs::_xmlNodeSet {
218 nodeNr: node_max as std::os::raw::c_int,
219 nodeMax: node_max as std::os::raw::c_int,
220 nodeTab: node_tab,
221 },
222 );
223 raw
224 }
225}
226
227impl Default for NodeSet {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233#[derive(Debug, Clone)]
235pub enum XPathValue {
236 NodeSet(NodeSet),
238 String(String),
240 Number(f64),
242 Boolean(bool),
244}
245
246impl XPathValue {
247 pub const fn xpath_type(&self) -> XPathType {
249 match self {
250 XPathValue::NodeSet(_) => XPathType::NodeSet,
251 XPathValue::String(_) => XPathType::String,
252 XPathValue::Number(_) => XPathType::Number,
253 XPathValue::Boolean(_) => XPathType::Boolean,
254 }
255 }
256
257 pub fn as_boolean(&self) -> bool {
259 match self {
260 XPathValue::NodeSet(ns) => !ns.is_empty(),
261 XPathValue::String(s) => !s.is_empty(),
262 XPathValue::Number(n) => *n != 0.0 && !n.is_nan(),
263 XPathValue::Boolean(b) => *b,
264 }
265 }
266
267 pub fn as_number(&self) -> f64 {
269 match self {
270 XPathValue::NodeSet(ns) => {
271 if let Some(node) = ns.first() {
273 let s = node_string_value(node);
274 string_to_number(&s)
275 } else {
276 f64::NAN
277 }
278 }
279 XPathValue::String(s) => string_to_number(s),
280 XPathValue::Number(n) => *n,
281 XPathValue::Boolean(true) => 1.0,
282 XPathValue::Boolean(false) => 0.0,
283 }
284 }
285
286 pub fn as_string(&self) -> String {
288 match self {
289 XPathValue::NodeSet(ns) => {
290 if let Some(node) = ns.first() {
291 node_string_value(node)
292 } else {
293 String::new()
294 }
295 }
296 XPathValue::String(s) => s.clone(),
297 XPathValue::Number(n) => number_to_string(*n),
298 XPathValue::Boolean(true) => "true".to_string(),
299 XPathValue::Boolean(false) => "false".to_string(),
300 }
301 }
302
303 pub fn as_node_set(&self) -> &NodeSet {
305 match self {
306 XPathValue::NodeSet(ns) => ns,
307 _ => panic!("XPathValue is not a node-set"),
308 }
309 }
310
311 pub fn as_node_set_mut(&mut self) -> &mut NodeSet {
313 match self {
314 XPathValue::NodeSet(ns) => ns,
315 _ => panic!("XPathValue is not a node-set"),
316 }
317 }
318}
319
320pub fn node_string_value(node: *mut _xmlNode) -> String {
332 if node.is_null() {
333 return String::new();
334 }
335
336 unsafe {
337 let node_ref = &*node;
338 match node_ref.type_ {
339 1..=20 => {}
340 _ => return String::new(),
341 }
342
343 if node_ref.type_ == 1 || node_ref.type_ == 9 || node_ref.type_ == 13 {
345 let mut result = String::new();
346 collect_text(&mut result, node);
347 return result;
348 }
349
350 if node_ref.type_ == 2 {
355 if !node_ref.children.is_null() {
356 let child = &*node_ref.children;
357 if (child.type_ == 3 || child.type_ == 4) && !child.content.is_null() {
358 return crate::xml::string::xmlstr_to_string(child.content);
359 }
360 }
361 return String::new();
362 }
363
364 if node_ref.type_ == 3 || node_ref.type_ == 4 {
366 if !node_ref.content.is_null() {
367 return crate::xml::string::xmlstr_to_string(node_ref.content);
368 }
369 return String::new();
370 }
371
372 if node_ref.type_ == 7 {
374 if !node_ref.content.is_null() {
376 return crate::xml::string::xmlstr_to_string(node_ref.content);
377 }
378 return String::new();
379 }
380
381 String::new()
382 }
383}
384
385unsafe fn collect_text(result: &mut String, node: *mut _xmlNode) {
387 if node.is_null() {
388 return;
389 }
390 let node_ref = &*node;
391
392 if node_ref.type_ == 3 || node_ref.type_ == 4 {
394 if !node_ref.content.is_null() {
395 result.push_str(&crate::xml::string::xmlstr_to_string(node_ref.content));
396 }
397 return;
398 }
399
400 if node_ref.type_ == 1 || node_ref.type_ == 9 || node_ref.type_ == 19 {
402 let mut child = node_ref.children;
403 while !child.is_null() {
404 collect_text(result, child);
405 child = (*child).next;
406 }
407 }
408}
409
410pub fn string_bytes_to_number(bytes: &[u8]) -> f64 {
421 let len = bytes.len();
422 let mut cur = 0usize;
423 while cur < len && matches!(bytes[cur], b' ' | b'\t' | b'\n' | b'\r') {
425 cur += 1;
426 }
427 let mut isneg = false;
428 if cur < len && bytes[cur] == b'-' {
429 isneg = true;
430 cur += 1;
431 }
432 if cur >= len || (bytes[cur] != b'.' && !bytes[cur].is_ascii_digit()) {
433 return f64::NAN;
434 }
435
436 let mut ret = 0.0f64;
437 let mut ok = false;
438 while cur < len && bytes[cur].is_ascii_digit() {
439 ret = ret * 10.0 + (bytes[cur] - b'0') as f64;
440 ok = true;
441 cur += 1;
442 }
443
444 let mut frac: i32 = 0;
445 if cur < len && bytes[cur] == b'.' {
446 cur += 1;
447 if (cur >= len || !bytes[cur].is_ascii_digit()) && !ok {
448 return f64::NAN;
449 }
450 while cur < len && bytes[cur] == b'0' {
451 frac += 1;
452 cur += 1;
453 }
454 let max = frac + 20; let mut fraction = 0.0f64;
456 while cur < len && bytes[cur].is_ascii_digit() && frac < max {
457 let v = (bytes[cur] - b'0') as f64;
458 fraction = fraction * 10.0 + v;
459 frac += 1;
460 cur += 1;
461 }
462 fraction /= 10f64.powf(frac as f64);
463 ret += fraction;
464 while cur < len && bytes[cur].is_ascii_digit() {
465 cur += 1;
466 }
467 }
468
469 let mut exponent: i32 = 0;
470 let mut is_exponent_negative = false;
471 if cur < len && (bytes[cur] == b'e' || bytes[cur] == b'E') {
472 cur += 1;
473 if cur < len && bytes[cur] == b'-' {
474 is_exponent_negative = true;
475 cur += 1;
476 } else if cur < len && bytes[cur] == b'+' {
477 cur += 1;
478 }
479 while cur < len && bytes[cur].is_ascii_digit() {
480 if exponent < 1000000 {
481 exponent = exponent * 10 + (bytes[cur] - b'0') as i32;
482 }
483 cur += 1;
484 }
485 }
486 while cur < len && matches!(bytes[cur], b' ' | b'\t' | b'\n' | b'\r') {
487 cur += 1;
488 }
489 if cur != len {
490 return f64::NAN;
491 }
492 if isneg {
493 ret = -ret;
494 }
495 if is_exponent_negative {
496 exponent = -exponent;
497 }
498 ret *= 10f64.powf(exponent as f64);
499 ret
500}
501
502pub fn string_to_number(s: &str) -> f64 {
505 string_bytes_to_number(s.as_bytes())
506}
507
508pub fn number_to_string(n: f64) -> String {
515 if n.is_nan() {
516 return "NaN".to_string();
517 }
518 if n.is_infinite() {
519 return if n > 0.0 {
520 "Infinity".to_string()
521 } else {
522 "-Infinity".to_string()
523 };
524 }
525 if n == 0.0 {
526 return "0".to_string();
528 }
529 if n > i32::MIN as f64 && n < i32::MAX as f64 && n == (n as i32) as f64 {
532 return format!("{}", n as i32);
533 }
534
535 let absolute_value = n.abs();
536 let s = if ((absolute_value > 1e9) || (absolute_value < 1e-5)) && absolute_value != 0.0 {
537 let raw = format!("{:.14e}", n);
540 let e_pos = raw.find('e').expect("exponent format contains 'e'");
541 let mantissa = &raw[..e_pos];
542 let exponent = &raw[e_pos + 1..];
543 let mut mantissa = mantissa.to_string();
544 while mantissa.ends_with('0') {
545 mantissa.pop();
546 }
547 if mantissa.ends_with('.') {
548 mantissa.pop();
549 }
550 let (sign, digits) = if let Some(rest) = exponent.strip_prefix('-') {
553 ("-", rest)
554 } else {
555 ("+", exponent)
556 };
557 let digits = if digits.len() < 2 {
558 format!("0{}", digits)
559 } else {
560 digits.to_string()
561 };
562 format!("{}e{}{}", mantissa, sign, digits)
563 } else {
564 let integer_place = absolute_value.log10() as i32;
566 let fraction_place = if integer_place > 0 {
567 15 - integer_place - 1
568 } else {
569 15 - integer_place
570 };
571 let mut s = format!("{:.*}", fraction_place as usize, n);
572 if s.contains('.') {
574 while s.ends_with('0') {
575 s.pop();
576 }
577 if s.ends_with('.') {
578 s.pop();
579 }
580 }
581 s
582 };
583 if s == "-0" {
584 return "0".to_string();
585 }
586 s
587}
588
589pub unsafe fn compare_document_order(a: *mut _xmlNode, b: *mut _xmlNode) -> Ordering {
617 if a.is_null() && b.is_null() {
618 return Ordering::Equal;
619 }
620 if a.is_null() {
621 return Ordering::Less;
622 }
623 if b.is_null() {
624 return Ordering::Greater;
625 }
626 if a == b {
627 return Ordering::Equal;
628 }
629
630 let depth_a = node_depth(a);
632 let depth_b = node_depth(b);
633
634 if depth_a < depth_b {
636 let mut n = b;
637 for _ in 0..(depth_b - depth_a) {
638 n = (*n).parent;
639 if n.is_null() {
640 break;
641 }
642 }
643 if n == a {
644 return Ordering::Less;
645 }
646 } else if depth_b < depth_a {
647 let mut n = a;
648 for _ in 0..(depth_a - depth_b) {
649 n = (*n).parent;
650 if n.is_null() {
651 break;
652 }
653 }
654 if n == b {
655 return Ordering::Greater;
656 }
657 }
658
659 let mut parent_a = a;
661 let mut parent_b = b;
662
663 let mut d_a = depth_a;
665 let mut d_b = depth_b;
666 while d_a > d_b {
667 parent_a = (*parent_a).parent;
668 d_a -= 1;
669 }
670 while d_b > d_a {
671 parent_b = (*parent_b).parent;
672 d_b -= 1;
673 }
674
675 while (*parent_a).parent != (*parent_b).parent {
677 parent_a = (*parent_a).parent;
678 parent_b = (*parent_b).parent;
679 if parent_a.is_null() || parent_b.is_null() {
680 return a.cmp(&b);
682 }
683 }
684
685 let n = (*parent_a).parent;
687 if n.is_null() {
688 return a.cmp(&b);
689 }
690 let mut child = (*n).children;
691 while !child.is_null() {
692 if child == parent_a {
693 return Ordering::Less;
694 }
695 if child == parent_b {
696 return Ordering::Greater;
697 }
698 child = (*child).next;
699 }
700
701 a.cmp(&b)
703}
704
705unsafe fn node_depth(node: *mut _xmlNode) -> usize {
707 let mut depth = 0;
708 let mut n = node;
709 while !(*n).parent.is_null() {
710 depth += 1;
711 n = (*n).parent;
712 }
713 depth
714}
715
716#[cfg(test)]
721mod tests {
722 use super::*;
723 #[allow(clippy::approx_constant)]
724 #[test]
725 fn test_string_to_number() {
726 assert!(string_to_number("").is_nan());
727 assert!(string_to_number("NaN").is_nan());
728 assert_eq!(string_to_number("42"), 42.0);
729 assert_eq!(string_to_number("-42"), -42.0);
730 assert_eq!(string_to_number("3.14"), 3.14);
731 assert_eq!(string_to_number(" 42 "), 42.0);
732 assert!(string_to_number("true").is_nan());
733 assert!(string_to_number("false").is_nan());
734 assert_eq!(string_to_number("0"), 0.0);
735 }
736 #[allow(clippy::approx_constant)]
737 #[test]
738 fn test_number_to_string() {
739 assert_eq!(number_to_string(f64::NAN), "NaN");
740 assert_eq!(number_to_string(f64::INFINITY), "Infinity");
741 assert_eq!(number_to_string(f64::NEG_INFINITY), "-Infinity");
742 assert_eq!(number_to_string(0.0), "0");
743 assert_eq!(number_to_string(-0.0), "0");
744 assert_eq!(number_to_string(42.0), "42");
745 assert_eq!(number_to_string(3.14), "3.14");
746 }
747
748 #[test]
749 fn test_value_conversions() {
750 let v = XPathValue::Number(42.0);
751 assert_eq!(v.as_number(), 42.0);
752 assert_eq!(v.as_string(), "42");
753 assert!(v.as_boolean());
754
755 let v = XPathValue::Number(0.0);
756 assert!(!v.as_boolean());
757
758 let v = XPathValue::Number(f64::NAN);
759 assert!(!v.as_boolean());
760
761 let v = XPathValue::String("hello".into());
762 assert_eq!(v.as_string(), "hello");
763 assert!(v.as_boolean());
764
765 let v = XPathValue::String("".into());
766 assert!(!v.as_boolean());
767
768 let v = XPathValue::Boolean(true);
769 assert_eq!(v.as_number(), 1.0);
770 assert_eq!(v.as_string(), "true");
771
772 let v = XPathValue::Boolean(false);
773 assert_eq!(v.as_number(), 0.0);
774 assert_eq!(v.as_string(), "false");
775 }
776
777 #[test]
778 fn test_node_set() {
779 let ns = NodeSet::new();
780 assert!(ns.is_empty());
781 assert_eq!(ns.len(), 0);
782 }
783}