Skip to main content

libxml_rs/xml/list/
mod.rs

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