Skip to main content

libxml_rs/xml/list/
mod.rs

1//! Linked list — public xmlList API (§85 Phase 1).
2//!
3//! Implements libxml2's linked list.
4//!
5//! # UPSTREAM-PARITY
6//!
7//! The linked list supports:
8//! - Create/delete with custom deallocator
9//! - Push front/back, pop front/back
10//! - Insert/append at arbitrary positions
11//! - Search with custom comparator
12//! - Walk with callback
13//! - Remove first/last/all matching entries
14//! - Clear
15//! - Empty/front/back/size queries
16//! - Sort, reverse, reverse splice, merge
17//!
18//! # Phase 1 status
19//!
20//! Complete — all list operations are implemented.
21
22use core::ffi::c_void;
23use core::ptr;
24use std::os::raw::c_int;
25
26use crate::abi::allocator;
27
28// ═══════════════════════════════════════════════════════════════════════════════
29// Types
30// ═══════════════════════════════════════════════════════════════════════════════
31
32/// Deallocator function for list data.
33pub type xmlListDeallocator = unsafe extern "C" fn(*mut c_void);
34
35/// Data comparator function. Returns 0 if equal, non-zero if different.
36pub type xmlListDataCompare = unsafe extern "C" fn(*const c_void, *const c_void) -> c_int;
37
38/// Walker function for xmlListWalk.
39pub type xmlListWalker = unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int;
40
41/// A linked list node.
42struct ListNode {
43    data: *mut c_void,
44    prev: *mut ListNode,
45    next: *mut ListNode,
46}
47
48/// The linked list struct.
49#[derive(Debug)]
50pub struct List {
51    front: *mut ListNode,
52    back: *mut ListNode,
53    count: usize,
54    deallocator: Option<xmlListDeallocator>,
55    comparator: Option<xmlListDataCompare>,
56}
57
58// ═══════════════════════════════════════════════════════════════════════════════
59// Public API
60// ═══════════════════════════════════════════════════════════════════════════════
61
62/// Create a new linked list.
63///
64/// # UPSTREAM-PARITY
65///
66/// ```c
67/// xmlListPtr xmlListCreate(xmlListDeallocator deallocator,
68///                          xmlListDataCompare comparator);
69/// ```
70///
71/// Creates a linked list with the given deallocator and comparator.
72/// Both may be NULL.
73pub fn list_create(
74    deallocator: Option<xmlListDeallocator>,
75    comparator: Option<xmlListDataCompare>,
76) -> *mut List {
77    let list = Box::new(List {
78        front: ptr::null_mut(),
79        back: ptr::null_mut(),
80        count: 0,
81        deallocator,
82        comparator,
83    });
84
85    Box::into_raw(list)
86}
87
88/// Delete a linked list and all its nodes.
89///
90/// # UPSTREAM-PARITY
91///
92/// ```c
93/// void xmlListDelete(xmlListPtr l);
94/// ```
95///
96/// # SAFETY
97///
98/// - `l` must be a valid pointer to a List, or NULL.
99pub unsafe fn list_delete(l: *mut List) {
100    if l.is_null() {
101        return;
102    }
103
104    let list = unsafe { &mut *l };
105    let mut cur = list.front;
106
107    while !cur.is_null() {
108        let next = unsafe { (*cur).next };
109        // UPSTREAM-PARITY (list.c xmlListDeleteInternal): the deallocator
110        // receives the LINK pointer, not the data.
111        if let Some(dealloc) = list.deallocator {
112            unsafe { dealloc(cur as *mut c_void) };
113        }
114        unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
115        cur = next;
116    }
117
118    drop(Box::from_raw(l));
119}
120
121/// Search the list for data matching the given key.
122///
123/// # UPSTREAM-PARITY
124///
125/// ```c
126/// void *xmlListSearch(xmlListPtr l, void *data);
127/// ```
128///
129/// Uses the list's comparator function. Returns the matching data, or NULL.
130///
131/// # SAFETY
132///
133/// - `l` must be a valid pointer to a List, or NULL.
134pub unsafe fn list_search(l: *mut List, data: *const c_void) -> *mut c_void {
135    if l.is_null() {
136        return ptr::null_mut();
137    }
138
139    let list = unsafe { &*l };
140    let comparator = match list.comparator {
141        Some(c) => c,
142        None => return ptr::null_mut(),
143    };
144
145    let mut cur = list.front;
146    while !cur.is_null() {
147        let node = unsafe { &*cur };
148        if unsafe { comparator(node.data as *const c_void, data) == 0 } {
149            return node.data;
150        }
151        cur = node.next;
152    }
153
154    ptr::null_mut()
155}
156
157/// Return the last element of a list (upstream list.c `xmlListEnd`): the
158/// data of the last node, or NULL.
159///
160/// # SAFETY
161///
162/// - `l` must be a valid list pointer or NULL.
163pub unsafe fn list_end(l: *mut List) -> *mut c_void {
164    if l.is_null() {
165        return ptr::null_mut();
166    }
167    let list = unsafe { &*l };
168    if list.back.is_null() {
169        return ptr::null_mut();
170    }
171    unsafe { (*list.back).data }
172}
173
174/// Reverse-search a list with the comparator (upstream list.c
175/// `xmlListReverseSearch`): scans from the back, returns the first (from
176/// the end) matching node's data, or NULL.
177///
178/// # SAFETY
179///
180/// - `l` must be a valid list pointer or NULL.
181/// - `data` must be valid for the comparator.
182pub unsafe fn list_reverse_search(l: *mut List, data: *const c_void) -> *mut c_void {
183    if l.is_null() {
184        return ptr::null_mut();
185    }
186    let list = unsafe { &*l };
187    let comparator = match list.comparator {
188        Some(c) => c,
189        None => return ptr::null_mut(),
190    };
191    let mut cur = list.back;
192    while !cur.is_null() {
193        // SAFETY: cur is a valid node; comparator is valid.
194        let node_data = unsafe { (*cur).data };
195        if unsafe { comparator(node_data as *const c_void, data) } == 0 {
196            return node_data;
197        }
198        cur = unsafe { (*cur).prev };
199    }
200    ptr::null_mut()
201}
202
203/// Walk a list in reverse with a walker callback (upstream list.c
204/// `xmlListReverseWalk`).
205///
206/// # SAFETY
207///
208/// - `l` must be a valid list pointer or NULL.
209/// - `walker` may be NULL (no-op).
210pub unsafe fn list_reverse_walk(l: *mut List, walker: Option<xmlListWalker>, data: *mut c_void) {
211    if l.is_null() {
212        return;
213    }
214    let walker = match walker {
215        Some(w) => w,
216        None => return,
217    };
218    let list = unsafe { &*l };
219    let mut cur = list.back;
220    while !cur.is_null() {
221        // SAFETY: cur is a valid node; walker is valid.
222        let node_data = unsafe { (*cur).data };
223        // UPSTREAM-PARITY (list.c xmlListReverseWalk): returns 0 to stop.
224        if unsafe { walker(node_data, data) == 0 } {
225            return;
226        }
227        cur = unsafe { (*cur).prev };
228    }
229}
230
231/// Duplicate a list (upstream list.c `xmlListDup`): a shallow copy using
232/// the same deallocator/comparator; node data pointers are copied as-is.
233/// Returns the new list or NULL on allocation failure.
234///
235/// # SAFETY
236///
237/// - `l` must be a valid list pointer or NULL.
238pub unsafe fn list_dup(l: *mut List) -> *mut List {
239    if l.is_null() {
240        return ptr::null_mut();
241    }
242    let list = unsafe { &*l };
243    let new_list = Box::new(List {
244        front: ptr::null_mut(),
245        back: ptr::null_mut(),
246        count: 0,
247        deallocator: list.deallocator,
248        comparator: list.comparator,
249    });
250    let new_ptr = Box::into_raw(new_list);
251    let mut cur = list.front;
252    while !cur.is_null() {
253        // SAFETY: cur is valid; the node data is shallow-copied.
254        let data = unsafe { (*cur).data };
255        if unsafe { list_push_back(new_ptr, data) } != 0 {
256            unsafe { list_delete(new_ptr) };
257            return ptr::null_mut();
258        }
259        cur = unsafe { (*cur).next };
260    }
261    new_ptr
262}
263
264/// Copy a list with a data copier (upstream list.c `xmlListCopy`): each
265/// node's data is copied through `copier` (returns a fresh pointer or
266/// NULL on failure). The result replaces the target list `l`'s content.
267/// Returns 0 on success, -1 on error.
268///
269/// # SAFETY
270///
271/// - `l` must be a valid list pointer or NULL.
272/// - `copier` must be a valid copier function.
273pub unsafe fn list_copy(
274    l: *mut List,
275    copier: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
276) -> c_int {
277    if l.is_null() {
278        return -1;
279    }
280    let copier = match copier {
281        Some(c) => c,
282        None => return -1,
283    };
284    let list = unsafe { &*l };
285    let new_list = Box::new(List {
286        front: ptr::null_mut(),
287        back: ptr::null_mut(),
288        count: 0,
289        deallocator: list.deallocator,
290        comparator: list.comparator,
291    });
292    let new_ptr = Box::into_raw(new_list);
293    let mut cur = list.front;
294    while !cur.is_null() {
295        // SAFETY: cur is valid; copier must return a fresh copy or NULL.
296        let copied = unsafe { copier((*cur).data) };
297        if copied.is_null() {
298            unsafe { list_delete(new_ptr) };
299            return -1;
300        }
301        if unsafe { list_push_back(new_ptr, copied) } != 0 {
302            unsafe { list_delete(new_ptr) };
303            return -1;
304        }
305        cur = unsafe { (*cur).next };
306    }
307    // Replace `l`'s content with the copy (upstream copies INTO `l`).
308    unsafe {
309        list_clear(l);
310        let dst = &mut *l;
311        let src = &mut *new_ptr;
312        core::mem::swap(dst, src);
313        list_delete(new_ptr);
314    }
315    0
316}
317
318/// Return the data stored in a link (upstream list.c `xmlLinkGetData`).
319///
320/// # SAFETY
321///
322/// - `link` must be a valid link pointer or NULL.
323pub unsafe fn link_get_data(link: *mut c_void) -> *mut c_void {
324    if link.is_null() {
325        return ptr::null_mut();
326    }
327    unsafe { (*(link as *mut ListNode)).data }
328}
329
330/// Walk the list, calling the walker function for each element.
331///
332/// # UPSTREAM-PARITY
333///
334/// ```c
335/// void xmlListWalk(xmlListPtr l, xmlListWalker walker, void *data);
336/// ```
337///
338/// # SAFETY
339///
340/// - `l` must be a valid pointer to a List, or NULL.
341/// - `walker` must be a valid function pointer or NULL.
342pub unsafe fn list_walk(l: *mut List, walker: Option<xmlListWalker>, data: *mut c_void) {
343    if l.is_null() || walker.is_none() {
344        return;
345    }
346    let walker = walker.unwrap();
347
348    let list = unsafe { &*l };
349    let mut cur = list.front;
350    while !cur.is_null() {
351        let node = unsafe { &*cur };
352        // UPSTREAM-PARITY (list.c xmlListWalk): the walker returns 0 to stop.
353        if unsafe { walker(node.data, data) == 0 } {
354            break;
355        }
356        cur = node.next;
357    }
358}
359
360/// Push data to the back of the list.
361///
362/// # UPSTREAM-PARITY
363///
364/// ```c
365/// int xmlListPushBack(xmlListPtr l, void *data);
366/// ```
367///
368/// Returns 0 on success, -1 on failure.
369///
370/// # SAFETY
371///
372/// - `l` must be a valid pointer to a List, or NULL.
373pub unsafe fn list_push_back(l: *mut List, data: *mut c_void) -> c_int {
374    if l.is_null() {
375        return -1;
376    }
377
378    let list = unsafe { &mut *l };
379
380    let node = allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
381    if node.is_null() {
382        return -1;
383    }
384
385    unsafe {
386        (*node).data = data;
387        (*node).prev = list.back;
388        (*node).next = ptr::null_mut();
389    }
390
391    if list.back.is_null() {
392        list.front = node;
393        list.back = node;
394    } else {
395        unsafe { (*list.back).next = node };
396        list.back = node;
397    }
398
399    list.count += 1;
400    0
401}
402
403/// Push data to the front of the list.
404///
405/// # UPSTREAM-PARITY
406///
407/// ```c
408/// int xmlListPushFront(xmlListPtr l, void *data);
409/// ```
410///
411/// Returns 0 on success, -1 on failure.
412///
413/// # SAFETY
414///
415/// - `l` must be a valid pointer to a List, or NULL.
416pub unsafe fn list_push_front(l: *mut List, data: *mut c_void) -> c_int {
417    if l.is_null() {
418        return -1;
419    }
420
421    let list = unsafe { &mut *l };
422
423    let node = allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
424    if node.is_null() {
425        return -1;
426    }
427
428    unsafe {
429        (*node).data = data;
430        (*node).prev = ptr::null_mut();
431        (*node).next = list.front;
432    }
433
434    if list.front.is_null() {
435        list.front = node;
436        list.back = node;
437    } else {
438        unsafe { (*list.front).prev = node };
439        list.front = node;
440    }
441
442    list.count += 1;
443    0
444}
445
446/// Pop data from the back of the list.
447///
448/// # UPSTREAM-PARITY
449///
450/// ```c
451/// void xmlListPopBack(xmlListPtr l);
452/// ```
453///
454/// # SAFETY
455///
456/// - `l` must be a valid pointer to a List, or NULL.
457pub unsafe fn list_pop_back(l: *mut List) {
458    if l.is_null() {
459        return;
460    }
461
462    let list = unsafe { &mut *l };
463    if list.back.is_null() {
464        return;
465    }
466
467    let node = list.back;
468    let prev = unsafe { (*node).prev };
469
470    if let Some(dealloc) = list.deallocator {
471        unsafe { dealloc((*node).data) };
472    }
473    unsafe { allocator::xmlFreeImpl(node as *mut c_void) };
474
475    list.back = prev;
476    if prev.is_null() {
477        list.front = ptr::null_mut();
478    } else {
479        unsafe { (*prev).next = ptr::null_mut() };
480    }
481
482    list.count = list.count.saturating_sub(1);
483}
484
485/// Pop data from the front of the list.
486///
487/// # UPSTREAM-PARITY
488///
489/// ```c
490/// void xmlListPopFront(xmlListPtr l);
491/// ```
492///
493/// # SAFETY
494///
495/// - `l` must be a valid pointer to a List, or NULL.
496pub unsafe fn list_pop_front(l: *mut List) {
497    if l.is_null() {
498        return;
499    }
500
501    let list = unsafe { &mut *l };
502    if list.front.is_null() {
503        return;
504    }
505
506    let node = list.front;
507    let next = unsafe { (*node).next };
508
509    if let Some(dealloc) = list.deallocator {
510        unsafe { dealloc((*node).data) };
511    }
512    unsafe { allocator::xmlFreeImpl(node as *mut c_void) };
513
514    list.front = next;
515    if next.is_null() {
516        list.back = ptr::null_mut();
517    } else {
518        unsafe { (*next).prev = ptr::null_mut() };
519    }
520
521    list.count = list.count.saturating_sub(1);
522}
523
524/// Insert data into the sorted position.
525///
526/// # UPSTREAM-PARITY
527///
528/// ```c
529/// int xmlListInsert(xmlListPtr l, void *data);
530/// ```
531///
532/// Inserts data in sorted order using the list's comparator.
533/// Returns 0 on success, -1 on failure.
534///
535/// # SAFETY
536///
537/// - `l` must be a valid pointer to a List, or NULL.
538pub unsafe fn list_insert(l: *mut List, data: *mut c_void) -> c_int {
539    if l.is_null() {
540        return -1;
541    }
542
543    let list = unsafe { &mut *l };
544
545    // If no comparator, just push back
546    let comparator = match list.comparator {
547        Some(c) => c,
548        None => return list_push_back(l, data),
549    };
550
551    // Find insertion point
552    let mut cur = list.front;
553    while !cur.is_null() {
554        let node = unsafe { &*cur };
555        if unsafe { comparator(data as *const c_void, node.data as *const c_void) <= 0 } {
556            // Insert before cur
557            let new_node =
558                allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
559            if new_node.is_null() {
560                return -1;
561            }
562            unsafe {
563                (*new_node).data = data;
564                (*new_node).prev = node.prev;
565                (*new_node).next = cur;
566                if !node.prev.is_null() {
567                    (*node.prev).next = new_node;
568                } else {
569                    list.front = new_node;
570                }
571                (*cur).prev = new_node;
572            }
573            list.count += 1;
574            return 0;
575        }
576        cur = node.next;
577    }
578
579    // Append at end
580    list_push_back(l, data)
581}
582
583/// Append data to the end of the list (alias for push_back).
584///
585/// # UPSTREAM-PARITY
586///
587/// ```c
588/// int xmlListAppend(xmlListPtr l, void *data);
589/// ```
590///
591/// # SAFETY
592///
593/// - `l`, `data` must be valid pointers (or NULL
594///   where the upstream C contract allows), obtained from the
595///   matching constructor/owner and not yet freed; the callee may
596///   take or keep ownership exactly as the C API specifies.
597///
598/// The caller must not race this call with concurrent mutation of the
599/// same objects from other threads (per-object state is not internally
600/// synchronized). Violating any of the above is undefined behavior.
601///
602/// Exercised by the C-API differential courts
603/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
604/// courts; those pass byte-for-byte against the upstream oracle.
605pub unsafe fn list_append(l: *mut List, data: *mut c_void) -> c_int {
606    // UPSTREAM-PARITY (list.c xmlListAppend): with a comparator the new
607    // element is inserted in sorted order (before the first node whose data
608    // compares greater); without one it is pushed to the back.
609    if l.is_null() {
610        return -1;
611    }
612    let comparator = unsafe { (*l).comparator };
613    match comparator {
614        None => unsafe { list_push_back(l, data) },
615        Some(cmp) => {
616            unsafe {
617                let mut cur = (*l).front;
618                while !cur.is_null() {
619                    if cmp((*cur).data as *const c_void, data as *const c_void) > 0 {
620                        break;
621                    }
622                    cur = (*cur).next;
623                }
624                // Insert before `cur`.
625                let node =
626                    allocator::xmlMallocZero(size_of::<ListNode>() as usize) as *mut ListNode;
627                if node.is_null() {
628                    return -1;
629                }
630                (*node).data = data;
631                (*node).next = cur;
632                if cur.is_null() {
633                    (*node).prev = (*l).back;
634                    if !(*l).back.is_null() {
635                        (*(*l).back).next = node;
636                    }
637                    (*l).back = node;
638                    if (*l).front.is_null() {
639                        (*l).front = node;
640                    }
641                } else {
642                    (*node).prev = (*cur).prev;
643                    if !(*cur).prev.is_null() {
644                        (*(*cur).prev).next = node;
645                    } else {
646                        (*l).front = node;
647                    }
648                    (*cur).prev = node;
649                }
650            }
651            0
652        }
653    }
654}
655
656/// Remove the first matching element.
657///
658/// # UPSTREAM-PARITY
659///
660/// ```c
661/// int xmlListRemoveFirst(xmlListPtr l, void *data);
662/// ```
663///
664/// Returns 0 on success, -1 if not found.
665///
666/// # SAFETY
667///
668/// - `l` must be a valid pointer to a List, or NULL.
669pub unsafe fn list_remove_first(l: *mut List, data: *const c_void) -> c_int {
670    if l.is_null() {
671        return -1;
672    }
673
674    let list = unsafe { &mut *l };
675    let comparator = match list.comparator {
676        Some(c) => c,
677        None => return -1,
678    };
679
680    let mut cur = list.front;
681    while !cur.is_null() {
682        let node = unsafe { &*cur };
683        let next = node.next;
684        if unsafe { comparator(node.data as *const c_void, data) == 0 } {
685            // Remove this node
686            if !node.prev.is_null() {
687                unsafe { (*node.prev).next = node.next };
688            } else {
689                list.front = node.next;
690            }
691            if !node.next.is_null() {
692                unsafe { (*node.next).prev = node.prev };
693            } else {
694                list.back = node.prev;
695            }
696
697            if let Some(dealloc) = list.deallocator {
698                unsafe { dealloc(node.data) };
699            }
700            unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
701            list.count = list.count.saturating_sub(1);
702            return 0;
703        }
704        cur = next;
705    }
706
707    -1
708}
709
710/// Remove the last matching element.
711///
712/// # UPSTREAM-PARITY
713///
714/// ```c
715/// int xmlListRemoveLast(xmlListPtr l, void *data);
716/// ```
717///
718/// Returns 0 on success, -1 if not found.
719///
720/// # SAFETY
721///
722/// - `l` must be a valid pointer to a List, or NULL.
723pub unsafe fn list_remove_last(l: *mut List, data: *const c_void) -> c_int {
724    if l.is_null() {
725        return -1;
726    }
727
728    let list = unsafe { &mut *l };
729    let comparator = match list.comparator {
730        Some(c) => c,
731        None => return -1,
732    };
733
734    let mut cur = list.back;
735    while !cur.is_null() {
736        let node = unsafe { &*cur };
737        let prev = node.prev;
738        if unsafe { comparator(node.data as *const c_void, data) == 0 } {
739            // Remove this node
740            if !node.prev.is_null() {
741                unsafe { (*node.prev).next = node.next };
742            } else {
743                list.front = node.next;
744            }
745            if !node.next.is_null() {
746                unsafe { (*node.next).prev = node.prev };
747            } else {
748                list.back = node.prev;
749            }
750
751            if let Some(dealloc) = list.deallocator {
752                unsafe { dealloc(node.data) };
753            }
754            unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
755            list.count = list.count.saturating_sub(1);
756            return 0;
757        }
758        cur = prev;
759    }
760
761    -1
762}
763
764/// Remove all matching elements.
765///
766/// # UPSTREAM-PARITY
767///
768/// ```c
769/// int xmlListRemoveAll(xmlListPtr l, void *data);
770/// ```
771///
772/// Returns the number of elements removed.
773///
774/// # SAFETY
775///
776/// - `l` must be a valid pointer to a List, or NULL.
777pub unsafe fn list_remove_all(l: *mut List, data: *const c_void) -> c_int {
778    if l.is_null() {
779        return 0;
780    }
781
782    let list = unsafe { &mut *l };
783    let comparator = match list.comparator {
784        Some(c) => c,
785        None => return 0,
786    };
787
788    let mut removed = 0;
789    let mut cur = list.front;
790
791    while !cur.is_null() {
792        let node = unsafe { &*cur };
793        let next = node.next;
794
795        if unsafe { comparator(node.data as *const c_void, data) == 0 } {
796            // Remove this node
797            if !node.prev.is_null() {
798                unsafe { (*node.prev).next = node.next };
799            } else {
800                list.front = node.next;
801            }
802            if !node.next.is_null() {
803                unsafe { (*node.next).prev = node.prev };
804            } else {
805                list.back = node.prev;
806            }
807
808            if let Some(dealloc) = list.deallocator {
809                unsafe { dealloc(node.data) };
810            }
811            unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
812            list.count = list.count.saturating_sub(1);
813            removed += 1;
814        }
815
816        cur = next;
817    }
818
819    removed
820}
821
822/// Clear the list (remove all elements).
823///
824/// # UPSTREAM-PARITY
825///
826/// ```c
827/// void xmlListClear(xmlListPtr l);
828/// ```
829///
830/// # SAFETY
831///
832/// - `l` must be a valid pointer to a List, or NULL.
833pub unsafe fn list_clear(l: *mut List) {
834    if l.is_null() {
835        return;
836    }
837
838    let list = unsafe { &mut *l };
839    let mut cur = list.front;
840
841    while !cur.is_null() {
842        let next = unsafe { (*cur).next };
843        if let Some(dealloc) = list.deallocator {
844            unsafe { dealloc((*cur).data) };
845        }
846        unsafe { allocator::xmlFreeImpl(cur as *mut c_void) };
847        cur = next;
848    }
849
850    list.front = ptr::null_mut();
851    list.back = ptr::null_mut();
852    list.count = 0;
853}
854
855/// Check if the list is empty.
856///
857/// # UPSTREAM-PARITY
858///
859/// ```c
860/// int xmlListEmpty(xmlListPtr l);
861/// ```
862///
863/// Returns 1 if empty, 0 if not empty.
864pub fn list_empty(l: *mut List) -> c_int {
865    if l.is_null() {
866        return 1;
867    }
868    let list = unsafe { &*l };
869    if list.front.is_null() {
870        1
871    } else {
872        0
873    }
874}
875
876/// Get the data at the front of the list.
877///
878/// # UPSTREAM-PARITY
879///
880/// ```c
881/// void *xmlListFront(xmlListPtr l);
882/// ```
883///
884/// Returns the data at the front, or NULL if the list is empty.
885pub fn list_front(l: *mut List) -> *mut c_void {
886    if l.is_null() {
887        return ptr::null_mut();
888    }
889    let list = unsafe { &*l };
890    if list.front.is_null() {
891        ptr::null_mut()
892    } else {
893        unsafe { (*list.front).data }
894    }
895}
896
897/// Get the data at the back of the list.
898///
899/// # UPSTREAM-PARITY
900///
901/// ```c
902/// void *xmlListBack(xmlListPtr l);
903/// ```
904///
905/// Returns the data at the back, or NULL if the list is empty.
906pub fn list_back(l: *mut List) -> *mut c_void {
907    if l.is_null() {
908        return ptr::null_mut();
909    }
910    let list = unsafe { &*l };
911    if list.back.is_null() {
912        ptr::null_mut()
913    } else {
914        unsafe { (*list.back).data }
915    }
916}
917
918/// Get the number of elements in the list.
919///
920/// # UPSTREAM-PARITY
921///
922/// ```c
923/// int xmlListSize(xmlListPtr l);
924/// ```
925///
926/// Returns the number of elements, or -1 if the list is NULL.
927pub fn list_size(l: *mut List) -> c_int {
928    if l.is_null() {
929        return -1;
930    }
931    let list = unsafe { &*l };
932    list.count as c_int
933}
934
935/// Sort the list in-place using the comparator.
936///
937/// # UPSTREAM-PARITY
938///
939/// ```c
940/// void xmlListSort(xmlListPtr l);
941/// ```
942///
943/// # SAFETY
944///
945/// - `l` must be a valid pointer to a List, or NULL.
946pub unsafe fn list_sort(l: *mut List) {
947    if l.is_null() {
948        return;
949    }
950
951    let list = unsafe { &mut *l };
952    if list.count <= 1 {
953        return;
954    }
955
956    let comparator = match list.comparator {
957        Some(c) => c,
958        None => return,
959    };
960
961    // Convert to Vec, sort, rebuild
962    let mut nodes: Vec<*mut ListNode> = Vec::with_capacity(list.count);
963    let mut cur = list.front;
964    while !cur.is_null() {
965        nodes.push(cur);
966        cur = unsafe { (*cur).next };
967    }
968
969    // Bubble sort (simple, matches upstream's simple approach)
970    for i in 0..nodes.len() {
971        for j in 0..nodes.len() - 1 - i {
972            let a = unsafe { &*nodes[j] };
973            let b = unsafe { &*nodes[j + 1] };
974            if unsafe { comparator(a.data as *const c_void, b.data as *const c_void) > 0 } {
975                nodes.swap(j, j + 1);
976            }
977        }
978    }
979
980    // Rebuild links
981    list.front = nodes[0];
982    list.back = nodes[nodes.len() - 1];
983
984    for i in 0..nodes.len() {
985        unsafe {
986            (*nodes[i]).prev = if i > 0 { nodes[i - 1] } else { ptr::null_mut() };
987            (*nodes[i]).next = if i + 1 < nodes.len() {
988                nodes[i + 1]
989            } else {
990                ptr::null_mut()
991            };
992        }
993    }
994}
995
996/// Reverse the list in-place.
997///
998/// # UPSTREAM-PARITY
999///
1000/// ```c
1001/// void xmlListReverse(xmlListPtr l);
1002/// ```
1003///
1004/// # SAFETY
1005///
1006/// - `l` must be a valid pointer to a List, or NULL.
1007pub unsafe fn list_reverse(l: *mut List) {
1008    if l.is_null() {
1009        return;
1010    }
1011
1012    let list = unsafe { &mut *l };
1013
1014    // `cur` keeps the old front (the new back): the walk below traverses the
1015    // old-next chain to the old back, swapping each node's links.
1016    let mut cur = list.front;
1017    std::mem::swap(&mut list.front, &mut list.back);
1018
1019    while !cur.is_null() {
1020        let next = unsafe { (*cur).next };
1021        unsafe {
1022            (*cur).next = (*cur).prev;
1023            (*cur).prev = next;
1024        }
1025        cur = next;
1026    }
1027}
1028
1029/// Reverse splice: move all elements from `l2` to the front of `l1` in reverse order.
1030///
1031/// # UPSTREAM-PARITY
1032///
1033/// ```c
1034/// void xmlListReverseSplice(xmlListPtr l1, xmlListPtr l2);
1035/// ```
1036///
1037/// # SAFETY
1038///
1039/// - `l1`, `l2` must be valid pointers to Lists, or NULL.
1040pub unsafe fn list_reverse_splice(l1: *mut List, l2: *mut List) {
1041    if l1.is_null() || l2.is_null() {
1042        return;
1043    }
1044
1045    let list1 = unsafe { &mut *l1 };
1046    let list2 = unsafe { &mut *l2 };
1047
1048    if list2.front.is_null() {
1049        return;
1050    }
1051
1052    // Reverse l2 first
1053    list_reverse(l2);
1054
1055    // Move all nodes from l2 to front of l1
1056    unsafe {
1057        (*list2.back).next = list1.front;
1058        if !list1.front.is_null() {
1059            (*list1.front).prev = list2.back;
1060        } else {
1061            list1.back = list2.back;
1062        }
1063        list1.front = list2.front;
1064    }
1065
1066    list1.count += list2.count;
1067
1068    // Clear l2
1069    list2.front = ptr::null_mut();
1070    list2.back = ptr::null_mut();
1071    list2.count = 0;
1072}
1073
1074/// Merge two sorted lists into one.
1075///
1076/// # UPSTREAM-PARITY
1077///
1078/// ```c
1079/// void xmlListMerge(xmlListPtr l1, xmlListPtr l2);
1080/// ```
1081///
1082/// Merges `l2` into `l1` in sorted order. `l2` becomes empty.
1083///
1084/// # SAFETY
1085///
1086/// - `l1`, `l2` must be valid pointers to Lists, or NULL.
1087pub unsafe fn list_merge(l1: *mut List, l2: *mut List) {
1088    if l1.is_null() || l2.is_null() {
1089        return;
1090    }
1091
1092    let list1 = unsafe { &mut *l1 };
1093    let list2 = unsafe { &mut *l2 };
1094
1095    if list2.front.is_null() {
1096        return;
1097    }
1098
1099    let comparator = match list1.comparator {
1100        Some(c) => c,
1101        None => {
1102            // No comparator — just append all of l2 to l1
1103            if !list1.back.is_null() {
1104                unsafe { (*list1.back).next = list2.front };
1105                unsafe { (*list2.front).prev = list1.back };
1106            } else {
1107                list1.front = list2.front;
1108            }
1109            list1.back = list2.back;
1110            list1.count += list2.count;
1111            list2.front = ptr::null_mut();
1112            list2.back = ptr::null_mut();
1113            list2.count = 0;
1114            return;
1115        }
1116    };
1117
1118    // Merge sorted lists
1119    let mut cur2 = list2.front;
1120    let mut insert_before = list1.front;
1121
1122    while !cur2.is_null() {
1123        let next2 = unsafe { (*cur2).next };
1124
1125        // Find insertion point
1126        while !insert_before.is_null() {
1127            if unsafe {
1128                comparator(
1129                    (*cur2).data as *const c_void,
1130                    (*insert_before).data as *const c_void,
1131                ) <= 0
1132            } {
1133                break;
1134            }
1135            insert_before = unsafe { (*insert_before).next };
1136        }
1137
1138        // Insert cur2 before insert_before
1139        if insert_before.is_null() {
1140            // Append at end
1141            if list1.back.is_null() {
1142                list1.front = cur2;
1143                list1.back = cur2;
1144                unsafe {
1145                    (*cur2).prev = ptr::null_mut();
1146                    (*cur2).next = ptr::null_mut();
1147                }
1148            } else {
1149                unsafe {
1150                    (*cur2).prev = list1.back;
1151                    (*cur2).next = ptr::null_mut();
1152                    (*list1.back).next = cur2;
1153                }
1154                list1.back = cur2;
1155            }
1156        } else {
1157            unsafe {
1158                (*cur2).prev = (*insert_before).prev;
1159                (*cur2).next = insert_before;
1160                if !(*insert_before).prev.is_null() {
1161                    (*(*insert_before).prev).next = cur2;
1162                } else {
1163                    list1.front = cur2;
1164                }
1165                (*insert_before).prev = cur2;
1166            }
1167        }
1168
1169        list1.count += 1;
1170        cur2 = next2;
1171    }
1172
1173    // Clear l2
1174    list2.front = ptr::null_mut();
1175    list2.back = ptr::null_mut();
1176    list2.count = 0;
1177}
1178
1179// ═══════════════════════════════════════════════════════════════════════════════
1180// Tests
1181// ═══════════════════════════════════════════════════════════════════════════════
1182
1183#[cfg(test)]
1184mod tests {
1185    use super::*;
1186
1187    unsafe extern "C" fn int_compare(a: *const c_void, b: *const c_void) -> c_int {
1188        let ai = *(a as *const i32);
1189        let bi = *(b as *const i32);
1190        ai.cmp(&bi) as c_int
1191    }
1192
1193    #[test]
1194    fn test_list_create_delete() {
1195        unsafe {
1196            let list = list_create(None, None);
1197            assert!(!list.is_null());
1198            list_delete(list);
1199        }
1200    }
1201
1202    #[test]
1203    fn test_list_push_pop() {
1204        unsafe {
1205            let list = list_create(None, None);
1206            let v1 = &mut 1 as *mut c_int as *mut c_void;
1207            let v2 = &mut 2 as *mut c_int as *mut c_void;
1208
1209            list_push_back(list, v1);
1210            list_push_back(list, v2);
1211            assert_eq!(list_size(list), 2);
1212
1213            assert_eq!(*(list_front(list) as *const i32), 1);
1214            assert_eq!(*(list_back(list) as *const i32), 2);
1215
1216            list_pop_back(list);
1217            assert_eq!(list_size(list), 1);
1218            assert_eq!(*(list_back(list) as *const i32), 1);
1219
1220            list_pop_front(list);
1221            assert_eq!(list_size(list), 0);
1222            assert_eq!(list_empty(list), 1);
1223
1224            list_delete(list);
1225        }
1226    }
1227
1228    #[test]
1229    fn test_list_push_front() {
1230        unsafe {
1231            let list = list_create(None, None);
1232            let v1 = &mut 1 as *mut c_int as *mut c_void;
1233            let v2 = &mut 2 as *mut c_int as *mut c_void;
1234
1235            list_push_front(list, v1);
1236            list_push_front(list, v2);
1237            assert_eq!(*(list_front(list) as *const i32), 2);
1238            assert_eq!(*(list_back(list) as *const i32), 1);
1239
1240            list_delete(list);
1241        }
1242    }
1243
1244    #[test]
1245    fn test_list_insert_sorted() {
1246        unsafe {
1247            let list = list_create(None, Some(int_compare));
1248            let v2 = &mut 2 as *mut c_int as *mut c_void;
1249            let v1 = &mut 1 as *mut c_int as *mut c_void;
1250            let v3 = &mut 3 as *mut c_int as *mut c_void;
1251
1252            list_insert(list, v2);
1253            list_insert(list, v1);
1254            list_insert(list, v3);
1255
1256            // Should be 1, 2, 3
1257            assert_eq!(*(list_front(list) as *const i32), 1);
1258            assert_eq!(*(list_back(list) as *const i32), 3);
1259            assert_eq!(list_size(list), 3);
1260
1261            list_delete(list);
1262        }
1263    }
1264
1265    #[test]
1266    fn test_list_remove_first() {
1267        unsafe {
1268            let list = list_create(None, Some(int_compare));
1269            let v1 = &mut 1 as *mut c_int as *mut c_void;
1270            let v2 = &mut 2 as *mut c_int as *mut c_void;
1271
1272            list_push_back(list, v1);
1273            list_push_back(list, v2);
1274
1275            let one: i32 = 1;
1276            let result = list_remove_first(list, &one as *const i32 as *const c_void);
1277            assert_eq!(result, 0);
1278            assert_eq!(list_size(list), 1);
1279            assert_eq!(*(list_front(list) as *const i32), 2);
1280
1281            list_delete(list);
1282        }
1283    }
1284
1285    #[test]
1286    fn test_list_clear() {
1287        unsafe {
1288            let list = list_create(None, None);
1289            list_push_back(list, &mut 1 as *mut c_int as *mut c_void);
1290            list_push_back(list, &mut 2 as *mut c_int as *mut c_void);
1291            assert_eq!(list_size(list), 2);
1292
1293            list_clear(list);
1294            assert_eq!(list_empty(list), 1);
1295            assert_eq!(list_size(list), 0);
1296
1297            list_delete(list);
1298        }
1299    }
1300
1301    #[test]
1302    fn test_list_reverse() {
1303        unsafe {
1304            let list = list_create(None, None);
1305            let v1 = &mut 1 as *mut c_int as *mut c_void;
1306            let v2 = &mut 2 as *mut c_int as *mut c_void;
1307            let v3 = &mut 3 as *mut c_int as *mut c_void;
1308
1309            list_push_back(list, v1);
1310            list_push_back(list, v2);
1311            list_push_back(list, v3);
1312
1313            list_reverse(list);
1314
1315            assert_eq!(*(list_front(list) as *const i32), 3);
1316            assert_eq!(*(list_back(list) as *const i32), 1);
1317
1318            list_delete(list);
1319        }
1320    }
1321
1322    #[test]
1323    fn test_list_null_handling() {
1324        unsafe {
1325            assert_eq!(list_empty(ptr::null_mut()), 1);
1326            assert_eq!(list_size(ptr::null_mut()), -1);
1327            assert!(list_front(ptr::null_mut()).is_null());
1328            assert!(list_back(ptr::null_mut()).is_null());
1329            list_delete(ptr::null_mut()); // Should not crash
1330            list_clear(ptr::null_mut()); // Should not crash
1331            list_pop_front(ptr::null_mut()); // Should not crash
1332            list_pop_back(ptr::null_mut()); // Should not crash
1333        }
1334    }
1335}