exml/xpath/
node_set.rs

1use std::collections::HashSet;
2
3use crate::{
4    tree::{
5        NodeCommon, XmlElementType, XmlGenericNodePtr, XmlNodePtr, XmlNsPtr, xml_free_node_list,
6    },
7    xpath::xml_xpath_node_set_free_ns,
8};
9
10use super::{
11    XmlXPathObject, xml_xpath_cast_node_to_string, xml_xpath_cmp_nodes_ext, xml_xpath_err_memory,
12    xml_xpath_new_node_set, xml_xpath_node_set_dup_ns,
13};
14
15// when evaluating an XPath expression nodesets are created and we
16// arbitrary limit the maximum length of those node set. 10000000 is
17// an insanely large value which should never be reached under normal
18// circumstances, one would first need to construct an in memory tree
19// with more than 10 millions nodes.
20const XPATH_MAX_NODESET_LENGTH: usize = 10000000;
21
22/// A node-set (an unordered collection of nodes without duplicates).
23#[repr(C)]
24#[derive(Clone, PartialEq, Default)]
25pub struct XmlNodeSet {
26    // array of nodes in no particular order
27    pub node_tab: Vec<XmlGenericNodePtr>,
28    // @@ with_ns to check whether namespace nodes should be looked at @@
29}
30
31impl XmlNodeSet {
32    pub fn with_value(val: Option<XmlGenericNodePtr>) -> Option<Self> {
33        let mut ret = Self::default();
34        if let Some(val) = val {
35            ret.node_tab = vec![];
36            if let Ok(ns) = XmlNsPtr::try_from(val) {
37                let ns_node =
38                    xml_xpath_node_set_dup_ns(ns.node.or(ns.next.map(|next| next.into())), ns)?;
39
40                ret.node_tab.push(ns_node);
41            } else {
42                ret.node_tab.push(val);
43            }
44        }
45        Some(ret)
46    }
47
48    /// Checks whether @ns is empty or not.
49    ///
50    /// Returns %TRUE if @ns is an empty node-set.
51    #[doc(alias = "xmlXPathNodeSetIsEmpty")]
52    pub(crate) fn is_empty(&self) -> bool {
53        self.node_tab.is_empty()
54    }
55
56    /// Implement a functionality similar to the DOM NodeList.length.
57    ///
58    /// Returns the number of nodes in the node-set.
59    #[doc(alias = "xmlXPathNodeSetGetLength")]
60    pub(crate) fn len(&self) -> usize {
61        self.node_tab.len()
62    }
63
64    /// Implements a functionality similar to the DOM NodeList.item().
65    ///
66    /// Returns the xmlNodePtr at the given @index in @ns or NULL if
67    /// @index is out of range (0 to length-1)
68    #[doc(alias = "xmlXPathNodeSetItem")]
69    pub(crate) fn get(&self, index: usize) -> Option<XmlGenericNodePtr> {
70        self.node_tab.get(index).copied()
71    }
72
73    /// checks whether @cur contains @val
74    ///
75    /// Returns true (1) if @cur contains @val, false (0) otherwise
76    #[doc(alias = "xmlXPathNodeSetContains")]
77    pub fn contains(&self, val: Option<XmlGenericNodePtr>) -> bool {
78        let Some(val) = val else {
79            return false;
80        };
81        let table = &self.node_tab;
82        if let Ok(ns1) = XmlNsPtr::try_from(val) {
83            for &node in table {
84                if let Ok(ns2) = XmlNsPtr::try_from(node) {
85                    if ns1 == ns2 {
86                        return true;
87                    }
88                    if ns1.node.is_some() && ns2.node == ns1.node && ns1.prefix() == ns2.prefix() {
89                        return true;
90                    }
91                }
92            }
93        } else {
94            for &node in table {
95                if val == node {
96                    return true;
97                }
98            }
99        }
100        false
101    }
102
103    /// Implements the EXSLT - Sets has-same-nodes function:
104    ///    boolean set:has-same-node(node-set, node-set)
105    ///
106    /// Returns true (1) if @nodes1 shares any node with @nodes2, false (0) otherwise
107    #[doc(alias = "xmlXPathHasSameNodes")]
108    pub unsafe fn has_same_nodes(&self, other: &XmlNodeSet) -> bool {
109        let t1 = &self.node_tab;
110        let t2 = &other.node_tab;
111        if t1.is_empty() || t2.is_empty() {
112            return false;
113        }
114        t1.iter().any(|node| t2.contains(node))
115    }
116
117    /// Implements the EXSLT - Sets intersection() function:
118    ///    node-set set:intersection (node-set, node-set)
119    ///
120    /// Returns a node set comprising the nodes that are within both the
121    /// node sets passed as arguments
122    #[doc(alias = "xmlXPathIntersection")]
123    pub fn intersection(&self, other: &XmlNodeSet) -> Option<Box<XmlNodeSet>> {
124        let mut ret = xml_xpath_node_set_create(None)?;
125        let t1 = &self.node_tab;
126        let t2 = &other.node_tab;
127        if t1.is_empty() || t2.is_empty() {
128            return Some(ret);
129        }
130
131        for &node in t1 {
132            if t2.contains(&node) {
133                // TODO: Propagate memory error.
134                if ret.as_mut().add_unique(node) < 0 {
135                    break;
136                }
137            }
138        }
139        Some(ret)
140    }
141
142    /// Free the NodeSet compound.
143    ///
144    /// If `free_actual_tree` is `true`, free the actual tree also.
145    #[doc(alias = "xmlXPathFreeNodeSet", alias = "xmlXPathFreeValueTree")]
146    pub(crate) fn cleanup(&mut self, free_actual_tree: bool) {
147        unsafe {
148            let table = &mut self.node_tab;
149            while let Some(node) = table.pop() {
150                if let Ok(ns) = XmlNsPtr::try_from(node) {
151                    xml_xpath_node_set_free_ns(ns);
152                } else if free_actual_tree {
153                    xml_free_node_list(Some(node));
154                }
155            }
156        }
157    }
158
159    /// Sort the node set in document order
160    #[doc(alias = "xmlXPathNodeSetSort")]
161    pub fn sort(&mut self) {
162        // Use the old Shell's sort implementation to sort the node-set
163        // Timsort ought to be quite faster
164        let table = &mut self.node_tab;
165        // TODO: Use `sort_unstable` of Rust standard library.
166        //       When I tried to rewirte, it did not work fine
167        //       because `xml_xpath_cmp_nodes_ext` does not satisfy "total order" constraint.
168        let len = table.len();
169        let mut incr = len;
170        while {
171            incr /= 2;
172            incr > 0
173        } {
174            for i in incr..len {
175                let mut j = i as i32 - incr as i32;
176                while j >= 0 {
177                    if xml_xpath_cmp_nodes_ext(table[j as usize], table[j as usize + incr])
178                        .is_some_and(|f| f.is_gt())
179                    {
180                        table.swap(j as usize, j as usize + incr);
181                        j -= incr as i32;
182                    } else {
183                        break;
184                    }
185                }
186            }
187        }
188    }
189
190    /// Removes an entry from an existing NodeSet list.
191    #[doc(alias = "xmlXPathNodeSetRemove")]
192    pub fn remove(&mut self, val: i32) {
193        if val >= self.node_tab.len() as i32 {
194            return;
195        }
196        if let Ok(ns) = XmlNsPtr::try_from(self.node_tab[val as usize]) {
197            xml_xpath_node_set_free_ns(ns);
198        }
199        self.node_tab.remove(val as usize);
200    }
201
202    /// Removes an xmlNodePtr from an existing NodeSet
203    #[doc(alias = "xmlXPathNodeSetDel")]
204    pub fn delete(&mut self, val: XmlGenericNodePtr) {
205        // find node in nodeTab
206        let Some(pos) = self.node_tab.iter().position(|&node| node == val) else {
207            return;
208        };
209        if let Ok(ns) = XmlNsPtr::try_from(self.node_tab[pos]) {
210            xml_xpath_node_set_free_ns(ns);
211        }
212        self.node_tab.remove(pos);
213    }
214
215    /// Clears the list from temporary XPath objects (e.g. namespace nodes
216    /// are feed) starting with the entry at @pos, but does *not* free the list
217    /// itself. Sets the length of the list to @pos.
218    #[doc(alias = "xmlXPathNodeSetClearFromPos")]
219    pub(super) fn truncate(&mut self, new_len: usize, has_ns_nodes: bool) {
220        if new_len >= self.node_tab.len() {
221            return;
222        }
223        if has_ns_nodes {
224            for &node in &self.node_tab[new_len..] {
225                if let Ok(ns) = XmlNsPtr::try_from(node) {
226                    xml_xpath_node_set_free_ns(ns);
227                }
228            }
229        }
230        self.node_tab.truncate(new_len);
231    }
232
233    /// Clears the list from all temporary XPath objects (e.g. namespace nodes are feed),
234    /// but does *not* free the list itself. Sets the length of the list to 0.
235    #[doc(alias = "xmlXPathNodeSetClear")]
236    pub(super) fn clear(&mut self, has_ns_nodes: bool) {
237        self.truncate(0, has_ns_nodes);
238    }
239
240    /// Add a new xmlNodePtr to an existing NodeSet
241    ///
242    /// Returns 0 in case of success, and -1 in case of error
243    #[doc(alias = "xmlXPathNodeSetAdd")]
244    pub fn add(&mut self, val: XmlGenericNodePtr) -> i32 {
245        // @@ with_ns to check whether namespace nodes should be looked at @@
246        // prevent duplicates
247        for &node in &self.node_tab {
248            if node == val {
249                return 0;
250            }
251        }
252
253        // grow the nodeTab if needed
254        if self.node_tab.len() >= XPATH_MAX_NODESET_LENGTH {
255            xml_xpath_err_memory(None, Some("growing nodeset hit limit\n"));
256            return -1;
257        }
258        if let Ok(ns) = XmlNsPtr::try_from(val) {
259            let Some(ns_node) =
260                xml_xpath_node_set_dup_ns(ns.node.or(ns.next.map(|next| next.into())), ns)
261            else {
262                return -1;
263            };
264
265            self.node_tab.push(ns_node);
266        } else {
267            self.node_tab.push(val);
268        }
269        0
270    }
271
272    /// Add a new xmlNodePtr to an existing NodeSet, optimized version
273    /// when we are sure the node is not already in the set.
274    ///
275    /// Returns 0 in case of success and -1 in case of failure
276    #[doc(alias = "xmlXPathNodeSetAddUnique")]
277    pub fn add_unique(&mut self, val: XmlGenericNodePtr) -> i32 {
278        // @@ with_ns to check whether namespace nodes should be looked at @@
279        // grow the nodeTab if needed
280        if self.node_tab.len() >= XPATH_MAX_NODESET_LENGTH {
281            xml_xpath_err_memory(None, Some("growing nodeset hit limit\n"));
282            return -1;
283        }
284        if let Ok(ns) = XmlNsPtr::try_from(val) {
285            let Some(ns_node) =
286                xml_xpath_node_set_dup_ns(ns.node.or(ns.next.map(|next| next.into())), ns)
287            else {
288                return -1;
289            };
290
291            self.node_tab.push(ns_node);
292        } else {
293            self.node_tab.push(val);
294        }
295        0
296    }
297
298    /// Add a new namespace node to an existing NodeSet
299    ///
300    /// Returns 0 in case of success and -1 in case of error
301    #[doc(alias = "xmlXPathNodeSetAddNs")]
302    pub fn add_ns(&mut self, node: XmlNodePtr, ns: XmlNsPtr) -> i32 {
303        if !matches!(ns.typ, XmlElementType::XmlNamespaceDecl)
304            || !matches!(node.element_type(), XmlElementType::XmlElementNode)
305        {
306            return -1;
307        }
308
309        // @@ with_ns to check whether namespace nodes should be looked at @@
310        // prevent duplicates
311        for &cur_node in &self.node_tab {
312            if XmlNsPtr::try_from(cur_node)
313                .ok()
314                .filter(|cur_node| cur_node.node == Some(node.into()))
315                .filter(|cur_node| ns.prefix() == cur_node.prefix())
316                .is_some()
317            {
318                return 0;
319            }
320        }
321
322        // grow the nodeTab if needed
323        if self.node_tab.len() >= XPATH_MAX_NODESET_LENGTH {
324            xml_xpath_err_memory(None, Some("growing nodeset hit limit\n"));
325            return -1;
326        }
327        let Some(ns_node) = xml_xpath_node_set_dup_ns(Some(node.into()), ns) else {
328            return -1;
329        };
330        self.node_tab.push(ns_node);
331        0
332    }
333}
334
335/// Create a new xmlNodeSetPtr of type f64 and of value @val
336///
337/// Returns the newly created object.
338#[doc(alias = "xmlXPathNodeSetCreate")]
339pub fn xml_xpath_node_set_create(val: Option<XmlGenericNodePtr>) -> Option<Box<XmlNodeSet>> {
340    let set = XmlNodeSet::with_value(val)?;
341    Some(Box::new(set))
342}
343
344/// Free the NodeSet compound (not the actual nodes !).
345#[doc(alias = "xmlXPathFreeNodeSet")]
346pub fn xml_xpath_free_node_set(obj: Option<Box<XmlNodeSet>>) {
347    if let Some(mut obj) = obj {
348        obj.as_mut().cleanup(false);
349    }
350}
351
352/// Free the NodeSet compound and the actual tree, this is different from xmlXPathFreeNodeSet()
353#[doc(alias = "xmlXPathFreeValueTree")]
354pub fn xml_xpath_free_value_tree(obj: Option<Box<XmlNodeSet>>) {
355    if let Some(mut obj) = obj {
356        obj.as_mut().cleanup(true);
357    }
358}
359
360/// Implements the EXSLT - Sets difference() function:
361///    node-set set:difference (node-set, node-set)
362///
363/// Returns the difference between the two node sets.
364///
365/// # Note
366/// In original libxml, if `node2` is empty, return `nodes1`.  
367/// However, this function returns the clone of `nodes1`.
368#[doc(alias = "xmlXPathDifference")]
369pub fn xml_xpath_difference(
370    nodes1: Option<&XmlNodeSet>,
371    nodes2: Option<&XmlNodeSet>,
372) -> Option<Box<XmlNodeSet>> {
373    let Some(nodes2) = nodes2.filter(|n| !n.is_empty()) else {
374        return nodes1.cloned().map(Box::new);
375    };
376
377    // TODO: Check memory error.
378    let mut ret = xml_xpath_node_set_create(None);
379    let Some(nodes1) = nodes1 else {
380        return ret;
381    };
382    if nodes1.is_empty() {
383        return ret;
384    }
385
386    let l1 = nodes1.len();
387
388    if let Some(ret) = ret.as_mut() {
389        for i in 0..l1 {
390            let cur = nodes1.get(i).unwrap();
391            if !nodes2.contains(Some(cur)) {
392                // TODO: Propagate memory error.
393                if ret.add_unique(cur) < 0 {
394                    break;
395                }
396            }
397        }
398    }
399    ret
400}
401
402/// Implements the EXSLT - Sets distinct() function:
403///    node-set set:distinct (node-set)
404///
405/// Returns a subset of the nodes contained in @nodes.
406///
407/// # Note
408/// In original libxml, if `nodes` is empty, return `nodes`.  
409/// However, this function returns the clone of `nodes`.
410#[doc(alias = "xmlXPathDistinctSorted")]
411pub fn xml_xpath_distinct_sorted(nodes: Option<&XmlNodeSet>) -> Option<Box<XmlNodeSet>> {
412    let nodes = nodes?;
413    let mut ret = xml_xpath_node_set_create(None)?;
414    if nodes.is_empty() {
415        return Some(ret);
416    }
417
418    let l = nodes.len();
419    let mut hash = HashSet::new();
420    for i in 0..l {
421        let cur = nodes.get(i).unwrap();
422        let strval = xml_xpath_cast_node_to_string(Some(cur));
423        if hash.insert(strval) && ret.as_mut().add_unique(cur) < 0 {
424            xml_xpath_free_node_set(Some(ret));
425            return None;
426        }
427    }
428    Some(ret)
429}
430
431/// Implements the EXSLT - Sets distinct() function:
432///    node-set set:distinct (node-set)
433/// @nodes is sorted by document order, then #exslSetsDistinctSorted
434/// is called with the sorted node-set
435///
436/// Returns a subset of the nodes contained in @nodes.
437///
438/// # Note
439/// In original libxml, if `nodes` is empty, return `nodes`.  
440/// However, this function returns the clone of `nodes`.
441#[doc(alias = "xmlXPathDistinct")]
442pub fn xml_xpath_distinct(nodes: Option<&mut XmlNodeSet>) -> Option<Box<XmlNodeSet>> {
443    let nodes = nodes?;
444    if nodes.is_empty() {
445        return Some(Box::new(nodes.clone()));
446    }
447
448    nodes.sort();
449    xml_xpath_distinct_sorted(Some(nodes))
450}
451
452/// Implements the EXSLT - Sets leading() function:
453///    node-set set:leading (node-set, node-set)
454///
455/// Returns the nodes in @nodes that precede @node in document order,
456/// an empty node-set if @nodes doesn't contain @node
457///
458/// # Note
459/// In original libxml, if `node` is NULL, return `nodes`.  
460/// However, this function returns the clone of `nodes`.
461#[doc(alias = "xmlXPathNodeLeadingSorted")]
462pub fn xml_xpath_node_leading_sorted(
463    nodes: Option<&XmlNodeSet>,
464    node: Option<XmlGenericNodePtr>,
465) -> Option<Box<XmlNodeSet>> {
466    let Some(node) = node else {
467        return nodes.cloned().map(Box::new);
468    };
469    let mut ret = xml_xpath_node_set_create(None)?;
470    let Some(nodes) = nodes.filter(|n| !n.is_empty() && n.contains(Some(node))) else {
471        return Some(ret);
472    };
473
474    let l = nodes.len();
475    for i in 0..l {
476        let cur = nodes.get(i).unwrap();
477        if cur == node {
478            break;
479        }
480        // TODO: Propagate memory error.
481        if ret.add_unique(cur) < 0 {
482            break;
483        }
484    }
485    Some(ret)
486}
487
488/// Implements the EXSLT - Sets leading() function:
489///    node-set set:leading (node-set, node-set)
490///
491/// Returns the nodes in @nodes1 that precede the first node in @nodes2 in document order,
492/// an empty node-set if @nodes1 doesn't contain @nodes2
493///
494/// # Note
495/// In original libxml, if `nodes2` is NULL, return `nodes1`.  
496/// However, this function returns the clone of `nodes1`.
497#[doc(alias = "xmlXPathLeadingSorted")]
498pub fn xml_xpath_leading_sorted(
499    nodes1: Option<&XmlNodeSet>,
500    nodes2: Option<&XmlNodeSet>,
501) -> Option<Box<XmlNodeSet>> {
502    let Some(nodes2) = nodes2.filter(|n| !n.is_empty()) else {
503        return nodes1.cloned().map(Box::new);
504    };
505    xml_xpath_node_leading_sorted(nodes1, nodes2.get(1))
506}
507
508/// Implements the EXSLT - Sets leading() function:
509///    node-set set:leading (node-set, node-set)
510/// @nodes is sorted by document order, then #exslSetsNodeLeadingSorted
511/// is called.
512///
513/// Returns the nodes in @nodes that precede @node in document order,
514/// an empty node-set if @nodes doesn't contain @node
515///
516/// # Note
517/// In original libxml, if `nodes2` is NULL, return `nodes1`.  
518/// However, this function returns the clone of `nodes1`.
519#[doc(alias = "xmlXPathNodeLeading")]
520pub fn xml_xpath_node_leading(
521    mut nodes: Option<&mut XmlNodeSet>,
522    node: Option<XmlGenericNodePtr>,
523) -> Option<Box<XmlNodeSet>> {
524    if let Some(nodes) = nodes.as_deref_mut() {
525        nodes.sort();
526    }
527    xml_xpath_node_leading_sorted(nodes.map(|n| &*n), node)
528}
529
530/// Implements the EXSLT - Sets leading() function:
531///    node-set set:leading (node-set, node-set)
532/// @nodes1 and @nodes2 are sorted by document order, then
533/// #exslSetsLeadingSorted is called.
534///
535/// Returns the nodes in @nodes1 that precede the first node in @nodes2
536/// in document order, or an empty node-set if @nodes1 doesn't contain @nodes2
537///
538/// # Note
539/// In original libxml, if `nodes2` is NULL or empty, return `nodes1`.  
540/// However, this function returns the clone of `nodes1`.
541#[doc(alias = "xmlXPathLeading")]
542pub fn xml_xpath_leading(
543    nodes1: Option<&mut XmlNodeSet>,
544    nodes2: Option<&mut XmlNodeSet>,
545) -> Option<Box<XmlNodeSet>> {
546    let Some(nodes2) = nodes2.filter(|n| !n.is_empty()) else {
547        return nodes1.cloned().map(Box::new);
548    };
549    let Some(nodes1) = nodes1.filter(|n| !n.is_empty()) else {
550        return xml_xpath_node_set_create(None);
551    };
552    nodes1.sort();
553    nodes2.sort();
554    xml_xpath_node_leading_sorted(Some(nodes1), nodes2.get(1))
555}
556
557/// Implements the EXSLT - Sets trailing() function:
558///    node-set set:trailing (node-set, node-set)
559///
560/// Returns the nodes in @nodes that follow @node in document order,
561/// @nodes if @node is NULL or an empty node-set if @nodes doesn't contain @node
562///
563/// # Note
564/// In original libxml, if `node` is NULL, return `nodes`.  
565/// However, this function returns the clone of `nodes`.
566#[doc(alias = "xmlXPathNodeTrailingSorted")]
567pub fn xml_xpath_node_trailing_sorted(
568    nodes: &XmlNodeSet,
569    node: Option<XmlGenericNodePtr>,
570) -> Option<Box<XmlNodeSet>> {
571    let Some(node) = node else {
572        return Some(Box::new(nodes.clone()));
573    };
574
575    let mut ret = xml_xpath_node_set_create(None)?;
576    if nodes.is_empty() || !nodes.contains(Some(node)) {
577        return Some(ret);
578    }
579
580    let l = nodes.len();
581    for i in (0..l).rev() {
582        let cur = nodes.get(i).unwrap();
583        if cur == node {
584            break;
585        }
586        // TODO: Propagate memory error.
587        if ret.as_mut().add_unique(cur) < 0 {
588            break;
589        }
590    }
591    ret.as_mut().sort(); /* bug 413451 */
592    Some(ret)
593}
594
595/// Implements the EXSLT - Sets trailing() function:
596///    node-set set:trailing (node-set, node-set)
597///
598/// Returns the nodes in @nodes1 that follow the first node in @nodes2
599/// in document order, @nodes1 if @nodes2 is NULL or empty or
600/// an empty node-set if @nodes1 doesn't contain @nodes2
601///
602/// # Note
603/// In original libxml, if `nodes2` is NULL or empty, return `nodes1`.  
604/// However, this function returns the clone of `nodes1`.
605#[doc(alias = "xmlXPathTrailingSorted")]
606pub fn xml_xpath_trailing_sorted(
607    nodes1: &XmlNodeSet,
608    nodes2: Option<&XmlNodeSet>,
609) -> Option<Box<XmlNodeSet>> {
610    let Some(nodes2) = nodes2.filter(|n| !n.is_empty()) else {
611        return Some(Box::new(nodes1.clone()));
612    };
613    xml_xpath_node_trailing_sorted(nodes1, nodes2.get(0))
614}
615
616/// Implements the EXSLT - Sets trailing() function:
617///    node-set set:trailing (node-set, node-set)
618/// @nodes is sorted by document order, then #xmlXPathNodeTrailingSorted
619/// is called.
620///
621/// Returns the nodes in @nodes that follow @node in document order,
622/// @nodes if @node is NULL or an empty node-set if @nodes doesn't contain @node
623///
624/// # Note
625/// In original libxml, if `nodes2` is NULL or empty, return `nodes1`.  
626/// However, this function returns the clone of `nodes1`.
627#[doc(alias = "xmlXPathNodeTrailing")]
628pub fn xml_xpath_node_trailing(
629    nodes: &mut XmlNodeSet,
630    node: Option<XmlGenericNodePtr>,
631) -> Option<Box<XmlNodeSet>> {
632    nodes.sort();
633    xml_xpath_node_trailing_sorted(nodes, node)
634}
635
636/// Implements the EXSLT - Sets trailing() function:
637///    node-set set:trailing (node-set, node-set)
638/// @nodes1 and @nodes2 are sorted by document order, then
639/// #xmlXPathTrailingSorted is called.
640///
641/// Returns the nodes in @nodes1 that follow the first node in @nodes2
642/// in document order, @nodes1 if @nodes2 is NULL or empty or
643/// an empty node-set if @nodes1 doesn't contain @nodes2
644///
645/// # Note
646/// In original libxml, if `nodes2` is NULL or empty, return `nodes1`.  
647/// However, this function returns the clone of `nodes1`.
648#[doc(alias = "xmlXPathTrailing")]
649pub fn xml_xpath_trailing(
650    nodes1: Option<&mut XmlNodeSet>,
651    nodes2: Option<&mut XmlNodeSet>,
652) -> Option<Box<XmlNodeSet>> {
653    let Some(nodes2) = nodes2.filter(|n| !n.is_empty()) else {
654        return nodes1.cloned().map(Box::new);
655    };
656    let Some(nodes1) = nodes1.filter(|n| !n.is_empty()) else {
657        return xml_xpath_node_set_create(None);
658    };
659    nodes1.sort();
660    nodes2.sort();
661    xml_xpath_node_trailing_sorted(nodes1, nodes2.get(0))
662}
663
664/// Merges two nodesets, all nodes from @set2 are added to @set1.
665/// Checks for duplicate nodes. Clears set2.
666///
667/// Returns @set1 once extended or NULL in case of error.
668///
669/// Frees @set1 in case of error.
670///
671/// # Note
672/// In original libxml, if `nodes2` is NULL or empty, return `nodes1`.  
673/// However, this function returns the clone of `nodes1`.
674#[doc(alias = "xmlXPathNodeSetMergeAndClear")]
675pub(super) fn xml_xpath_node_set_merge_and_clear(
676    set1: Option<Box<XmlNodeSet>>,
677    set2: Option<&mut XmlNodeSet>,
678) -> Option<Box<XmlNodeSet>> {
679    let mut set1 = set1?;
680    let init_nb_set1 = set1.node_tab.len();
681    let set2 = set2?;
682    set2.node_tab.reverse();
683    'b: while let Some(n2) = set2.node_tab.pop() {
684        // Skip duplicates.
685        for &n1 in &set1.node_tab[..init_nb_set1] {
686            if n1 == n2 {
687                // goto skip_node;
688                continue 'b;
689            }
690            if let Some((n1, n2)) = XmlNsPtr::try_from(n1).ok().zip(XmlNsPtr::try_from(n2).ok()) {
691                if n1.node == n2.node && n1.prefix() == n2.prefix() {
692                    // Free the namespace node.
693                    xml_xpath_node_set_free_ns(n2);
694                    // goto skip_node;
695                    continue 'b;
696                }
697            }
698        }
699        // grow the nodeTab if needed
700        if set1.node_tab.len() >= XPATH_MAX_NODESET_LENGTH {
701            xml_xpath_err_memory(None, Some("merging nodeset hit limit\n"));
702            // goto error;
703            xml_xpath_free_node_set(Some(set1));
704            set2.clear(true);
705            return None;
706        }
707        set1.node_tab.push(n2);
708    }
709    Some(set1)
710
711    // error:
712    // xmlXPathFreeNodeSet(set1);
713    // xmlXPathNodeSetClear(set2, 1);
714    // return null_mut();
715}
716
717/// Merges two nodesets, all nodes from @set2 are added to @set1.
718/// Doesn't check for duplicate nodes. Clears set2.
719///
720/// Returns @set1 once extended or NULL in case of error.
721///
722/// Frees @set1 in case of error.
723#[doc(alias = "xmlXPathNodeSetMergeAndClearNoDupls")]
724pub(super) fn xml_xpath_node_set_merge_and_clear_no_dupls(
725    set1: Option<Box<XmlNodeSet>>,
726    set2: Option<&mut XmlNodeSet>,
727) -> Option<Box<XmlNodeSet>> {
728    let mut set1 = set1?;
729    let set2 = set2?;
730    set2.node_tab.reverse();
731    while let Some(n2) = set2.node_tab.pop() {
732        if set1.node_tab.len() >= XPATH_MAX_NODESET_LENGTH {
733            xml_xpath_err_memory(None, Some("merging nodeset hit limit\n"));
734            // goto error;
735            xml_xpath_free_node_set(Some(set1));
736            set2.clear(true);
737            return None;
738        }
739        set1.node_tab.push(n2);
740    }
741    Some(set1)
742
743    // error:
744    //     xmlXPathFreeNodeSet(set1);
745    //     xmlXPathNodeSetClear(set2, 1);
746    //     return null_mut();
747}
748
749/// Merges two nodesets, all nodes from @val2 are added to @val1
750/// if @val1 is NULL, a new set is created and copied from @val2
751///
752/// Returns @val1 once extended or NULL in case of error.
753///
754/// Frees @val1 in case of error.
755#[doc(alias = "xmlXPathNodeSetMerge")]
756pub fn xml_xpath_node_set_merge(
757    val1: Option<Box<XmlNodeSet>>,
758    val2: Option<&XmlNodeSet>,
759) -> Option<Box<XmlNodeSet>> {
760    let mut skip: i32;
761
762    let Some(val2) = val2 else {
763        return val1;
764    };
765    let mut val1 = val1.or_else(|| xml_xpath_node_set_create(None))?;
766
767    // @@ with_ns to check whether namespace nodes should be looked at @@
768    let init_nr = val1.as_ref().node_tab.len();
769
770    for &n2 in &val2.node_tab {
771        // check against duplicates
772        skip = 0;
773        for &n1 in &val1.node_tab[..init_nr] {
774            if n1 == n2
775                || XmlNsPtr::try_from(n1)
776                    .ok()
777                    .zip(XmlNsPtr::try_from(n2).ok())
778                    .filter(|(n1, n2)| n1.node == n2.node)
779                    .filter(|(n1, n2)| n1.prefix == n2.prefix)
780                    .is_some()
781            {
782                skip = 1;
783                break;
784            }
785        }
786        if skip != 0 {
787            continue;
788        }
789
790        // grow the nodeTab if needed
791        if val1.node_tab.len() >= XPATH_MAX_NODESET_LENGTH {
792            xml_xpath_err_memory(None, Some("merging nodeset hit limit\n"));
793            // goto error;
794            xml_xpath_free_node_set(Some(val1));
795            return None;
796        }
797        if let Ok(ns) = XmlNsPtr::try_from(n2) {
798            let Some(ns_node) =
799                xml_xpath_node_set_dup_ns(ns.node.or(ns.next.map(|next| next.into())), ns)
800            else {
801                xml_xpath_free_node_set(Some(val1));
802                return None;
803            };
804
805            val1.node_tab.push(ns_node);
806        } else {
807            val1.node_tab.push(n2);
808        }
809    }
810
811    Some(val1)
812
813    // error:
814    // xmlXPathFreeNodeSet(val1);
815    // return null_mut();
816}
817
818/// Create a new xmlXPathObjectPtr of type NodeSet and initialize
819/// it with the Nodeset @val
820///
821/// Returns the newly created object.
822#[doc(alias = "xmlXPathNewNodeSetList")]
823pub fn xml_xpath_new_node_set_list(val: Option<&mut XmlNodeSet>) -> Option<XmlXPathObject> {
824    if let Some(val) = val {
825        let mut ret = xml_xpath_new_node_set(Some(val.node_tab[0]));
826        if let Some(nodeset) = ret.nodesetval.as_deref_mut() {
827            for &node in &val.node_tab[1..] {
828                // TODO: Propagate memory error.
829                if nodeset.add_unique(node) < 0 {
830                    break;
831                }
832            }
833        }
834        Some(ret)
835    } else {
836        None
837    }
838}