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