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