Skip to main content

libxml_rs/abi/
exports_tree.rs

1//! exports_tree — tree construction/editing/content/property/namespace/free
2//! family closure (11.1-I).
3//!
4//! C ABI exports for the libxml2 tree, entities, valid and writer families:
5//! `xmlAddChildList`, `xmlAddNextSibling`, `xmlAddPrevSibling`,
6//! `xmlReplaceNode`, node/property/namespace creation, content get/set/add,
7//! `xml:lang`/`xml:space`/`xml:base` handling, `xmlSetTreeDoc`,
8//! `xmlGetNsListSafe`, `xmlReconciliateNs`, `xmlFreeNs[List]`,
9//! `xmlFreeProp[List]`, `xmlSetCompressMode`/`xmlGetCompressMode`, the
10//! `xmlDOMWrap*` functions, the entities/valid tables
11//! (`xmlCreateEntitiesTable`, `xmlFreeEntitiesTable`,
12//! `xmlCreateEnumeration`, `xmlFreeEnumeration`,
13//! `xmlFreeDocElementContent`, `xmlFreeAttributeTable`,
14//! `xmlFreeElementTable`, `xmlFreeNotationTable`),
15//! `xmlNewEntityInputStream`
16//! and `xmlNewTextWriterPushParser`.
17//!
18//! # UPSTREAM-PARITY
19//!
20//! All semantics follow upstream libxml2 (`archaeology/libxml2-git/tree.c`,
21//! `valid.c`, `entities.c`, `parserInternals.c`, `xmlwriter.c`).
22//!
23//! The internal helpers from `src/xml/tree/mod.rs` (`new_node`, `new_text`,
24//! `copy_node`, `add_child`, `get_prop`, `set_prop`, `unset_prop`,
25//! `node_get_content`, `get_ns_list`, `search_ns`, `search_ns_by_href`,
26//! `unlink_node`, `free_node`, `free_node_list`, `new_ns`, `get_doc_entity`,
27//! ...) are reused wherever they match upstream semantics; functions whose
28//! upstream behavior is not covered by those helpers are ported locally.
29
30#![allow(
31    missing_docs,
32    non_snake_case,
33    non_camel_case_types,
34    non_upper_case_globals
35)]
36#![allow(clippy::missing_safety_doc)]
37#![allow(clippy::not_unsafe_ptr_arg_deref)]
38#![allow(clippy::comparison_chain)]
39#![allow(clippy::needless_range_loop)]
40#![allow(clippy::manual_swap)]
41// Ported goto-heavy C (tree.c) writes state variables before re-reading
42// them; the dead first assignments mirror upstream exactly.
43#![allow(unused_assignments)]
44#![allow(missing_debug_implementations)]
45
46use core::ffi::c_void;
47use core::ptr;
48use std::os::raw::{c_char, c_int};
49use std::sync::atomic::{AtomicI32, Ordering};
50
51use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl, xmlMallocZero, xmlReallocImpl};
52use crate::abi::constants::*;
53use crate::abi::structs::*;
54use crate::abi::types::xmlElementType::*;
55use crate::abi::types::*;
56use crate::xml::entities;
57use crate::xml::hash;
58use crate::xml::tree;
59
60// ═══════════════════════════════════════════════════════════════════════════════
61// String helpers
62// ═══════════════════════════════════════════════════════════════════════════════
63
64/// Duplicate a null-terminated xmlChar string (NULL-safe).
65unsafe fn dup_str(s: *const xmlChar) -> *mut xmlChar {
66    unsafe { crate::abi::exports_xml2::xmlStrdup(s) }
67}
68
69/// Length of a null-terminated xmlChar string (NULL → 0).
70unsafe fn str_len(s: *const xmlChar) -> c_int {
71    unsafe { crate::abi::exports_xml2::xmlStrlen(s) }
72}
73
74/// Copy the (NUL-terminated) prefix of `name` up to `max` bytes into an
75/// owned, NUL-terminated buffer (upstream `snprintf(..., "%.*s", max, name)`).
76unsafe fn str_prefix(name: *const xmlChar, max: usize) -> Vec<u8> {
77    if name.is_null() {
78        return vec![0];
79    }
80    let mut v = Vec::new();
81    let mut i = 0usize;
82    while i < max {
83        let c = unsafe { *name.add(i) };
84        if c == 0 {
85            break;
86        }
87        v.push(c);
88        i += 1;
89    }
90    v.push(0);
91    v
92}
93
94/// Pointer-or-value string equality (upstream `(a == b) || xmlStrEqual(a, b)`).
95unsafe fn str_eq_or_ptr(a: *const xmlChar, b: *const xmlChar) -> bool {
96    if a == b {
97        return true;
98    }
99    unsafe { crate::abi::exports_xml2::xmlStrEqual(a, b) != 0 }
100}
101
102/// Case-insensitive equality against a byte literal (e.g. `"html"`).
103unsafe fn strcasecmp_eq(a: *const xmlChar, lit: &[u8]) -> bool {
104    if a.is_null() {
105        return false;
106    }
107    let mut i = 0usize;
108    loop {
109        let ca = unsafe { *a.add(i) };
110        if i >= lit.len() {
111            return ca == 0;
112        }
113        let cb = lit[i];
114        if ca == 0 || ca.to_ascii_lowercase() != cb.to_ascii_lowercase() {
115            return false;
116        }
117        i += 1;
118    }
119}
120
121/// `IS_STR_XML` from upstream tree.c — prefix is the `xml` prefix.
122unsafe fn is_str_xml(s: *const xmlChar) -> bool {
123    unsafe { crate::abi::exports_xml2::xmlStrEqual(s, b"xml\0".as_ptr() as *const xmlChar) != 0 }
124}
125
126/// `xmlStringText` — the canonical name of text nodes.
127const TEXT_NAME: &[u8] = b"text\0";
128
129/// UTF-8 encode `val` into `out` (upstream `xmlCopyCharMultiByte`).
130unsafe fn copy_char_multibyte(mut out: *mut xmlChar, val: c_int) -> c_int {
131    if out.is_null() || val < 0 {
132        return 0;
133    }
134    if val >= 0x80 {
135        let saved = out;
136        let bits: c_int;
137        if val < 0x800 {
138            *out = ((val >> 6) | 0xC0) as u8;
139            out = out.add(1);
140            bits = 0;
141        } else if val < 0x10000 {
142            *out = ((val >> 12) | 0xE0) as u8;
143            out = out.add(1);
144            bits = 6;
145        } else if val < 0x110000 {
146            *out = ((val >> 18) | 0xF0) as u8;
147            out = out.add(1);
148            bits = 12;
149        } else {
150            return 0;
151        }
152        let mut b = bits;
153        loop {
154            *out = (((val >> b) & 0x3F) | 0x80) as u8;
155            out = out.add(1);
156            if b == 0 {
157                break;
158            }
159            b -= 6;
160        }
161        return unsafe { out.offset_from(saved) } as c_int;
162    }
163    *out = val as u8;
164    1
165}
166
167/// Copy a byte vector into a NUL-terminated xmlMalloc'd string.
168unsafe fn vec_to_c_string(v: &[u8]) -> *mut xmlChar {
169    let buf = unsafe { xmlMallocImpl(v.len() + 1) } as *mut xmlChar;
170    if buf.is_null() {
171        return ptr::null_mut();
172    }
173    unsafe {
174        ptr::copy_nonoverlapping(v.as_ptr(), buf, v.len());
175        *buf.add(v.len()) = 0;
176    }
177    buf
178}
179
180/// Upstream `xmlTextSetContent`.
181unsafe fn text_set_content(text: *mut _xmlNode, content: *mut xmlChar) {
182    if !(*text).content.is_null() {
183        let inline_addr = core::ptr::addr_of_mut!((*text).properties) as *const c_void;
184        if (*text).content as *const c_void != inline_addr {
185            unsafe { xmlFreeImpl((*text).content as *mut c_void) };
186        }
187    }
188    (*text).content = content;
189    (*text).properties = ptr::null_mut();
190}
191
192/// Upstream `xmlTextAddContent` — append `len` bytes (or the full string if
193/// `len < 0`) to the content of a text node.
194unsafe fn text_add_content(text: *mut _xmlNode, content: *const xmlChar, len: c_int) -> c_int {
195    if content.is_null() {
196        return 0;
197    }
198    let l = if len < 0 {
199        unsafe { str_len(content) }
200    } else {
201        len
202    };
203    let merged = unsafe { crate::abi::exports_xml2::xmlStrncatNew((*text).content, content, l) };
204    if merged.is_null() {
205        return -1;
206    }
207    unsafe { text_set_content(text, merged) };
208    0
209}
210
211/// Upstream `xmlNewDocText`.
212unsafe fn new_doc_text(doc: *mut _xmlDoc, content: *const xmlChar) -> *mut _xmlNode {
213    let n = unsafe { tree::new_text(content) };
214    if !n.is_null() {
215        (*n).doc = doc;
216    }
217    n
218}
219
220/// Upstream `xmlNewDocTextLen`.
221unsafe fn new_doc_text_len(
222    doc: *mut _xmlDoc,
223    content: *const xmlChar,
224    len: c_int,
225) -> *mut _xmlNode {
226    let node = unsafe { xmlMallocZero(size_of::<_xmlNode>()) } as *mut _xmlNode;
227    if node.is_null() {
228        return ptr::null_mut();
229    }
230    (*node).type_ = XML_TEXT_NODE as c_int;
231    (*node).name = unsafe { dup_str(TEXT_NAME.as_ptr() as *const xmlChar) };
232    (*node).doc = doc;
233    if !content.is_null() {
234        (*node).content = unsafe { crate::abi::exports_xml2::xmlStrndup(content, len) };
235        if (*node).content.is_null() {
236            unsafe { tree::free_node(node) };
237            return ptr::null_mut();
238        }
239    }
240    node
241}
242
243/// Upstream static `xmlNewEntityRef` — creates an ENTITY_REF node taking
244/// ownership of `name`.
245unsafe fn new_entity_ref(doc: *mut _xmlDoc, name: *mut xmlChar) -> *mut _xmlNode {
246    let cur = unsafe { xmlMallocZero(size_of::<_xmlNode>()) } as *mut _xmlNode;
247    if cur.is_null() {
248        unsafe { xmlFreeImpl(name as *mut c_void) };
249        return ptr::null_mut();
250    }
251    (*cur).type_ = XML_ENTITY_REF_NODE as c_int;
252    (*cur).doc = doc;
253    (*cur).name = name;
254    cur
255}
256
257/// Look up the content of a predefined entity (lt/gt/amp/quot/apos), or NULL.
258unsafe fn predefined_entity_content(name: *const xmlChar) -> *const xmlChar {
259    if name.is_null() {
260        return ptr::null();
261    }
262    let c = unsafe { *name };
263    let n1 = unsafe { *name.add(1) };
264    let n2 = unsafe { *name.add(2) };
265    match c {
266        b'l' if n1 == b't' && n2 == 0 => b"<\0".as_ptr() as *const xmlChar,
267        b'g' if n1 == b't' && n2 == 0 => b">\0".as_ptr() as *const xmlChar,
268        b'a' if n1 == b'm' && n2 == b'p' && unsafe { *name.add(3) } == 0 => {
269            b"&\0".as_ptr() as *const xmlChar
270        }
271        b'q' if n1 == b'u'
272            && n2 == b'o'
273            && unsafe { *name.add(3) } == b't'
274            && unsafe { *name.add(4) } == 0 =>
275        {
276            b"\"\0".as_ptr() as *const xmlChar
277        }
278        b'a' if n1 == b'p'
279            && n2 == b'o'
280            && unsafe { *name.add(3) } == b's'
281            && unsafe { *name.add(4) } == 0 =>
282        {
283            b"'\0".as_ptr() as *const xmlChar
284        }
285        _ => ptr::null(),
286    }
287}
288
289/// Append a node to the tail of a list, updating `head`/`last` and the
290/// sibling links (upstream inline list-append used by xmlNodeParseAttValue).
291unsafe fn list_append(head: *mut *mut _xmlNode, last: *mut *mut _xmlNode, node: *mut _xmlNode) {
292    if (*head).is_null() {
293        *head = node;
294    } else {
295        (**last).next = node;
296        (*node).prev = *last;
297    }
298    *last = node;
299}
300
301/// Upstream `xmlNodeParseAttValue` — parse `value` (up to `len` bytes, or the
302/// whole string when `len == usize::MAX`) as an XML attribute value into a
303/// list of text/entity-ref nodes, freeing and replacing `attr`'s children.
304unsafe fn node_parse_att_value(
305    doc: *mut _xmlDoc,
306    attr: *mut _xmlNode,
307    value: *const xmlChar,
308    len: usize,
309) -> c_int {
310    let mut head: *mut _xmlNode = ptr::null_mut();
311    let mut last: *mut _xmlNode = ptr::null_mut();
312    let mut remaining = len;
313
314    if !value.is_null() && *value != 0 {
315        let mut cur = value;
316        let mut q = cur;
317        let mut buf: Vec<u8> = Vec::new();
318        let mut failed = false;
319        while remaining > 0 && *cur != 0 {
320            if *cur == b'&' {
321                let mut charval: c_int = 0;
322                if cur != q {
323                    buf.extend_from_slice(core::slice::from_raw_parts(
324                        q,
325                        cur.offset_from(q) as usize,
326                    ));
327                    q = cur;
328                }
329                if remaining > 2 && *cur.add(1) == b'#' && *cur.add(2) == b'x' {
330                    cur = cur.add(3);
331                    remaining -= 3;
332                    while remaining > 0 {
333                        let tmp = *cur;
334                        if tmp == b';' {
335                            break;
336                        }
337                        charval = match tmp {
338                            b'0'..=b'9' => charval * 16 + (tmp - b'0') as c_int,
339                            b'a'..=b'f' => charval * 16 + (tmp - b'a') as c_int + 10,
340                            b'A'..=b'F' => charval * 16 + (tmp - b'A') as c_int + 10,
341                            _ => {
342                                charval = 0;
343                                break;
344                            }
345                        };
346                        if charval > 0x110000 {
347                            charval = 0x110000;
348                        }
349                        cur = cur.add(1);
350                        remaining -= 1;
351                    }
352                    if *cur == b';' {
353                        cur = cur.add(1);
354                        remaining -= 1;
355                    }
356                    q = cur;
357                } else if remaining > 1 && *cur.add(1) == b'#' {
358                    cur = cur.add(2);
359                    remaining -= 2;
360                    while remaining > 0 {
361                        let tmp = *cur;
362                        if tmp == b';' {
363                            break;
364                        }
365                        charval = match tmp {
366                            b'0'..=b'9' => charval * 10 + (tmp - b'0') as c_int,
367                            _ => {
368                                charval = 0;
369                                break;
370                            }
371                        };
372                        if charval > 0x110000 {
373                            charval = 0x110000;
374                        }
375                        cur = cur.add(1);
376                        remaining -= 1;
377                    }
378                    if *cur == b';' {
379                        cur = cur.add(1);
380                        remaining -= 1;
381                    }
382                    q = cur;
383                } else {
384                    cur = cur.add(1);
385                    remaining -= 1;
386                    q = cur;
387                    while remaining > 0 && *cur != 0 && *cur != b';' {
388                        cur = cur.add(1);
389                        remaining -= 1;
390                    }
391                    if remaining <= 0 || *cur == 0 {
392                        failed = true;
393                        break;
394                    }
395                    if cur != q {
396                        let mut val = unsafe {
397                            crate::abi::exports_xml2::xmlStrndup(q, (cur.offset_from(q)) as c_int)
398                        };
399                        if val.is_null() {
400                            failed = true;
401                            break;
402                        }
403                        let ent = unsafe { tree::get_doc_entity(doc, val) };
404                        let is_predef = unsafe { predefined_entity_content(val) };
405                        if !is_predef.is_null() {
406                            // Predefined entities don't generate nodes.
407                            let p = is_predef;
408                            let mut i = 0usize;
409                            while unsafe { *p.add(i) } != 0 {
410                                buf.push(unsafe { *p.add(i) });
411                                i += 1;
412                            }
413                        } else {
414                            // Flush the buffer into a text node.
415                            if !buf.is_empty() {
416                                let node = unsafe { new_doc_text(doc, ptr::null()) };
417                                if node.is_null() {
418                                    unsafe { xmlFreeImpl(val as *mut c_void) };
419                                    failed = true;
420                                    break;
421                                }
422                                (*node).content = unsafe { vec_to_c_string(&buf) };
423                                if (*node).content.is_null() {
424                                    unsafe { tree::free_node(node) };
425                                    unsafe { xmlFreeImpl(val as *mut c_void) };
426                                    failed = true;
427                                    break;
428                                }
429                                buf.clear();
430                                (*node).parent = attr;
431                                unsafe { list_append(&mut head, &mut last, node) };
432                            }
433                            // Create an entity-reference node.
434                            let node = unsafe { new_entity_ref(doc, val) };
435                            val = ptr::null_mut();
436                            if node.is_null() {
437                                failed = true;
438                                break;
439                            }
440                            (*node).parent = attr;
441                            (*node).last = ent as *mut _xmlNode;
442                            if !ent.is_null() {
443                                (*node).children = ent as *mut _xmlNode;
444                                (*node).content = (*ent).content;
445                            }
446                            unsafe { list_append(&mut head, &mut last, node) };
447                        }
448                        if !val.is_null() {
449                            unsafe { xmlFreeImpl(val as *mut c_void) };
450                        }
451                    }
452                    cur = cur.add(1);
453                    remaining -= 1;
454                    q = cur;
455                }
456                if charval != 0 && !failed {
457                    if charval >= 0x110000 {
458                        charval = 0xFFFD; // replacement character
459                    }
460                    let mut buffer = [0u8; 8];
461                    let l = unsafe { copy_char_multibyte(buffer.as_mut_ptr(), charval) };
462                    if l > 0 {
463                        buf.extend_from_slice(&buffer[..l as usize]);
464                    }
465                }
466                if failed {
467                    break;
468                }
469            } else {
470                cur = cur.add(1);
471                remaining -= 1;
472            }
473        }
474        if !failed && cur != q {
475            buf.extend_from_slice(core::slice::from_raw_parts(q, cur.offset_from(q) as usize));
476        }
477        if !failed {
478            if !buf.is_empty() {
479                let node = unsafe { new_doc_text(doc, ptr::null()) };
480                if node.is_null() {
481                    failed = true;
482                } else {
483                    (*node).content = unsafe { vec_to_c_string(&buf) };
484                    if (*node).content.is_null() {
485                        unsafe { tree::free_node(node) };
486                        failed = true;
487                    } else {
488                        (*node).parent = attr;
489                        unsafe { list_append(&mut head, &mut last, node) };
490                    }
491                }
492            } else if head.is_null() {
493                let node = unsafe { new_doc_text(doc, b"\0".as_ptr() as *const xmlChar) };
494                if node.is_null() {
495                    failed = true;
496                } else {
497                    (*node).parent = attr;
498                    head = node;
499                    last = node;
500                }
501            }
502        }
503        if failed {
504            if !head.is_null() {
505                unsafe { tree::free_node_list(head) };
506            }
507            return -1;
508        }
509    }
510
511    if !attr.is_null() {
512        if !(*attr).children.is_null() {
513            unsafe { tree::free_node_list((*attr).children) };
514        }
515        (*attr).children = head;
516        (*attr).last = last;
517    }
518    0
519}
520
521// ═══════════════════════════════════════════════════════════════════════════════
522// Namespace helpers (xmlTreeEnsureXMLDecl, xmlSearchNsByHrefSafe subset, …)
523// ═══════════════════════════════════════════════════════════════════════════════
524
525/// Upstream `xmlTreeEnsureXMLDecl` — ensure `doc->oldNs` holds the XML ns.
526unsafe fn ensure_xml_decl(doc: *mut _xmlDoc) -> *mut _xmlNs {
527    if doc.is_null() {
528        return ptr::null_mut();
529    }
530    if !(*doc).oldNs.is_null() {
531        return (*doc).oldNs;
532    }
533    let ns = unsafe {
534        tree::new_ns(
535            ptr::null_mut(),
536            XML_XML_NAMESPACE.as_ptr() as *const xmlChar,
537            b"xml\0".as_ptr() as *const xmlChar,
538        )
539    };
540    if ns.is_null() {
541        return ptr::null_mut();
542    }
543    (*doc).oldNs = ns;
544    ns
545}
546
547/// Upstream `xmlTreeNSListLookupByPrefix`.
548unsafe fn ns_list_lookup_by_prefix(ns_list: *mut _xmlNs, prefix: *const xmlChar) -> *mut _xmlNs {
549    if ns_list.is_null() {
550        return ptr::null_mut();
551    }
552    let mut ns = ns_list;
553    loop {
554        if unsafe { str_eq_or_ptr(prefix, (*ns).prefix) } {
555            return ns;
556        }
557        if (*ns).next.is_null() {
558            break;
559        }
560        ns = (*ns).next;
561    }
562    ptr::null_mut()
563}
564
565// ═══════════════════════════════════════════════════════════════════════════════
566// Attribute / namespace property helpers
567// ═══════════════════════════════════════════════════════════════════════════════
568
569/// Upstream `xmlFreeProp`.
570unsafe fn free_prop_impl(cur: *mut _xmlAttr) {
571    if cur.is_null() {
572        return;
573    }
574    if !(*cur).children.is_null() {
575        unsafe { tree::free_node_list((*cur).children) };
576    }
577    if !(*cur).name.is_null() {
578        unsafe { xmlFreeImpl((*cur).name as *mut c_void) };
579    }
580    unsafe { xmlFreeImpl(cur as *mut c_void) };
581}
582
583/// Upstream `xmlFreeNs`.
584unsafe fn free_ns_impl(cur: *mut _xmlNs) {
585    if cur.is_null() {
586        return;
587    }
588    if !(*cur).href.is_null() {
589        unsafe { xmlFreeImpl((*cur).href as *mut c_void) };
590    }
591    if !(*cur).prefix.is_null() {
592        unsafe { xmlFreeImpl((*cur).prefix as *mut c_void) };
593    }
594    unsafe { xmlFreeImpl(cur as *mut c_void) };
595}
596
597/// Upstream `xmlGetPropNodeInternal` (ns-name variant).
598unsafe fn find_prop_node_internal(
599    node: *mut _xmlNode,
600    name: *const xmlChar,
601    ns_href: *const xmlChar,
602    _use_dtd: c_int,
603) -> *mut _xmlAttr {
604    if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int || name.is_null() {
605        return ptr::null_mut();
606    }
607    if !(*node).properties.is_null() {
608        let mut prop = (*node).properties;
609        if ns_href.is_null() {
610            loop {
611                if (*prop).ns.is_null()
612                    && !(*prop).name.is_null()
613                    && unsafe { crate::abi::exports_xml2::xmlStrEqual((*prop).name, name) != 0 }
614                {
615                    return prop;
616                }
617                if (*prop).next.is_null() {
618                    break;
619                }
620                prop = (*prop).next;
621            }
622        } else {
623            loop {
624                if !(*prop).ns.is_null()
625                    && !(*prop).name.is_null()
626                    && unsafe { crate::abi::exports_xml2::xmlStrEqual((*prop).name, name) != 0 }
627                    && !(*(*prop).ns).href.is_null()
628                    && unsafe {
629                        crate::abi::exports_xml2::xmlStrEqual((*(*prop).ns).href, ns_href) != 0
630                    }
631                {
632                    return prop;
633                }
634                if (*prop).next.is_null() {
635                    break;
636                }
637                prop = (*prop).next;
638            }
639        }
640    }
641    ptr::null_mut()
642}
643
644/// Upstream `xmlGetPropNodeValueInternal` — dup of the attribute's text value.
645unsafe fn get_prop_node_value_internal(prop: *mut _xmlAttr) -> *mut xmlChar {
646    if (*prop).children.is_null() {
647        return unsafe { dup_str(b"\0".as_ptr() as *const xmlChar) };
648    }
649    let text = (*prop).children;
650    if (*text).type_ == XML_TEXT_NODE as c_int {
651        if (*text).content.is_null() {
652            return unsafe { dup_str(b"\0".as_ptr() as *const xmlChar) };
653        }
654        return unsafe { dup_str((*text).content) };
655    }
656    // Entity-ref children: fall back to the raw children content walk.
657    unsafe { tree::node_get_content(prop as *mut _xmlNode) }
658}
659
660/// Upstream `xmlNodeGetAttrValue` — value of the attribute with `name` in
661/// namespace `ns_uri` (NULL = no namespace).
662unsafe fn get_attr_value(
663    node: *mut _xmlNode,
664    name: *const xmlChar,
665    ns_uri: *const xmlChar,
666) -> *mut xmlChar {
667    let prop = unsafe { find_prop_node_internal(node, name, ns_uri, 0) };
668    if prop.is_null() {
669        return ptr::null_mut();
670    }
671    unsafe { get_prop_node_value_internal(prop) }
672}
673
674/// Upstream `xmlSetNsProp` — set (or create) a namespaced attribute.
675unsafe fn set_ns_prop_impl(
676    node: *mut _xmlNode,
677    ns: *mut _xmlNs,
678    name: *const xmlChar,
679    value: *const xmlChar,
680) -> *mut _xmlAttr {
681    if node.is_null() || name.is_null() {
682        return ptr::null_mut();
683    }
684    let ns_href = if ns.is_null() {
685        ptr::null()
686    } else {
687        (*ns).href
688    };
689    let existing = unsafe { find_prop_node_internal(node, name, ns_href, 0) };
690    if !existing.is_null() {
691        if !(*existing).children.is_null() {
692            unsafe { tree::free_node_list((*existing).children) };
693            (*existing).children = ptr::null_mut();
694            (*existing).last = ptr::null_mut();
695        }
696        if !value.is_null() {
697            let text = unsafe { new_doc_text((*node).doc, value) };
698            if !text.is_null() {
699                (*existing).children = text;
700                (*existing).last = text;
701                (*text).parent = existing as *mut _xmlNode;
702                (*text).doc = (*node).doc;
703            }
704        }
705        return existing;
706    }
707    unsafe { new_prop_internal(node, ns, name, value, 0) }
708}
709
710/// Upstream `xmlNewPropInternal` (raw value path).
711unsafe fn new_prop_internal(
712    node: *mut _xmlNode,
713    ns: *mut _xmlNs,
714    name: *const xmlChar,
715    value: *const xmlChar,
716    eatname: c_int,
717) -> *mut _xmlAttr {
718    let doc: *mut _xmlDoc;
719    if !node.is_null() && (*node).type_ != XML_ELEMENT_NODE as c_int {
720        if eatname == 1 {
721            unsafe { xmlFreeImpl(name as *mut c_void) };
722        }
723        return ptr::null_mut();
724    }
725    let cur = unsafe { xmlMallocZero(size_of::<_xmlAttr>()) } as *mut _xmlAttr;
726    if cur.is_null() {
727        if eatname == 1 {
728            unsafe { xmlFreeImpl(name as *mut c_void) };
729        }
730        return ptr::null_mut();
731    }
732    (*cur).type_ = XML_ATTRIBUTE_NODE as c_int;
733    (*cur).parent = node;
734    if !node.is_null() {
735        doc = (*node).doc;
736        (*cur).doc = doc;
737    } else {
738        doc = ptr::null_mut();
739    }
740    (*cur).ns = ns;
741
742    if eatname == 0 {
743        (*cur).name = unsafe { dup_str(name) };
744        if (*cur).name.is_null() {
745            unsafe { free_prop_impl(cur) };
746            return ptr::null_mut();
747        }
748    } else {
749        (*cur).name = name;
750    }
751
752    if !value.is_null() {
753        let text = unsafe { new_doc_text(doc, value) };
754        if text.is_null() {
755            unsafe { free_prop_impl(cur) };
756            return ptr::null_mut();
757        }
758        (*cur).children = text;
759        (*cur).last = ptr::null_mut();
760        let mut tmp = text;
761        while !tmp.is_null() {
762            (*tmp).parent = cur as *mut _xmlNode;
763            if (*tmp).next.is_null() {
764                (*cur).last = tmp;
765            }
766            tmp = (*tmp).next;
767        }
768    }
769
770    if !node.is_null() {
771        if (*node).properties.is_null() {
772            (*node).properties = cur;
773        } else {
774            let mut prev = (*node).properties;
775            while !(*prev).next.is_null() {
776                prev = (*prev).next;
777            }
778            (*prev).next = cur;
779            (*cur).prev = prev;
780        }
781    }
782    cur
783}
784
785/// Upstream `xmlRemoveProp`.
786unsafe fn remove_prop_impl(cur: *mut _xmlAttr) -> c_int {
787    if cur.is_null() {
788        return -1;
789    }
790    if (*cur).parent.is_null() {
791        return -1;
792    }
793    let mut tmp = (*(*cur).parent).properties;
794    if tmp == cur {
795        (*(*cur).parent).properties = (*cur).next;
796        if !(*cur).next.is_null() {
797            (*(*cur).next).prev = ptr::null_mut();
798        }
799        unsafe { free_prop_impl(cur) };
800        return 0;
801    }
802    while !tmp.is_null() {
803        if (*tmp).next == cur {
804            (*tmp).next = (*cur).next;
805            if !(*tmp).next.is_null() {
806                (*(*tmp).next).prev = tmp;
807            }
808            unsafe { free_prop_impl(cur) };
809            return 0;
810        }
811        tmp = (*tmp).next;
812    }
813    -1
814}
815
816// ═══════════════════════════════════════════════════════════════════════════════
817// xmlNodeSetDoc / xmlSetListDoc / xmlSetTreeDoc
818// ═══════════════════════════════════════════════════════════════════════════════
819
820/// Upstream `xmlNodeSetDoc` (without dictionary and ID-table handling).
821unsafe fn node_set_doc_impl(node: *mut _xmlNode, doc: *mut _xmlDoc) -> c_int {
822    let ret = 0;
823    match (*node).type_ as u32 {
824        t if t == XML_ENTITY_REF_NODE as u32 => {
825            (*node).children = ptr::null_mut();
826            (*node).last = ptr::null_mut();
827            (*node).content = ptr::null_mut();
828            if !doc.is_null() && (!(*doc).intSubset.is_null() || !(*doc).extSubset.is_null()) {
829                let ent = unsafe { tree::get_doc_entity(doc, (*node).name) };
830                if !ent.is_null() {
831                    (*node).children = ent as *mut _xmlNode;
832                    (*node).last = ent as *mut _xmlNode;
833                    (*node).content = (*ent).content;
834                }
835            }
836        }
837        t if t == XML_DTD_NODE as u32 => {
838            if !(*node).doc.is_null() {
839                if (*(*node).doc).intSubset == node as *mut _xmlDtd {
840                    (*(*node).doc).intSubset = ptr::null_mut();
841                }
842                if (*(*node).doc).extSubset == node as *mut _xmlDtd {
843                    (*(*node).doc).extSubset = ptr::null_mut();
844                }
845            }
846        }
847        _ => {}
848    }
849    (*node).doc = doc;
850    ret
851}
852
853/// Upstream `xmlSetListDoc`.
854unsafe fn set_list_doc_impl(list: *mut _xmlNode, doc: *mut _xmlDoc) -> c_int {
855    let mut ret = 0;
856    if list.is_null() || (*list).type_ == XML_NAMESPACE_DECL as c_int {
857        return 0;
858    }
859    let mut cur = list;
860    while !cur.is_null() {
861        if (*cur).doc != doc {
862            if unsafe { set_tree_doc_impl(cur, doc) } < 0 {
863                ret = -1;
864            }
865        }
866        cur = (*cur).next;
867    }
868    ret
869}
870
871/// Upstream `xmlSetTreeDoc`.
872unsafe fn set_tree_doc_impl(tree: *mut _xmlNode, doc: *mut _xmlDoc) -> c_int {
873    let mut ret = 0;
874    if tree.is_null() || (*tree).type_ == XML_NAMESPACE_DECL as c_int {
875        return 0;
876    }
877    if (*tree).doc == doc {
878        return 0;
879    }
880    if (*tree).type_ == XML_ELEMENT_NODE as c_int {
881        let mut prop = (*tree).properties;
882        while !prop.is_null() {
883            if !(*prop).children.is_null() {
884                if unsafe { set_list_doc_impl((*prop).children, doc) } < 0 {
885                    ret = -1;
886                }
887            }
888            if unsafe { node_set_doc_impl(prop as *mut _xmlNode, doc) } < 0 {
889                ret = -1;
890            }
891            prop = (*prop).next;
892        }
893    }
894    if !(*tree).children.is_null() && (*tree).type_ != XML_ENTITY_REF_NODE as c_int {
895        if unsafe { set_list_doc_impl((*tree).children, doc) } < 0 {
896            ret = -1;
897        }
898    }
899    if unsafe { node_set_doc_impl(tree, doc) } < 0 {
900        ret = -1;
901    }
902    ret
903}
904
905/// Upstream `xmlInsertProp` — insert an attribute at (prev, next), destroying
906/// a same-named duplicate.
907unsafe fn insert_prop(
908    doc: *mut _xmlDoc,
909    cur: *mut _xmlNode,
910    parent: *mut _xmlNode,
911    prev: *mut _xmlNode,
912    next: *mut _xmlNode,
913) -> *mut _xmlNode {
914    if (!prev.is_null() && (*prev).type_ != XML_ATTRIBUTE_NODE as c_int)
915        || (!next.is_null() && (*next).type_ != XML_ATTRIBUTE_NODE as c_int)
916    {
917        return ptr::null_mut();
918    }
919    let ns_href = if (*cur).ns.is_null() {
920        ptr::null()
921    } else {
922        (*(*cur).ns).href
923    };
924    let attr = unsafe { find_prop_node_internal(parent, (*cur).name, ns_href, 0) };
925
926    unsafe { tree::unlink_node(cur) };
927
928    if (*cur).doc != doc {
929        if unsafe { set_tree_doc_impl(cur, doc) } < 0 {
930            return ptr::null_mut();
931        }
932    }
933    (*cur).parent = parent;
934    (*cur).prev = prev;
935    (*cur).next = next;
936
937    if prev.is_null() {
938        if !parent.is_null() {
939            (*parent).properties = cur as *mut _xmlAttr;
940        }
941    } else {
942        (*prev).next = cur;
943    }
944    if !next.is_null() {
945        (*next).prev = cur;
946    }
947
948    if !attr.is_null() && attr != cur as *mut _xmlAttr {
949        // Different instance: destroy it (attributes must be unique).
950        unsafe { remove_prop_impl(attr) };
951    }
952    cur
953}
954
955/// Upstream `xmlInsertNode` — unlink `cur` and insert it between `prev` and
956/// `next` (coalescing adjacent text nodes when `coalesce != 0`).
957unsafe fn insert_node(
958    doc: *mut _xmlDoc,
959    cur: *mut _xmlNode,
960    parent: *mut _xmlNode,
961    prev: *mut _xmlNode,
962    next: *mut _xmlNode,
963    coalesce: c_int,
964) -> *mut _xmlNode {
965    if (*cur).type_ == XML_ATTRIBUTE_NODE as c_int {
966        return unsafe { insert_prop(doc, cur, parent, prev, next) };
967    }
968
969    if coalesce != 0 && (*cur).type_ == XML_TEXT_NODE as c_int {
970        if !prev.is_null()
971            && (*prev).type_ == XML_TEXT_NODE as c_int
972            && unsafe { str_eq_or_ptr((*prev).name, (*cur).name) }
973        {
974            if unsafe { text_add_content(prev, (*cur).content, -1) } < 0 {
975                return ptr::null_mut();
976            }
977            unsafe { tree::unlink_node(cur) };
978            unsafe { tree::free_node(cur) };
979            return prev;
980        }
981        if !next.is_null()
982            && (*next).type_ == XML_TEXT_NODE as c_int
983            && unsafe { str_eq_or_ptr((*next).name, (*cur).name) }
984        {
985            if !(*cur).content.is_null() {
986                let l = unsafe { str_len((*next).content) };
987                let merged = unsafe {
988                    crate::abi::exports_xml2::xmlStrncatNew((*cur).content, (*next).content, l)
989                };
990                if merged.is_null() {
991                    return ptr::null_mut();
992                }
993                unsafe { text_set_content(next, merged) };
994            }
995            unsafe { tree::unlink_node(cur) };
996            unsafe { tree::free_node(cur) };
997            return next;
998        }
999    }
1000
1001    let old_parent = (*cur).parent;
1002    if !old_parent.is_null() {
1003        if (*old_parent).children == cur {
1004            (*old_parent).children = (*cur).next;
1005        }
1006        if (*old_parent).last == cur {
1007            (*old_parent).last = (*cur).prev;
1008        }
1009    }
1010    if !(*cur).next.is_null() {
1011        (*(*cur).next).prev = (*cur).prev;
1012    }
1013    if !(*cur).prev.is_null() {
1014        (*(*cur).prev).next = (*cur).next;
1015    }
1016
1017    if (*cur).doc != doc {
1018        if unsafe { set_tree_doc_impl(cur, doc) } < 0 {
1019            (*cur).parent = ptr::null_mut();
1020            (*cur).prev = ptr::null_mut();
1021            (*cur).next = ptr::null_mut();
1022            return ptr::null_mut();
1023        }
1024    }
1025
1026    (*cur).parent = parent;
1027    (*cur).prev = prev;
1028    (*cur).next = next;
1029
1030    if prev.is_null() {
1031        if !parent.is_null() {
1032            (*parent).children = cur;
1033        }
1034    } else {
1035        (*prev).next = cur;
1036    }
1037    if next.is_null() {
1038        if !parent.is_null() {
1039            (*parent).last = cur;
1040        }
1041    } else {
1042        (*next).prev = cur;
1043    }
1044    cur
1045}
1046
1047/// Upstream `xmlAddChild` (with text merging), used by xmlNodeAddContentLen.
1048unsafe fn add_child_coalesce(parent: *mut _xmlNode, cur: *mut _xmlNode) -> *mut _xmlNode {
1049    if parent.is_null()
1050        || (*parent).type_ == XML_NAMESPACE_DECL as c_int
1051        || cur.is_null()
1052        || (*cur).type_ == XML_NAMESPACE_DECL as c_int
1053        || parent == cur
1054    {
1055        return ptr::null_mut();
1056    }
1057    // Undocumented quirk: adding to a text node appends content.
1058    if (*parent).type_ == XML_TEXT_NODE as c_int {
1059        if unsafe { text_add_content(parent, (*cur).content, -1) } < 0 {
1060            return ptr::null_mut();
1061        }
1062        unsafe { tree::unlink_node(cur) };
1063        unsafe { tree::free_node(cur) };
1064        return parent;
1065    }
1066    let prev: *mut _xmlNode = if (*cur).type_ == XML_ATTRIBUTE_NODE as c_int {
1067        let mut p = (*parent).properties;
1068        if !p.is_null() {
1069            while !(*p).next.is_null() {
1070                p = (*p).next;
1071            }
1072        }
1073        p as *mut _xmlNode
1074    } else {
1075        (*parent).last
1076    };
1077    if cur == prev {
1078        return cur;
1079    }
1080    unsafe { insert_node((*parent).doc, cur, parent, prev, ptr::null_mut(), 1) }
1081}
1082
1083/// Upstream `xmlNewElem` (static helper of xmlNewDocNode).
1084unsafe fn new_elem(
1085    doc: *mut _xmlDoc,
1086    ns: *mut _xmlNs,
1087    name: *const xmlChar,
1088    content: *const xmlChar,
1089) -> *mut _xmlNode {
1090    let cur = unsafe { xmlMallocZero(size_of::<_xmlNode>()) } as *mut _xmlNode;
1091    if cur.is_null() {
1092        return ptr::null_mut();
1093    }
1094    (*cur).type_ = XML_ELEMENT_NODE as c_int;
1095    (*cur).doc = doc;
1096    (*cur).name = name;
1097    (*cur).ns = ns;
1098    if !content.is_null() {
1099        if unsafe { node_parse_att_value(doc, cur, content, usize::MAX) } < 0 {
1100            // Don't free name on error.
1101            unsafe { xmlFreeImpl(cur as *mut c_void) };
1102            return ptr::null_mut();
1103        }
1104    }
1105    cur
1106}
1107
1108/// Upstream `xmlNewDocNode`.
1109unsafe fn new_doc_node(
1110    doc: *mut _xmlDoc,
1111    ns: *mut _xmlNs,
1112    name: *const xmlChar,
1113    content: *const xmlChar,
1114) -> *mut _xmlNode {
1115    if name.is_null() {
1116        return ptr::null_mut();
1117    }
1118    let copy = unsafe { dup_str(name) };
1119    if copy.is_null() {
1120        return ptr::null_mut();
1121    }
1122    let cur = unsafe { new_elem(doc, ns, copy, content) };
1123    if cur.is_null() {
1124        unsafe { xmlFreeImpl(copy as *mut c_void) };
1125        return ptr::null_mut();
1126    }
1127    cur
1128}
1129
1130/// Upstream `xmlNewDocNodeEatName`.
1131unsafe fn new_doc_node_eat_name(
1132    doc: *mut _xmlDoc,
1133    ns: *mut _xmlNs,
1134    name: *mut xmlChar,
1135    content: *const xmlChar,
1136) -> *mut _xmlNode {
1137    if name.is_null() {
1138        return ptr::null_mut();
1139    }
1140    let cur = unsafe { new_elem(doc, ns, name, content) };
1141    if cur.is_null() {
1142        unsafe { xmlFreeImpl(name as *mut c_void) };
1143        return ptr::null_mut();
1144    }
1145    cur
1146}
1147
1148/// Upstream `xmlNewDocRawNode`.
1149unsafe fn new_doc_raw_node(
1150    doc: *mut _xmlDoc,
1151    ns: *mut _xmlNs,
1152    name: *const xmlChar,
1153    content: *const xmlChar,
1154) -> *mut _xmlNode {
1155    let cur = unsafe { new_doc_node(doc, ns, name, ptr::null()) };
1156    if cur.is_null() {
1157        return ptr::null_mut();
1158    }
1159    (*cur).doc = doc;
1160    if !content.is_null() {
1161        let text = unsafe { new_doc_text(doc, content) };
1162        if text.is_null() {
1163            unsafe { tree::free_node(cur) };
1164            return ptr::null_mut();
1165        }
1166        (*cur).children = text;
1167        (*cur).last = text;
1168        (*text).parent = cur;
1169    }
1170    cur
1171}
1172
1173// ═══════════════════════════════════════════════════════════════════════════════
1174// xmlDOMWrap internal infrastructure (ns-map, upstream tree.c §DOMWrap)
1175// ═══════════════════════════════════════════════════════════════════════════════
1176
1177/// `xmlDOMWrapAcquireNsFunction` from tree.h.
1178pub type xmlDOMWrapAcquireNsFunction = unsafe extern "C" fn(
1179    ctxt: *mut _xmlDOMWrapCtxt,
1180    node: *mut _xmlNode,
1181    nsName: *const xmlChar,
1182    nsPrefix: *const xmlChar,
1183) -> *mut _xmlNs;
1184
1185/// Upstream `struct _xmlDOMWrapCtxt`.
1186#[repr(C)]
1187pub struct _xmlDOMWrapCtxt {
1188    pub _private: *mut c_void,
1189    pub type_: c_int,
1190    pub namespaceMap: *mut c_void,
1191    pub getNsForNodeFunc: Option<xmlDOMWrapAcquireNsFunction>,
1192}
1193
1194const XML_TREE_NSMAP_PARENT: c_int = -1;
1195const XML_TREE_NSMAP_DOC: c_int = -3;
1196const XML_TREE_NSMAP_CUSTOM: c_int = -4;
1197
1198const XML_DOM_RECONNS_REMOVEREDUND: c_int = 1 << 0;
1199
1200/// Upstream `struct xmlNsMapItem`.
1201#[repr(C)]
1202struct NsMapItem {
1203    next: *mut NsMapItem,
1204    prev: *mut NsMapItem,
1205    oldNs: *mut _xmlNs,
1206    newNs: *mut _xmlNs,
1207    shadowDepth: c_int,
1208    depth: c_int,
1209}
1210
1211/// Upstream `struct xmlNsMap` (with a pool of detached items).
1212#[repr(C)]
1213struct NsMap {
1214    first: *mut NsMapItem,
1215    last: *mut NsMapItem,
1216    pool: *mut NsMapItem,
1217}
1218
1219unsafe fn ns_map_not_empty(m: *mut NsMap) -> bool {
1220    !m.is_null() && !(*m).first.is_null()
1221}
1222
1223/// Upstream `xmlDOMWrapNsMapFree`.
1224unsafe fn ns_map_free(nsmap: *mut NsMap) {
1225    if nsmap.is_null() {
1226        return;
1227    }
1228    let mut cur = (*nsmap).pool;
1229    while !cur.is_null() {
1230        let next = (*cur).next;
1231        unsafe { xmlFreeImpl(cur as *mut c_void) };
1232        cur = next;
1233    }
1234    cur = (*nsmap).first;
1235    while !cur.is_null() {
1236        let next = (*cur).next;
1237        unsafe { xmlFreeImpl(cur as *mut c_void) };
1238        cur = next;
1239    }
1240    unsafe { xmlFreeImpl(nsmap as *mut c_void) };
1241}
1242
1243/// Upstream `xmlDOMWrapNsMapAddItem`.
1244unsafe fn ns_map_add_item(
1245    nsmap: *mut *mut NsMap,
1246    position: c_int,
1247    oldNs: *mut _xmlNs,
1248    newNs: *mut _xmlNs,
1249    depth: c_int,
1250) -> *mut NsMapItem {
1251    if nsmap.is_null() {
1252        return ptr::null_mut();
1253    }
1254    if position != -1 && position != 0 {
1255        return ptr::null_mut();
1256    }
1257    let mut map = *nsmap;
1258    if map.is_null() {
1259        map = unsafe { xmlMallocZero(size_of::<NsMap>()) } as *mut NsMap;
1260        if map.is_null() {
1261            return ptr::null_mut();
1262        }
1263        *nsmap = map;
1264    }
1265    let ret = unsafe { xmlMallocZero(size_of::<NsMapItem>()) } as *mut NsMapItem;
1266    if ret.is_null() {
1267        return ptr::null_mut();
1268    }
1269    if (*map).first.is_null() {
1270        (*map).first = ret;
1271        (*map).last = ret;
1272    } else if position == -1 {
1273        (*ret).prev = (*map).last;
1274        (*(*map).last).next = ret;
1275        (*map).last = ret;
1276    } else {
1277        (*(*map).first).prev = ret;
1278        (*ret).next = (*map).first;
1279        (*map).first = ret;
1280    }
1281    (*ret).oldNs = oldNs;
1282    (*ret).newNs = newNs;
1283    (*ret).shadowDepth = -1;
1284    (*ret).depth = depth;
1285    ret
1286}
1287
1288/// Upstream `XML_NSMAP_POP`.
1289unsafe fn ns_map_pop(map: *mut NsMap) -> *mut NsMapItem {
1290    let i = (*map).last;
1291    (*map).last = (*i).prev;
1292    if (*map).last.is_null() {
1293        (*map).first = ptr::null_mut();
1294    } else {
1295        (*(*map).last).next = ptr::null_mut();
1296    }
1297    (*i).prev = ptr::null_mut();
1298    (*i).next = (*map).pool;
1299    (*map).pool = i;
1300    i
1301}
1302
1303/// Upstream `xmlDOMWrapStoreNs`.
1304unsafe fn store_ns(
1305    doc: *mut _xmlDoc,
1306    ns_name: *const xmlChar,
1307    prefix: *const xmlChar,
1308) -> *mut _xmlNs {
1309    if doc.is_null() {
1310        return ptr::null_mut();
1311    }
1312    let mut ns = unsafe { ensure_xml_decl(doc) };
1313    if ns.is_null() {
1314        return ptr::null_mut();
1315    }
1316    if !(*ns).next.is_null() {
1317        ns = (*ns).next;
1318        loop {
1319            if unsafe { str_eq_or_ptr((*ns).prefix, prefix) }
1320                && !(*ns).href.is_null()
1321                && !ns_name.is_null()
1322                && unsafe { crate::abi::exports_xml2::xmlStrEqual((*ns).href, ns_name) != 0 }
1323            {
1324                return ns;
1325            }
1326            if (*ns).next.is_null() {
1327                break;
1328            }
1329            ns = (*ns).next;
1330        }
1331    }
1332    if !ns.is_null() {
1333        let n = unsafe { tree::new_ns(ptr::null_mut(), ns_name, prefix) };
1334        if n.is_null() {
1335            return ptr::null_mut();
1336        }
1337        (*ns).next = n;
1338        return n;
1339    }
1340    ptr::null_mut()
1341}
1342
1343/// Upstream `xmlDOMWrapNSNormGatherInScopeNs`.
1344unsafe fn gather_in_scope_ns(map: *mut *mut NsMap, node: *mut _xmlNode) -> c_int {
1345    if map.is_null() || !(*map).is_null() {
1346        return -1;
1347    }
1348    if node.is_null() || (*node).type_ == XML_NAMESPACE_DECL as c_int {
1349        return -1;
1350    }
1351    let mut cur = node;
1352    while !cur.is_null() && cur != (*cur).doc as *mut _xmlNode {
1353        if (*cur).type_ == XML_ELEMENT_NODE as c_int && !(*cur).nsDef.is_null() {
1354            let mut ns = (*cur).nsDef;
1355            loop {
1356                let mut shadowed = 0;
1357                if unsafe { ns_map_not_empty(*map) } {
1358                    let mut mi = (*(*map)).first;
1359                    while !mi.is_null() {
1360                        if !(*mi).newNs.is_null()
1361                            && unsafe { str_eq_or_ptr((*ns).prefix, (*(*mi).newNs).prefix) }
1362                        {
1363                            shadowed = 1;
1364                            break;
1365                        }
1366                        mi = (*mi).next;
1367                    }
1368                }
1369                let mi =
1370                    unsafe { ns_map_add_item(map, 0, ptr::null_mut(), ns, XML_TREE_NSMAP_PARENT) };
1371                if mi.is_null() {
1372                    return -1;
1373                }
1374                if shadowed != 0 {
1375                    (*mi).shadowDepth = 0;
1376                }
1377                if (*ns).next.is_null() {
1378                    break;
1379                }
1380                ns = (*ns).next;
1381            }
1382        }
1383        cur = (*cur).parent;
1384    }
1385    0
1386}
1387
1388/// Upstream `xmlDOMWrapNSNormAddNsMapItem2`.
1389unsafe fn ns_norm_add_ns_map_item2(
1390    list: *mut *mut *mut _xmlNs,
1391    size: *mut c_int,
1392    number: *mut c_int,
1393    old_ns: *mut _xmlNs,
1394    new_ns: *mut _xmlNs,
1395) -> c_int {
1396    if *number >= *size {
1397        let new_size = if *size <= 0 { 6 } else { (*size) * 2 };
1398        let tmp = unsafe {
1399            xmlReallocImpl(
1400                *list as *mut c_void,
1401                (new_size as usize) * 2 * size_of::<*mut _xmlNs>(),
1402            )
1403        } as *mut *mut _xmlNs;
1404        if tmp.is_null() {
1405            return -1;
1406        }
1407        *list = tmp;
1408        *size = new_size;
1409    }
1410    let arr = *list;
1411    unsafe {
1412        arr.add((2 * *number) as usize).write(old_ns);
1413        arr.add((2 * *number + 1) as usize).write(new_ns);
1414    }
1415    *number += 1;
1416    0
1417}
1418
1419/// Upstream `xmlSearchNsByPrefixStrict`.
1420unsafe fn search_ns_by_prefix_strict(
1421    doc: *mut _xmlDoc,
1422    node: *mut _xmlNode,
1423    prefix: *const xmlChar,
1424    ret_ns: *mut *mut _xmlNs,
1425) -> c_int {
1426    if doc.is_null() || node.is_null() || (*node).type_ == XML_NAMESPACE_DECL as c_int {
1427        return -1;
1428    }
1429    if !ret_ns.is_null() {
1430        *ret_ns = ptr::null_mut();
1431    }
1432    if unsafe { is_str_xml(prefix) } {
1433        if !ret_ns.is_null() {
1434            let ns = unsafe { ensure_xml_decl(doc) };
1435            if ns.is_null() {
1436                return -1;
1437            }
1438            *ret_ns = ns;
1439        }
1440        return 1;
1441    }
1442    let mut cur = node;
1443    loop {
1444        if (*cur).type_ == XML_ELEMENT_NODE as c_int {
1445            if !(*cur).nsDef.is_null() {
1446                let mut ns = (*cur).nsDef;
1447                loop {
1448                    if unsafe { str_eq_or_ptr(prefix, (*ns).prefix) } {
1449                        // Disabled namespaces, e.g. xmlns:abc="".
1450                        if (*ns).href.is_null() {
1451                            return 0;
1452                        }
1453                        if !ret_ns.is_null() {
1454                            *ret_ns = ns;
1455                        }
1456                        return 1;
1457                    }
1458                    if (*ns).next.is_null() {
1459                        break;
1460                    }
1461                    ns = (*ns).next;
1462                }
1463            }
1464        } else if (*cur).type_ == XML_ENTITY_DECL as c_int {
1465            return 0;
1466        }
1467        cur = (*cur).parent;
1468        if cur.is_null() || (*cur).doc == cur as *mut _xmlDoc {
1469            break;
1470        }
1471    }
1472    0
1473}
1474
1475/// Upstream `xmlSearchNsByNamespaceStrict`.
1476unsafe fn search_ns_by_namespace_strict(
1477    doc: *mut _xmlDoc,
1478    node: *mut _xmlNode,
1479    ns_name: *const xmlChar,
1480    ret_ns: *mut *mut _xmlNs,
1481    prefixed: c_int,
1482) -> c_int {
1483    if doc.is_null() || ns_name.is_null() || ret_ns.is_null() {
1484        return -1;
1485    }
1486    if node.is_null() || (*node).type_ == XML_NAMESPACE_DECL as c_int {
1487        return -1;
1488    }
1489    *ret_ns = ptr::null_mut();
1490    if unsafe {
1491        crate::abi::exports_xml2::xmlStrEqual(ns_name, XML_XML_NAMESPACE.as_ptr() as *const xmlChar)
1492            != 0
1493    } {
1494        let ns = unsafe { ensure_xml_decl(doc) };
1495        if ns.is_null() {
1496            return -1;
1497        }
1498        *ret_ns = ns;
1499        return 1;
1500    }
1501    let mut cur = node;
1502    let mut prev: *mut _xmlNode = ptr::null_mut();
1503    let mut out: *mut _xmlNode = ptr::null_mut();
1504    loop {
1505        if (*cur).type_ == XML_ELEMENT_NODE as c_int {
1506            if !(*cur).nsDef.is_null() {
1507                let mut ns = (*cur).nsDef;
1508                loop {
1509                    if prefixed != 0 && (*ns).prefix.is_null() {
1510                        if (*ns).next.is_null() {
1511                            break;
1512                        }
1513                        ns = (*ns).next;
1514                        continue;
1515                    }
1516                    if !prev.is_null() {
1517                        // Check the last level of ns-decls for a shadowing prefix.
1518                        let mut prevns = (*prev).nsDef;
1519                        let mut shadowed: *mut _xmlNs = ptr::null_mut();
1520                        while !prevns.is_null() {
1521                            if unsafe { str_eq_or_ptr((*prevns).prefix, (*ns).prefix) } {
1522                                shadowed = prevns;
1523                                break;
1524                            }
1525                            prevns = (*prevns).next;
1526                        }
1527                        if !shadowed.is_null() {
1528                            if (*ns).next.is_null() {
1529                                break;
1530                            }
1531                            ns = (*ns).next;
1532                            continue;
1533                        }
1534                    }
1535                    if unsafe { crate::abi::exports_xml2::xmlStrEqual(ns_name, (*ns).href) != 0 } {
1536                        if !out.is_null() {
1537                            // The prefix might be shadowed at the 3rd level.
1538                            if unsafe {
1539                                search_ns_by_prefix_strict(doc, node, (*ns).prefix, ptr::null_mut())
1540                            } == 0
1541                            {
1542                                if (*ns).next.is_null() {
1543                                    break;
1544                                }
1545                                ns = (*ns).next;
1546                                continue;
1547                            }
1548                        }
1549                        *ret_ns = ns;
1550                        return 1;
1551                    }
1552                    if (*ns).next.is_null() {
1553                        break;
1554                    }
1555                    ns = (*ns).next;
1556                }
1557                out = prev;
1558                prev = cur;
1559            }
1560        } else if (*cur).type_ == XML_ENTITY_DECL as c_int {
1561            return 0;
1562        }
1563        cur = (*cur).parent;
1564        if cur.is_null() || (*cur).doc == cur as *mut _xmlDoc {
1565            break;
1566        }
1567    }
1568    0
1569}
1570
1571/// Upstream `xmlDOMWrapNSNormDeclareNsForced`.
1572unsafe fn declare_ns_forced(
1573    doc: *mut _xmlDoc,
1574    elem: *mut _xmlNode,
1575    ns_name: *const xmlChar,
1576    prefix: *const xmlChar,
1577    check_shadow: c_int,
1578) -> *mut _xmlNs {
1579    if doc.is_null() || elem.is_null() || (*elem).type_ != XML_ELEMENT_NODE as c_int {
1580        return ptr::null_mut();
1581    }
1582    let mut counter: c_int = 0;
1583    let mut buf: Vec<u8> = Vec::new();
1584    let mut cur_prefix: *const xmlChar = prefix;
1585    loop {
1586        let mut used = false;
1587        if !(*elem).nsDef.is_null()
1588            && !unsafe { ns_list_lookup_by_prefix((*elem).nsDef, cur_prefix) }.is_null()
1589        {
1590            used = true;
1591        }
1592        if !used && check_shadow != 0 && !(*elem).parent.is_null() {
1593            if unsafe {
1594                search_ns_by_prefix_strict(doc, (*elem).parent, cur_prefix, ptr::null_mut())
1595            } == 1
1596            {
1597                used = true;
1598            }
1599        }
1600        if !used {
1601            let ret = unsafe { tree::new_ns(ptr::null_mut(), ns_name, cur_prefix) };
1602            if ret.is_null() {
1603                return ptr::null_mut();
1604            }
1605            if (*elem).nsDef.is_null() {
1606                (*elem).nsDef = ret;
1607            } else {
1608                let mut ns2 = (*elem).nsDef;
1609                while !(*ns2).next.is_null() {
1610                    ns2 = (*ns2).next;
1611                }
1612                (*ns2).next = ret;
1613            }
1614            return ret;
1615        }
1616        counter += 1;
1617        if counter > 1000 {
1618            return ptr::null_mut();
1619        }
1620        if prefix.is_null() {
1621            let s = format!("ns_{}", counter);
1622            buf = s.into_bytes();
1623            buf.push(0);
1624        } else {
1625            let p = unsafe { str_prefix(prefix, 30) };
1626            let p_str = String::from_utf8_lossy(&p[..p.len().saturating_sub(1)]);
1627            let s = format!("{}_{}", p_str, counter);
1628            buf = s.into_bytes();
1629            buf.push(0);
1630        }
1631        cur_prefix = buf.as_ptr() as *const xmlChar;
1632    }
1633}
1634
1635/// Upstream `xmlDOMWrapNSNormAcquireNormalizedNs`.
1636unsafe fn acquire_normalized_ns(
1637    doc: *mut _xmlDoc,
1638    elem: *mut _xmlNode,
1639    ns: *mut _xmlNs,
1640    ret_ns: *mut *mut _xmlNs,
1641    ns_map: *mut *mut NsMap,
1642    depth: c_int,
1643    ancestors_only: c_int,
1644    prefixed: c_int,
1645) -> c_int {
1646    if doc.is_null() || ns.is_null() || ret_ns.is_null() || ns_map.is_null() {
1647        return -1;
1648    }
1649    *ret_ns = ptr::null_mut();
1650    if unsafe { is_str_xml((*ns).prefix) } {
1651        let xml_ns = unsafe { ensure_xml_decl(doc) };
1652        if xml_ns.is_null() {
1653            return -1;
1654        }
1655        *ret_ns = xml_ns;
1656        return 0;
1657    }
1658    if unsafe { ns_map_not_empty(*ns_map) } && !(ancestors_only != 0 && elem.is_null()) {
1659        let mut mi = (*(*ns_map)).first;
1660        while !mi.is_null() {
1661            if (*mi).depth >= XML_TREE_NSMAP_PARENT
1662                && ((ancestors_only == 0) || (*mi).depth == XML_TREE_NSMAP_PARENT)
1663                && (*mi).shadowDepth == -1
1664                && !(*mi).newNs.is_null()
1665                && !(*(*mi).newNs).href.is_null()
1666                && *(*(*mi).newNs).href != 0
1667                && ((prefixed == 0) || !(*(*mi).newNs).prefix.is_null())
1668                && ((*ns).href == (*(*mi).newNs).href
1669                    || unsafe {
1670                        crate::abi::exports_xml2::xmlStrEqual((*ns).href, (*(*mi).newNs).href) != 0
1671                    })
1672            {
1673                (*mi).oldNs = ns;
1674                *ret_ns = (*mi).newNs;
1675                return 0;
1676            }
1677            mi = (*mi).next;
1678        }
1679    }
1680    if elem.is_null() {
1681        let tmpns = unsafe { store_ns(doc, (*ns).href, (*ns).prefix) };
1682        if tmpns.is_null() {
1683            return -1;
1684        }
1685        if unsafe { ns_map_add_item(ns_map, -1, ns, tmpns, XML_TREE_NSMAP_DOC) }.is_null() {
1686            return -1;
1687        }
1688        *ret_ns = tmpns;
1689    } else {
1690        let tmpns = unsafe { declare_ns_forced(doc, elem, (*ns).href, (*ns).prefix, 0) };
1691        if tmpns.is_null() {
1692            return -1;
1693        }
1694        if unsafe { ns_map_not_empty(*ns_map) } {
1695            let mut mi = (*(*ns_map)).first;
1696            while !mi.is_null() {
1697                if (*mi).depth < depth
1698                    && (*mi).shadowDepth == -1
1699                    && !(*mi).newNs.is_null()
1700                    && unsafe { str_eq_or_ptr((*ns).prefix, (*(*mi).newNs).prefix) }
1701                {
1702                    (*mi).shadowDepth = depth;
1703                    break;
1704                }
1705                mi = (*mi).next;
1706            }
1707        }
1708        if unsafe { ns_map_add_item(ns_map, -1, ns, tmpns, depth) }.is_null() {
1709            return -1;
1710        }
1711        *ret_ns = tmpns;
1712    }
1713    0
1714}
1715
1716/// Pop the ns-map entries at/under `depth` and unshadow, when leaving an
1717/// element node (upstream tail of xmlDOMWrapReconcileNamespaces /
1718/// xmlDOMWrapAdoptBranch / xmlDOMWrapCloneNode).
1719unsafe fn ns_map_pop_depth(ns_map: *mut NsMap, depth: c_int) {
1720    if ns_map.is_null() {
1721        return;
1722    }
1723    if unsafe { ns_map_not_empty(ns_map) } {
1724        while !(*ns_map).last.is_null() && (*(*ns_map).last).depth >= depth {
1725            unsafe { ns_map_pop(ns_map) };
1726        }
1727        let mut mi = (*ns_map).first;
1728        while !mi.is_null() {
1729            if (*mi).shadowDepth >= depth {
1730                (*mi).shadowDepth = -1;
1731            }
1732            mi = (*mi).next;
1733        }
1734    }
1735}
1736
1737// ═══════════════════════════════════════════════════════════════════════════════
1738// Namespace free functions
1739// ═══════════════════════════════════════════════════════════════════════════════
1740
1741/// Free an xmlNs object.
1742///
1743/// # UPSTREAM-PARITY
1744///
1745/// ```c
1746/// void xmlFreeNs(xmlNs *cur);
1747/// ```
1748///
1749/// # SAFETY
1750///
1751/// - `cur` must be a valid pointer to an _xmlNs, or NULL.
1752#[no_mangle]
1753pub unsafe extern "C" fn xmlFreeNs(cur: *mut _xmlNs) {
1754    unsafe { free_ns_impl(cur) };
1755}
1756
1757/// Free a list of xmlNs objects.
1758///
1759/// # UPSTREAM-PARITY
1760///
1761/// ```c
1762/// void xmlFreeNsList(xmlNs *cur);
1763/// ```
1764///
1765/// # SAFETY
1766///
1767/// - `cur` must be a valid pointer to the first _xmlNs, or NULL.
1768#[no_mangle]
1769pub unsafe extern "C" fn xmlFreeNsList(cur: *mut _xmlNs) {
1770    let mut c = cur;
1771    while !c.is_null() {
1772        let next = (*c).next;
1773        unsafe { free_ns_impl(c) };
1774        c = next;
1775    }
1776}
1777
1778// ═══════════════════════════════════════════════════════════════════════════════
1779// Attribute free functions
1780// ═══════════════════════════════════════════════════════════════════════════════
1781
1782/// Free an attribute including all children.
1783///
1784/// # UPSTREAM-PARITY
1785///
1786/// ```c
1787/// void xmlFreeProp(xmlAttr *cur);
1788/// ```
1789///
1790/// # SAFETY
1791///
1792/// - `cur` must be a valid pointer to an _xmlAttr, or NULL.
1793#[no_mangle]
1794pub unsafe extern "C" fn xmlFreeProp(cur: *mut _xmlAttr) {
1795    unsafe { free_prop_impl(cur) };
1796}
1797
1798/// Free an attribute list including all children.
1799///
1800/// # UPSTREAM-PARITY
1801///
1802/// ```c
1803/// void xmlFreePropList(xmlAttr *cur);
1804/// ```
1805///
1806/// # SAFETY
1807///
1808/// - `cur` must be a valid pointer to the first _xmlAttr, or NULL.
1809#[no_mangle]
1810pub unsafe extern "C" fn xmlFreePropList(cur: *mut _xmlAttr) {
1811    let mut c = cur;
1812    while !c.is_null() {
1813        let next = (*c).next;
1814        unsafe { free_prop_impl(c) };
1815        c = next;
1816    }
1817}
1818
1819// ═══════════════════════════════════════════════════════════════════════════════
1820// Creation functions (elements, fragments, properties, references, texts)
1821// ═══════════════════════════════════════════════════════════════════════════════
1822
1823/// Create an element node, eating the `name` string.
1824///
1825/// Like #xmlNewNode, but the `name` string will be used directly
1826/// without making a copy. Takes ownership of `name` which will also
1827/// be freed on error.
1828///
1829/// # UPSTREAM-PARITY
1830///
1831/// ```c
1832/// xmlNode *xmlNewNodeEatName(xmlNs *ns, xmlChar *name);
1833/// ```
1834///
1835/// # SAFETY
1836///
1837/// - `name` must be a valid xmlMalloc'd string (ownership transferred).
1838#[no_mangle]
1839pub unsafe extern "C" fn xmlNewNodeEatName(ns: *mut _xmlNs, name: *mut xmlChar) -> *mut _xmlNode {
1840    unsafe { new_doc_node_eat_name(ptr::null_mut(), ns, name, ptr::null()) }
1841}
1842
1843/// Create an element node.
1844///
1845/// If provided, `content` is expected to be a valid XML attribute value
1846/// possibly containing character and entity references. Syntax errors
1847/// and references to undeclared entities are ignored silently.
1848///
1849/// # UPSTREAM-PARITY
1850///
1851/// ```c
1852/// xmlNode *xmlNewDocNode(xmlDoc *doc, xmlNs *ns,
1853///                        const xmlChar *name, const xmlChar *content);
1854/// ```
1855///
1856/// # SAFETY
1857///
1858/// - `name` must be a valid null-terminated string.
1859#[no_mangle]
1860pub unsafe extern "C" fn xmlNewDocNode(
1861    doc: *mut _xmlDoc,
1862    ns: *mut _xmlNs,
1863    name: *const xmlChar,
1864    content: *const xmlChar,
1865) -> *mut _xmlNode {
1866    unsafe { new_doc_node(doc, ns, name, content) }
1867}
1868
1869/// Create an element node, eating the `name` string.
1870///
1871/// # UPSTREAM-PARITY
1872///
1873/// ```c
1874/// xmlNode *xmlNewDocNodeEatName(xmlDoc *doc, xmlNs *ns,
1875///                               xmlChar *name, const xmlChar *content);
1876/// ```
1877///
1878/// # SAFETY
1879///
1880/// - `name` must be a valid xmlMalloc'd string (ownership transferred).
1881#[no_mangle]
1882pub unsafe extern "C" fn xmlNewDocNodeEatName(
1883    doc: *mut _xmlDoc,
1884    ns: *mut _xmlNs,
1885    name: *mut xmlChar,
1886    content: *const xmlChar,
1887) -> *mut _xmlNode {
1888    unsafe { new_doc_node_eat_name(doc, ns, name, content) }
1889}
1890
1891/// Create an element node with raw (unescaped) text content.
1892///
1893/// # UPSTREAM-PARITY
1894///
1895/// ```c
1896/// xmlNode *xmlNewDocRawNode(xmlDoc *doc, xmlNs *ns,
1897///                           const xmlChar *name, const xmlChar *content);
1898/// ```
1899///
1900/// # SAFETY
1901///
1902/// - `name` must be a valid null-terminated string.
1903#[no_mangle]
1904pub unsafe extern "C" fn xmlNewDocRawNode(
1905    doc: *mut _xmlDoc,
1906    ns: *mut _xmlNs,
1907    name: *const xmlChar,
1908    content: *const xmlChar,
1909) -> *mut _xmlNode {
1910    unsafe { new_doc_raw_node(doc, ns, name, content) }
1911}
1912
1913/// Create a document fragment node.
1914///
1915/// # UPSTREAM-PARITY
1916///
1917/// ```c
1918/// xmlNode *xmlNewDocFragment(xmlDoc *doc);
1919/// ```
1920///
1921/// # SAFETY
1922///
1923/// - `doc` must be a valid pointer to an _xmlDoc, or NULL.
1924#[no_mangle]
1925pub unsafe extern "C" fn xmlNewDocFragment(doc: *mut _xmlDoc) -> *mut _xmlNode {
1926    let cur = unsafe { xmlMallocZero(size_of::<_xmlNode>()) } as *mut _xmlNode;
1927    if cur.is_null() {
1928        return ptr::null_mut();
1929    }
1930    (*cur).type_ = XML_DOCUMENT_FRAG_NODE as c_int;
1931    (*cur).doc = doc;
1932    cur
1933}
1934
1935/// Create an attribute node (raw value).
1936///
1937/// # UPSTREAM-PARITY
1938///
1939/// ```c
1940/// xmlAttr *xmlNewProp(xmlNode *node, const xmlChar *name, const xmlChar *value);
1941/// ```
1942///
1943/// # SAFETY
1944///
1945/// - `name` must be a valid null-terminated string.
1946#[no_mangle]
1947pub unsafe extern "C" fn xmlNewProp(
1948    node: *mut _xmlNode,
1949    name: *const xmlChar,
1950    value: *const xmlChar,
1951) -> *mut _xmlAttr {
1952    if name.is_null() {
1953        return ptr::null_mut();
1954    }
1955    unsafe { new_prop_internal(node, ptr::null_mut(), name, value, 0) }
1956}
1957
1958/// Create a namespaced attribute node (raw value).
1959///
1960/// # UPSTREAM-PARITY
1961///
1962/// ```c
1963/// xmlAttr *xmlNewNsProp(xmlNode *node, xmlNs *ns,
1964///                       const xmlChar *name, const xmlChar *value);
1965/// ```
1966///
1967/// # SAFETY
1968///
1969/// - `name` must be a valid null-terminated string.
1970#[no_mangle]
1971pub unsafe extern "C" fn xmlNewNsProp(
1972    node: *mut _xmlNode,
1973    ns: *mut _xmlNs,
1974    name: *const xmlChar,
1975    value: *const xmlChar,
1976) -> *mut _xmlAttr {
1977    if name.is_null() {
1978        return ptr::null_mut();
1979    }
1980    unsafe { new_prop_internal(node, ns, name, value, 0) }
1981}
1982
1983/// Create a namespaced attribute node, eating the `name` string.
1984///
1985/// # UPSTREAM-PARITY
1986///
1987/// ```c
1988/// xmlAttr *xmlNewNsPropEatName(xmlNode *node, xmlNs *ns,
1989///                              xmlChar *name, const xmlChar *value);
1990/// ```
1991///
1992/// # SAFETY
1993///
1994/// - `name` must be a valid xmlMalloc'd string (ownership transferred).
1995#[no_mangle]
1996pub unsafe extern "C" fn xmlNewNsPropEatName(
1997    node: *mut _xmlNode,
1998    ns: *mut _xmlNs,
1999    name: *mut xmlChar,
2000    value: *const xmlChar,
2001) -> *mut _xmlAttr {
2002    if name.is_null() {
2003        return ptr::null_mut();
2004    }
2005    unsafe { new_prop_internal(node, ns, name, value, 1) }
2006}
2007
2008/// Create an attribute node; `value` may contain XML character/entity
2009/// references.
2010///
2011/// # UPSTREAM-PARITY
2012///
2013/// ```c
2014/// xmlAttr *xmlNewDocProp(xmlDoc *doc, const xmlChar *name, const xmlChar *value);
2015/// ```
2016///
2017/// # SAFETY
2018///
2019/// - `name` must be a valid null-terminated string.
2020#[no_mangle]
2021pub unsafe extern "C" fn xmlNewDocProp(
2022    doc: *mut _xmlDoc,
2023    name: *const xmlChar,
2024    value: *const xmlChar,
2025) -> *mut _xmlAttr {
2026    if name.is_null() {
2027        return ptr::null_mut();
2028    }
2029    let cur = unsafe { xmlMallocZero(size_of::<_xmlAttr>()) } as *mut _xmlAttr;
2030    if cur.is_null() {
2031        return ptr::null_mut();
2032    }
2033    (*cur).type_ = XML_ATTRIBUTE_NODE as c_int;
2034    (*cur).name = unsafe { dup_str(name) };
2035    if (*cur).name.is_null() {
2036        unsafe { xmlFreeImpl(cur as *mut c_void) };
2037        return ptr::null_mut();
2038    }
2039    (*cur).doc = doc;
2040    if !value.is_null() {
2041        if unsafe { node_parse_att_value(doc, cur as *mut _xmlNode, value, usize::MAX) } < 0 {
2042            unsafe { free_prop_impl(cur) };
2043            return ptr::null_mut();
2044        }
2045    }
2046    cur
2047}
2048
2049/// Create a new entity reference node, linking the result with the
2050/// entity in `doc` if found.
2051///
2052/// Entity names like `&entity;` are handled as well.
2053///
2054/// # UPSTREAM-PARITY
2055///
2056/// ```c
2057/// xmlNode *xmlNewReference(const xmlDoc *doc, const xmlChar *name);
2058/// ```
2059///
2060/// # SAFETY
2061///
2062/// - `name` must be a valid null-terminated string.
2063#[no_mangle]
2064pub unsafe extern "C" fn xmlNewReference(
2065    doc: *const _xmlDoc,
2066    name: *const xmlChar,
2067) -> *mut _xmlNode {
2068    if name.is_null() {
2069        return ptr::null_mut();
2070    }
2071    let cur = unsafe { xmlMallocZero(size_of::<_xmlNode>()) } as *mut _xmlNode;
2072    if cur.is_null() {
2073        return ptr::null_mut();
2074    }
2075    (*cur).type_ = XML_ENTITY_REF_NODE as c_int;
2076    (*cur).doc = doc as *mut _xmlDoc;
2077    let mut nm = name;
2078    if *nm == b'&' {
2079        nm = nm.add(1);
2080        let len = unsafe { str_len(nm) } as usize;
2081        (*cur).name = if len > 0 && unsafe { *nm.add(len - 1) } == b';' {
2082            unsafe { crate::abi::exports_xml2::xmlStrndup(nm, (len - 1) as c_int) }
2083        } else {
2084            unsafe { crate::abi::exports_xml2::xmlStrndup(nm, len as c_int) }
2085        };
2086    } else {
2087        (*cur).name = unsafe { dup_str(nm) };
2088    }
2089    if (*cur).name.is_null() {
2090        unsafe { tree::free_node(cur) };
2091        return ptr::null_mut();
2092    }
2093    let ent = unsafe { tree::get_doc_entity(doc as *mut _xmlDoc, (*cur).name) };
2094    if !ent.is_null() {
2095        (*cur).content = (*ent).content;
2096        (*cur).children = ent as *mut _xmlNode;
2097        (*cur).last = ent as *mut _xmlNode;
2098    }
2099    cur
2100}
2101
2102/// Create a new child element with raw text content and append it to a
2103/// parent element.
2104///
2105/// If `ns` is NULL, the newly created element inherits the namespace
2106/// of the parent.
2107///
2108/// # UPSTREAM-PARITY
2109///
2110/// ```c
2111/// xmlNode *xmlNewTextChild(xmlNode *parent, xmlNs *ns,
2112///                          const xmlChar *name, const xmlChar *content);
2113/// ```
2114///
2115/// # SAFETY
2116///
2117/// - `parent` must be a valid pointer to an _xmlNode.
2118/// - `name` must be a valid null-terminated string.
2119#[no_mangle]
2120pub unsafe extern "C" fn xmlNewTextChild(
2121    parent: *mut _xmlNode,
2122    ns: *mut _xmlNs,
2123    name: *const xmlChar,
2124    content: *const xmlChar,
2125) -> *mut _xmlNode {
2126    if parent.is_null() || name.is_null() {
2127        return ptr::null_mut();
2128    }
2129    let mut ns = ns;
2130    match (*parent).type_ as u32 {
2131        t if t == XML_DOCUMENT_NODE as u32
2132            || t == XML_HTML_DOCUMENT_NODE as u32
2133            || t == XML_DOCUMENT_FRAG_NODE as u32 => {}
2134        t if t == XML_ELEMENT_NODE as u32 => {
2135            if ns.is_null() {
2136                ns = (*parent).ns;
2137            }
2138        }
2139        _ => return ptr::null_mut(),
2140    }
2141    let cur = unsafe { new_doc_raw_node((*parent).doc, ns, name, content) };
2142    if cur.is_null() {
2143        return ptr::null_mut();
2144    }
2145    (*cur).parent = parent;
2146    if (*parent).children.is_null() {
2147        (*parent).children = cur;
2148        (*parent).last = cur;
2149    } else {
2150        let prev = (*parent).last;
2151        (*prev).next = cur;
2152        (*cur).prev = prev;
2153        (*parent).last = cur;
2154    }
2155    cur
2156}
2157
2158/// Create a new text node.
2159///
2160/// # UPSTREAM-PARITY
2161///
2162/// ```c
2163/// xmlNode *xmlNewTextLen(const xmlChar *content, int len);
2164/// ```
2165///
2166/// # SAFETY
2167///
2168/// - `content` must point to at least `len` bytes, or be NULL.
2169#[no_mangle]
2170pub unsafe extern "C" fn xmlNewTextLen(content: *const xmlChar, len: c_int) -> *mut _xmlNode {
2171    let cur = unsafe { xmlMallocZero(size_of::<_xmlNode>()) } as *mut _xmlNode;
2172    if cur.is_null() {
2173        return ptr::null_mut();
2174    }
2175    (*cur).type_ = XML_TEXT_NODE as c_int;
2176    (*cur).name = unsafe { dup_str(TEXT_NAME.as_ptr() as *const xmlChar) };
2177    if !content.is_null() {
2178        (*cur).content = unsafe { crate::abi::exports_xml2::xmlStrndup(content, len) };
2179        if (*cur).content.is_null() {
2180            unsafe { tree::free_node(cur) };
2181            return ptr::null_mut();
2182        }
2183    }
2184    cur
2185}
2186
2187// ═══════════════════════════════════════════════════════════════════════════════
2188// Content get/set/add
2189// ═══════════════════════════════════════════════════════════════════════════════
2190
2191/// Replace the text content of a node.
2192///
2193/// # UPSTREAM-PARITY
2194///
2195/// ```c
2196/// int xmlNodeSetContent(xmlNode *cur, const xmlChar *content);
2197/// ```
2198///
2199/// # SAFETY
2200///
2201/// - `cur` must be a valid pointer to an _xmlNode, or NULL.
2202#[no_mangle]
2203pub unsafe extern "C" fn xmlNodeSetContent(cur: *mut _xmlNode, content: *const xmlChar) -> c_int {
2204    unsafe { node_set_content_internal(cur, content, -1) }
2205}
2206
2207/// See #xmlNodeSetContent.
2208///
2209/// # UPSTREAM-PARITY
2210///
2211/// ```c
2212/// int xmlNodeSetContentLen(xmlNode *cur, const xmlChar *content, int len);
2213/// ```
2214///
2215/// # SAFETY
2216///
2217/// - `cur` must be a valid pointer to an _xmlNode, or NULL.
2218#[no_mangle]
2219pub unsafe extern "C" fn xmlNodeSetContentLen(
2220    cur: *mut _xmlNode,
2221    content: *const xmlChar,
2222    len: c_int,
2223) -> c_int {
2224    unsafe { node_set_content_internal(cur, content, len) }
2225}
2226
2227/// Upstream `xmlNodeSetContentInternal`.
2228unsafe fn node_set_content_internal(
2229    cur: *mut _xmlNode,
2230    content: *const xmlChar,
2231    len: c_int,
2232) -> c_int {
2233    if cur.is_null() {
2234        return 1;
2235    }
2236    match (*cur).type_ as u32 {
2237        t if t == XML_DOCUMENT_FRAG_NODE as u32
2238            || t == XML_ELEMENT_NODE as u32
2239            || t == XML_ATTRIBUTE_NODE as u32 =>
2240        {
2241            let max_size = if len < 0 { usize::MAX } else { len as usize };
2242            if unsafe { node_parse_att_value((*cur).doc, cur, content, max_size) } < 0 {
2243                return -1;
2244            }
2245        }
2246        t if t == XML_TEXT_NODE as u32
2247            || t == XML_CDATA_SECTION_NODE as u32
2248            || t == XML_PI_NODE as u32
2249            || t == XML_COMMENT_NODE as u32 =>
2250        {
2251            let mut copy: *mut xmlChar = ptr::null_mut();
2252            if !content.is_null() {
2253                copy = if len < 0 {
2254                    unsafe { dup_str(content) }
2255                } else {
2256                    unsafe { crate::abi::exports_xml2::xmlStrndup(content, len) }
2257                };
2258                if copy.is_null() {
2259                    return -1;
2260                }
2261            }
2262            unsafe { text_set_content(cur, copy) };
2263        }
2264        _ => {}
2265    }
2266    0
2267}
2268
2269/// Append the extra substring to the node content.
2270///
2271/// NOTE: In contrast to #xmlNodeSetContentLen, `content` is supposed
2272/// to be raw text, so unescaped XML special chars are allowed.
2273///
2274/// # UPSTREAM-PARITY
2275///
2276/// ```c
2277/// int xmlNodeAddContent(xmlNode *cur, const xmlChar *content);
2278/// ```
2279///
2280/// # SAFETY
2281///
2282/// - `cur` must be a valid pointer to an _xmlNode, or NULL.
2283#[no_mangle]
2284pub unsafe extern "C" fn xmlNodeAddContent(cur: *mut _xmlNode, content: *const xmlChar) -> c_int {
2285    let len = unsafe { str_len(content) };
2286    unsafe { xmlNodeAddContentLen(cur, content, len) }
2287}
2288
2289/// Append the extra substring to the node content.
2290///
2291/// # UPSTREAM-PARITY
2292///
2293/// ```c
2294/// int xmlNodeAddContentLen(xmlNode *cur, const xmlChar *content, int len);
2295/// ```
2296///
2297/// # SAFETY
2298///
2299/// - `cur` must be a valid pointer to an _xmlNode, or NULL.
2300#[no_mangle]
2301pub unsafe extern "C" fn xmlNodeAddContentLen(
2302    cur: *mut _xmlNode,
2303    content: *const xmlChar,
2304    len: c_int,
2305) -> c_int {
2306    if cur.is_null() {
2307        return 1;
2308    }
2309    if content.is_null() || len <= 0 {
2310        return 0;
2311    }
2312    match (*cur).type_ as u32 {
2313        t if t == XML_DOCUMENT_FRAG_NODE as u32
2314            || t == XML_ELEMENT_NODE as u32
2315            || t == XML_ATTRIBUTE_NODE as u32 =>
2316        {
2317            let new_node = unsafe { new_doc_text_len((*cur).doc, content, len) };
2318            if new_node.is_null() {
2319                return -1;
2320            }
2321            let tmp = unsafe { add_child_coalesce(cur, new_node) };
2322            if tmp.is_null() {
2323                unsafe { tree::free_node(new_node) };
2324                return -1;
2325            }
2326        }
2327        t if t == XML_TEXT_NODE as u32
2328            || t == XML_CDATA_SECTION_NODE as u32
2329            || t == XML_PI_NODE as u32
2330            || t == XML_COMMENT_NODE as u32 =>
2331        {
2332            return unsafe { text_add_content(cur, content, len) };
2333        }
2334        _ => {}
2335    }
2336    0
2337}
2338
2339/// Get the string value of a node (caller frees with xmlFree).
2340///
2341/// # UPSTREAM-PARITY
2342///
2343/// ```c
2344/// xmlChar *xmlNodeGetContent(const xmlNode *cur);
2345/// ```
2346///
2347/// # SAFETY
2348///
2349/// - `cur` must be a valid pointer to an _xmlNode, or NULL.
2350#[no_mangle]
2351pub unsafe extern "C" fn xmlNodeGetContent(cur: *const _xmlNode) -> *mut xmlChar {
2352    unsafe { tree::node_get_content(cur as *mut _xmlNode) }
2353}
2354
2355// ═══════════════════════════════════════════════════════════════════════════════
2356// xml:lang / xml:space / xml:base / name handling
2357// ═══════════════════════════════════════════════════════════════════════════════
2358
2359/// Set the `xml:lang` attribute of a node.
2360///
2361/// # UPSTREAM-PARITY
2362///
2363/// ```c
2364/// int xmlNodeSetLang(xmlNode *cur, const xmlChar *lang);
2365/// ```
2366///
2367/// # SAFETY
2368///
2369/// - `cur` must be a valid pointer to an element node.
2370#[no_mangle]
2371pub unsafe extern "C" fn xmlNodeSetLang(cur: *mut _xmlNode, lang: *const xmlChar) -> c_int {
2372    if cur.is_null() || (*cur).type_ != XML_ELEMENT_NODE as c_int {
2373        return 1;
2374    }
2375    let ns = unsafe { ensure_xml_decl((*cur).doc) };
2376    if ns.is_null() {
2377        return -1;
2378    }
2379    let attr = unsafe { set_ns_prop_impl(cur, ns, b"lang\0".as_ptr() as *const xmlChar, lang) };
2380    if attr.is_null() {
2381        return -1;
2382    }
2383    0
2384}
2385
2386/// Find the `xml:lang` of a node (nearest ancestor-or-self).
2387///
2388/// # UPSTREAM-PARITY
2389///
2390/// ```c
2391/// xmlChar *xmlNodeGetLang(const xmlNode *cur);
2392/// ```
2393///
2394/// # SAFETY
2395///
2396/// - `cur` must be a valid pointer to an _xmlNode, or NULL.
2397#[no_mangle]
2398pub unsafe extern "C" fn xmlNodeGetLang(cur: *const _xmlNode) -> *mut xmlChar {
2399    if cur.is_null() || (*cur).type_ == XML_NAMESPACE_DECL as c_int {
2400        return ptr::null_mut();
2401    }
2402    let mut c = cur;
2403    while !c.is_null() {
2404        let lang = unsafe {
2405            get_attr_value(
2406                c as *mut _xmlNode,
2407                b"lang\0".as_ptr() as *const xmlChar,
2408                XML_XML_NAMESPACE.as_ptr() as *const xmlChar,
2409            )
2410        };
2411        if !lang.is_null() {
2412            return lang;
2413        }
2414        c = (*c).parent;
2415    }
2416    ptr::null_mut()
2417}
2418
2419/// Set the `xml:space` attribute of a node.
2420///
2421/// # UPSTREAM-PARITY
2422///
2423/// ```c
2424/// int xmlNodeSetSpacePreserve(xmlNode *cur, int val);
2425/// ```
2426///
2427/// # SAFETY
2428///
2429/// - `cur` must be a valid pointer to an element node.
2430#[no_mangle]
2431pub unsafe extern "C" fn xmlNodeSetSpacePreserve(cur: *mut _xmlNode, val: c_int) -> c_int {
2432    if cur.is_null() || (*cur).type_ != XML_ELEMENT_NODE as c_int {
2433        return 1;
2434    }
2435    let ns = unsafe { ensure_xml_decl((*cur).doc) };
2436    if ns.is_null() {
2437        return -1;
2438    }
2439    let string: &[u8] = if val == 0 {
2440        b"default\0"
2441    } else {
2442        b"preserve\0"
2443    };
2444    let attr = unsafe {
2445        set_ns_prop_impl(
2446            cur,
2447            ns,
2448            b"space\0".as_ptr() as *const xmlChar,
2449            string.as_ptr() as *const xmlChar,
2450        )
2451    };
2452    if attr.is_null() {
2453        return -1;
2454    }
2455    0
2456}
2457
2458/// Find the `xml:space` of a node.
2459///
2460/// # UPSTREAM-PARITY
2461///
2462/// ```c
2463/// int xmlNodeGetSpacePreserve(const xmlNode *cur);
2464/// ```
2465///
2466/// # SAFETY
2467///
2468/// - `cur` must be a valid pointer to an element node, or NULL.
2469#[no_mangle]
2470pub unsafe extern "C" fn xmlNodeGetSpacePreserve(cur: *const _xmlNode) -> c_int {
2471    if cur.is_null() || (*cur).type_ != XML_ELEMENT_NODE as c_int {
2472        return -1;
2473    }
2474    let mut c = cur;
2475    while !c.is_null() {
2476        let space = unsafe {
2477            get_attr_value(
2478                c as *mut _xmlNode,
2479                b"space\0".as_ptr() as *const xmlChar,
2480                XML_XML_NAMESPACE.as_ptr() as *const xmlChar,
2481            )
2482        };
2483        if !space.is_null() {
2484            if unsafe {
2485                crate::abi::exports_xml2::xmlStrEqual(
2486                    space,
2487                    b"preserve\0".as_ptr() as *const xmlChar,
2488                ) != 0
2489            } {
2490                unsafe { xmlFreeImpl(space as *mut c_void) };
2491                return 1;
2492            }
2493            if unsafe {
2494                crate::abi::exports_xml2::xmlStrEqual(
2495                    space,
2496                    b"default\0".as_ptr() as *const xmlChar,
2497                ) != 0
2498            } {
2499                unsafe { xmlFreeImpl(space as *mut c_void) };
2500                return 0;
2501            }
2502            unsafe { xmlFreeImpl(space as *mut c_void) };
2503        }
2504        c = (*c).parent;
2505    }
2506    -1
2507}
2508
2509/// Set (or reset) the name of a node.
2510///
2511/// # UPSTREAM-PARITY
2512///
2513/// ```c
2514/// void xmlNodeSetName(xmlNode *cur, const xmlChar *name);
2515/// ```
2516///
2517/// # SAFETY
2518///
2519/// - `cur` must be a valid pointer to an _xmlNode, or NULL.
2520#[no_mangle]
2521pub unsafe extern "C" fn xmlNodeSetName(cur: *mut _xmlNode, name: *const xmlChar) {
2522    if cur.is_null() || name.is_null() {
2523        return;
2524    }
2525    match (*cur).type_ as u32 {
2526        t if t == XML_ELEMENT_NODE as u32
2527            || t == XML_ATTRIBUTE_NODE as u32
2528            || t == XML_PI_NODE as u32
2529            || t == XML_ENTITY_REF_NODE as u32 => {}
2530        _ => return,
2531    }
2532    let copy = unsafe { dup_str(name) };
2533    if copy.is_null() {
2534        return;
2535    }
2536    let old = (*cur).name;
2537    (*cur).name = copy;
2538    if !old.is_null() {
2539        unsafe { xmlFreeImpl(old as *mut c_void) };
2540    }
2541}
2542
2543/// Set (or reset) the base URI of a node, i.e. the value of the
2544/// `xml:base` attribute.
2545///
2546/// # UPSTREAM-PARITY
2547///
2548/// ```c
2549/// int xmlNodeSetBase(xmlNode *cur, const xmlChar* uri);
2550/// ```
2551///
2552/// # SAFETY
2553///
2554/// - `cur` must be a valid pointer to an _xmlNode, or NULL.
2555#[no_mangle]
2556pub unsafe extern "C" fn xmlNodeSetBase(cur: *mut _xmlNode, uri: *const xmlChar) -> c_int {
2557    if cur.is_null() {
2558        return -1;
2559    }
2560    match (*cur).type_ as u32 {
2561        t if t == XML_ELEMENT_NODE as u32 || t == XML_ATTRIBUTE_NODE as u32 => {}
2562        t if t == XML_DOCUMENT_NODE as u32 || t == XML_HTML_DOCUMENT_NODE as u32 => {
2563            let doc = cur as *mut _xmlDoc;
2564            if !(*doc).URL.is_null() {
2565                unsafe { xmlFreeImpl((*doc).URL as *mut c_void) };
2566            }
2567            if uri.is_null() {
2568                (*doc).URL = ptr::null_mut();
2569            } else {
2570                (*doc).URL = unsafe { crate::abi::exports_uri::xmlPathToURI(uri as *const c_char) };
2571                if (*doc).URL.is_null() {
2572                    return -1;
2573                }
2574            }
2575            return 0;
2576        }
2577        _ => return -1,
2578    }
2579    let ns = unsafe { ensure_xml_decl((*cur).doc) };
2580    if ns.is_null() {
2581        return -1;
2582    }
2583    let fixed = unsafe { crate::abi::exports_uri::xmlPathToURI(uri as *const c_char) };
2584    if fixed.is_null() {
2585        return -1;
2586    }
2587    let attr = unsafe { set_ns_prop_impl(cur, ns, b"base\0".as_ptr() as *const xmlChar, fixed) };
2588    if attr.is_null() {
2589        unsafe { xmlFreeImpl(fixed as *mut c_void) };
2590        return -1;
2591    }
2592    unsafe { xmlFreeImpl(fixed as *mut c_void) };
2593    0
2594}
2595
2596/// Searches for the base URI of a node (RFC 2396 sections 5.1.1/5.1.2).
2597///
2598/// # UPSTREAM-PARITY
2599///
2600/// ```c
2601/// int xmlNodeGetBaseSafe(const xmlDoc *doc, const xmlNode *cur, xmlChar **baseOut);
2602/// ```
2603///
2604/// # SAFETY
2605///
2606/// - `doc`/`cur` must be valid pointers or NULL; `baseOut` must be valid.
2607#[no_mangle]
2608pub unsafe extern "C" fn xmlNodeGetBaseSafe(
2609    doc: *const _xmlDoc,
2610    cur: *const _xmlNode,
2611    baseOut: *mut *mut xmlChar,
2612) -> c_int {
2613    if baseOut.is_null() {
2614        return 1;
2615    }
2616    *baseOut = ptr::null_mut();
2617    if cur.is_null() && doc.is_null() {
2618        return 1;
2619    }
2620    if !cur.is_null() && (*cur).type_ == XML_NAMESPACE_DECL as c_int {
2621        return 1;
2622    }
2623    let mut doc = doc;
2624    if doc.is_null() {
2625        doc = (*cur).doc;
2626    }
2627    let mut ret: *mut xmlChar = ptr::null_mut();
2628
2629    if !doc.is_null() && (*doc).type_ == XML_HTML_DOCUMENT_NODE as c_int {
2630        let mut c = (*doc).children;
2631        while !c.is_null() {
2632            if (*c).type_ != XML_ELEMENT_NODE as c_int {
2633                c = (*c).next;
2634                continue;
2635            }
2636            if unsafe { strcasecmp_eq((*c).name, b"html") } {
2637                c = (*c).children;
2638                continue;
2639            }
2640            if unsafe { strcasecmp_eq((*c).name, b"head") } {
2641                c = (*c).children;
2642                continue;
2643            }
2644            if unsafe { strcasecmp_eq((*c).name, b"base") } {
2645                ret =
2646                    unsafe { get_attr_value(c, b"href\0".as_ptr() as *const xmlChar, ptr::null()) };
2647                if ret.is_null() {
2648                    return 1;
2649                }
2650                *baseOut = ret;
2651                return 0;
2652            }
2653            c = (*c).next;
2654        }
2655        return 0;
2656    }
2657
2658    let mut c = cur;
2659    while !c.is_null() {
2660        if (*c).type_ == XML_ENTITY_DECL as c_int {
2661            let ent = c as *const _xmlEntity as *mut _xmlEntity;
2662            if (*ent).URI.is_null() {
2663                break;
2664            }
2665            if !ret.is_null() {
2666                unsafe { xmlFreeImpl(ret as *mut c_void) };
2667            }
2668            ret = unsafe { dup_str((*ent).URI) };
2669            if ret.is_null() {
2670                return -1;
2671            }
2672            *baseOut = ret;
2673            return 0;
2674        }
2675        if (*c).type_ == XML_ELEMENT_NODE as c_int {
2676            let base = unsafe {
2677                get_attr_value(
2678                    c as *mut _xmlNode,
2679                    b"base\0".as_ptr() as *const xmlChar,
2680                    XML_XML_NAMESPACE.as_ptr() as *const xmlChar,
2681                )
2682            };
2683            if !base.is_null() {
2684                if !ret.is_null() {
2685                    let mut newbase: *mut xmlChar = ptr::null_mut();
2686                    let res = unsafe {
2687                        crate::abi::exports_uri::xmlBuildURISafe(
2688                            ret as *const c_char,
2689                            base as *const c_char,
2690                            &mut newbase,
2691                        )
2692                    };
2693                    unsafe { xmlFreeImpl(ret as *mut c_void) };
2694                    unsafe { xmlFreeImpl(base as *mut c_void) };
2695                    if res != 0 {
2696                        return res;
2697                    }
2698                    ret = newbase;
2699                } else {
2700                    ret = base;
2701                }
2702                if !ret.is_null()
2703                    && (unsafe { *ret } == b'h'
2704                        || unsafe { *ret } == b'f'
2705                        || unsafe { *ret } == b'u')
2706                {
2707                    if unsafe {
2708                        crate::abi::exports_xml2::xmlStrncmp(
2709                            ret,
2710                            b"http://\0".as_ptr() as *const xmlChar,
2711                            7,
2712                        ) == 0
2713                    } || unsafe {
2714                        crate::abi::exports_xml2::xmlStrncmp(
2715                            ret,
2716                            b"ftp://\0".as_ptr() as *const xmlChar,
2717                            6,
2718                        ) == 0
2719                    } || unsafe {
2720                        crate::abi::exports_xml2::xmlStrncmp(
2721                            ret,
2722                            b"urn:\0".as_ptr() as *const xmlChar,
2723                            4,
2724                        ) == 0
2725                    } {
2726                        *baseOut = ret;
2727                        return 0;
2728                    }
2729                }
2730            }
2731        }
2732        c = (*c).parent;
2733    }
2734
2735    if !doc.is_null() && !(*doc).URL.is_null() {
2736        if ret.is_null() {
2737            ret = unsafe { dup_str((*doc).URL) };
2738            if ret.is_null() {
2739                return -1;
2740            }
2741        } else {
2742            let mut newbase: *mut xmlChar = ptr::null_mut();
2743            let res = unsafe {
2744                crate::abi::exports_uri::xmlBuildURISafe(
2745                    ret as *const c_char,
2746                    (*doc).URL as *const c_char,
2747                    &mut newbase,
2748                )
2749            };
2750            unsafe { xmlFreeImpl(ret as *mut c_void) };
2751            if res != 0 {
2752                return res;
2753            }
2754            ret = newbase;
2755        }
2756    }
2757    *baseOut = ret;
2758    0
2759}
2760
2761/// See #xmlNodeGetBaseSafe. Returns NULL if no base is found (memory
2762/// allocation failures are indistinguishable).
2763///
2764/// # UPSTREAM-PARITY
2765///
2766/// ```c
2767/// xmlChar *xmlNodeGetBase(const xmlDoc *doc, const xmlNode *cur);
2768/// ```
2769///
2770/// # SAFETY
2771///
2772/// - `doc`/`cur` must be valid pointers or NULL.
2773#[no_mangle]
2774pub unsafe extern "C" fn xmlNodeGetBase(doc: *const _xmlDoc, cur: *const _xmlNode) -> *mut xmlChar {
2775    let mut base: *mut xmlChar = ptr::null_mut();
2776    unsafe { xmlNodeGetBaseSafe(doc, cur, &mut base) };
2777    base
2778}
2779
2780/// Check whether the node is a text node.
2781///
2782/// # UPSTREAM-PARITY
2783///
2784/// ```c
2785/// int xmlNodeIsText(const xmlNode *node);
2786/// ```
2787///
2788/// # SAFETY
2789///
2790/// - `node` must be a valid pointer to an _xmlNode, or NULL.
2791#[no_mangle]
2792pub unsafe extern "C" fn xmlNodeIsText(node: *const _xmlNode) -> c_int {
2793    if node.is_null() {
2794        return 0;
2795    }
2796    if (*node).type_ == XML_TEXT_NODE as c_int {
2797        1
2798    } else {
2799        0
2800    }
2801}
2802
2803// ═══════════════════════════════════════════════════════════════════════════════
2804// Tree manipulation: xmlSetTreeDoc, xmlAddNextSibling, xmlAddPrevSibling,
2805// xmlAddChildList, xmlReplaceNode
2806// ═══════════════════════════════════════════════════════════════════════════════
2807
2808/// Associate all nodes in a tree with a new document.
2809///
2810/// # UPSTREAM-PARITY
2811///
2812/// ```c
2813/// int xmlSetTreeDoc(xmlNode *tree, xmlDoc *doc);
2814/// ```
2815///
2816/// # SAFETY
2817///
2818/// - `tree` must be the root of an unlinked subtree, or NULL.
2819#[no_mangle]
2820pub unsafe extern "C" fn xmlSetTreeDoc(tree: *mut _xmlNode, doc: *mut _xmlDoc) -> c_int {
2821    unsafe { set_tree_doc_impl(tree, doc) }
2822}
2823
2824/// Unlink `cur` and insert it as next sibling after `prev`.
2825///
2826/// Unlike #xmlAddChild this function does not merge text nodes.
2827///
2828/// # UPSTREAM-PARITY
2829///
2830/// ```c
2831/// xmlNode *xmlAddNextSibling(xmlNode *prev, xmlNode *cur);
2832/// ```
2833///
2834/// # SAFETY
2835///
2836/// - `prev`/`cur` must be valid pointers to _xmlNode, or NULL.
2837#[no_mangle]
2838pub unsafe extern "C" fn xmlAddNextSibling(
2839    prev: *mut _xmlNode,
2840    cur: *mut _xmlNode,
2841) -> *mut _xmlNode {
2842    if prev.is_null()
2843        || (*prev).type_ == XML_NAMESPACE_DECL as c_int
2844        || cur.is_null()
2845        || (*cur).type_ == XML_NAMESPACE_DECL as c_int
2846        || cur == prev
2847    {
2848        return ptr::null_mut();
2849    }
2850    if cur == (*prev).next {
2851        return cur;
2852    }
2853    unsafe { insert_node((*prev).doc, cur, (*prev).parent, prev, (*prev).next, 0) }
2854}
2855
2856/// Unlink `cur` and insert it as previous sibling before `next`.
2857///
2858/// Unlike #xmlAddChild this function does not merge text nodes.
2859///
2860/// # UPSTREAM-PARITY
2861///
2862/// ```c
2863/// xmlNode *xmlAddPrevSibling(xmlNode *next, xmlNode *cur);
2864/// ```
2865///
2866/// # SAFETY
2867///
2868/// - `next`/`cur` must be valid pointers to _xmlNode, or NULL.
2869#[no_mangle]
2870pub unsafe extern "C" fn xmlAddPrevSibling(
2871    next: *mut _xmlNode,
2872    cur: *mut _xmlNode,
2873) -> *mut _xmlNode {
2874    if next.is_null()
2875        || (*next).type_ == XML_NAMESPACE_DECL as c_int
2876        || cur.is_null()
2877        || (*cur).type_ == XML_NAMESPACE_DECL as c_int
2878        || cur == next
2879    {
2880        return ptr::null_mut();
2881    }
2882    if cur == (*next).prev {
2883        return cur;
2884    }
2885    unsafe { insert_node((*next).doc, cur, (*next).parent, (*next).prev, next, 0) }
2886}
2887
2888/// Append a node list to another node.
2889///
2890/// # UPSTREAM-PARITY
2891///
2892/// ```c
2893/// xmlNode *xmlAddChildList(xmlNode *parent, xmlNode *cur);
2894/// ```
2895///
2896/// # SAFETY
2897///
2898/// - `parent`/`cur` must be valid pointers to _xmlNode, or NULL.
2899#[no_mangle]
2900pub unsafe extern "C" fn xmlAddChildList(
2901    parent: *mut _xmlNode,
2902    cur: *mut _xmlNode,
2903) -> *mut _xmlNode {
2904    if parent.is_null() || (*parent).type_ == XML_NAMESPACE_DECL as c_int {
2905        return ptr::null_mut();
2906    }
2907    if cur.is_null() || (*cur).type_ == XML_NAMESPACE_DECL as c_int {
2908        return ptr::null_mut();
2909    }
2910
2911    let mut oom = 0;
2912    let mut iter = cur;
2913    while !iter.is_null() {
2914        if (*iter).doc != (*parent).doc {
2915            if unsafe { set_tree_doc_impl(iter, (*parent).doc) } < 0 {
2916                oom = 1;
2917            }
2918        }
2919        iter = (*iter).next;
2920    }
2921    if oom != 0 {
2922        return ptr::null_mut();
2923    }
2924
2925    let mut cur = cur;
2926    if (*parent).children.is_null() {
2927        (*parent).children = cur;
2928    } else {
2929        let prev = (*parent).last;
2930        if (*cur).type_ == XML_TEXT_NODE as c_int
2931            && (*prev).type_ == XML_TEXT_NODE as c_int
2932            && unsafe { str_eq_or_ptr((*cur).name, (*prev).name) }
2933        {
2934            if unsafe { text_add_content(prev, (*cur).content, -1) } < 0 {
2935                return ptr::null_mut();
2936            }
2937            let next = (*cur).next;
2938            unsafe { tree::free_node(cur) };
2939            if next.is_null() {
2940                return prev;
2941            }
2942            cur = next;
2943        }
2944        (*prev).next = cur;
2945        (*cur).prev = prev;
2946    }
2947    while !(*cur).next.is_null() {
2948        (*cur).parent = parent;
2949        cur = (*cur).next;
2950    }
2951    (*cur).parent = parent;
2952    (*parent).last = cur;
2953    cur
2954}
2955
2956/// Unlink the old node; if `cur` is provided, it is unlinked and
2957/// inserted in place of `old`.
2958///
2959/// # UPSTREAM-PARITY
2960///
2961/// ```c
2962/// xmlNode *xmlReplaceNode(xmlNode *old, xmlNode *cur);
2963/// ```
2964///
2965/// # SAFETY
2966///
2967/// - `old`/`cur` must be valid pointers to _xmlNode, or NULL.
2968#[no_mangle]
2969pub unsafe extern "C" fn xmlReplaceNode(old: *mut _xmlNode, cur: *mut _xmlNode) -> *mut _xmlNode {
2970    if old == cur {
2971        return ptr::null_mut();
2972    }
2973    if old.is_null() || (*old).type_ == XML_NAMESPACE_DECL as c_int || (*old).parent.is_null() {
2974        return ptr::null_mut();
2975    }
2976    if cur.is_null() || (*cur).type_ == XML_NAMESPACE_DECL as c_int {
2977        // Don't route through xmlUnlinkNodeInternal to handle DTDs.
2978        unsafe { tree::unlink_node(old) };
2979        return old;
2980    }
2981    if (*old).type_ == XML_ATTRIBUTE_NODE as c_int && (*cur).type_ != XML_ATTRIBUTE_NODE as c_int {
2982        return old;
2983    }
2984    if (*cur).type_ == XML_ATTRIBUTE_NODE as c_int && (*old).type_ != XML_ATTRIBUTE_NODE as c_int {
2985        return old;
2986    }
2987    unsafe { tree::unlink_node(cur) };
2988    if unsafe { set_tree_doc_impl(cur, (*old).doc) } < 0 {
2989        return ptr::null_mut();
2990    }
2991    (*cur).parent = (*old).parent;
2992    (*cur).next = (*old).next;
2993    if !(*cur).next.is_null() {
2994        (*(*cur).next).prev = cur;
2995    }
2996    (*cur).prev = (*old).prev;
2997    if !(*cur).prev.is_null() {
2998        (*(*cur).prev).next = cur;
2999    }
3000    if !(*cur).parent.is_null() {
3001        if (*cur).type_ == XML_ATTRIBUTE_NODE as c_int {
3002            if (*(*cur).parent).properties == old as *mut _xmlAttr {
3003                (*(*cur).parent).properties = cur as *mut _xmlAttr;
3004            }
3005        } else {
3006            if (*(*cur).parent).children == old {
3007                (*(*cur).parent).children = cur;
3008            }
3009            if (*(*cur).parent).last == old {
3010                (*(*cur).parent).last = cur;
3011            }
3012        }
3013    }
3014    (*old).next = ptr::null_mut();
3015    (*old).prev = ptr::null_mut();
3016    (*old).parent = ptr::null_mut();
3017    old
3018}
3019
3020// ═══════════════════════════════════════════════════════════════════════════════
3021// Namespace list + reconciliation
3022// ═══════════════════════════════════════════════════════════════════════════════
3023
3024/// Find all in-scope namespaces of a node. `out` returns a NULL
3025/// terminated array of namespace pointers that must be freed by
3026/// the caller.
3027///
3028/// # UPSTREAM-PARITY
3029///
3030/// ```c
3031/// int xmlGetNsListSafe(const xmlDoc *doc, const xmlNode *node, xmlNs ***out);
3032/// ```
3033///
3034/// # SAFETY
3035///
3036/// - `out` must be a valid pointer; `node` must be valid or NULL.
3037#[no_mangle]
3038pub unsafe extern "C" fn xmlGetNsListSafe(
3039    doc: *const _xmlDoc,
3040    node: *const _xmlNode,
3041    out: *mut *mut *mut _xmlNs,
3042) -> c_int {
3043    let _ = doc;
3044    if out.is_null() {
3045        return 1;
3046    }
3047    *out = ptr::null_mut();
3048    if node.is_null() || (*node).type_ == XML_NAMESPACE_DECL as c_int {
3049        return 1;
3050    }
3051    let mut namespaces: *mut *mut _xmlNs = ptr::null_mut();
3052    let mut nbns: c_int = 0;
3053    let mut maxns: c_int = 0;
3054
3055    let mut n = node;
3056    while !n.is_null() {
3057        if (*n).type_ == XML_ELEMENT_NODE as c_int {
3058            let mut cur = (*n).nsDef;
3059            while !cur.is_null() {
3060                let mut i = 0;
3061                let mut found = false;
3062                while i < nbns {
3063                    if unsafe {
3064                        str_eq_or_ptr((*cur).prefix, (*(*namespaces).add(i as usize)).prefix)
3065                    } {
3066                        found = true;
3067                        break;
3068                    }
3069                    i += 1;
3070                }
3071                if !found {
3072                    if nbns >= maxns {
3073                        let new_size = if maxns <= 0 { 10 } else { maxns * 2 };
3074                        let tmp = unsafe {
3075                            xmlReallocImpl(
3076                                namespaces as *mut c_void,
3077                                ((new_size + 1) as usize) * size_of::<*mut _xmlNs>(),
3078                            )
3079                        } as *mut *mut _xmlNs;
3080                        if tmp.is_null() {
3081                            unsafe { xmlFreeImpl(namespaces as *mut c_void) };
3082                            return -1;
3083                        }
3084                        namespaces = tmp;
3085                        maxns = new_size;
3086                    }
3087                    *namespaces.add(nbns as usize) = cur;
3088                    nbns += 1;
3089                    *namespaces.add(nbns as usize) = ptr::null_mut();
3090                }
3091                cur = (*cur).next;
3092            }
3093        }
3094        n = (*n).parent;
3095    }
3096
3097    *out = namespaces;
3098    if namespaces.is_null() {
3099        1
3100    } else {
3101        0
3102    }
3103}
3104
3105/// Upstream `xmlNewReconciledNs` — locate a namespace definition in the tree
3106/// ancestors or create a new one similar to `ns`, reusing the prefix when
3107/// possible.
3108unsafe fn new_reconciled_ns(tree: *mut _xmlNode, ns: *mut _xmlNs) -> *mut _xmlNs {
3109    if tree.is_null() || (*tree).type_ != XML_ELEMENT_NODE as c_int {
3110        return ptr::null_mut();
3111    }
3112    if ns.is_null() || (*ns).type_ != XML_NAMESPACE_DECL as c_int {
3113        return ptr::null_mut();
3114    }
3115    // Search an existing namespace definition inherited.
3116    let def = unsafe { tree::search_ns_by_href(ptr::null_mut(), tree, (*ns).href) };
3117    if !def.is_null() {
3118        return def;
3119    }
3120    // Find a close prefix which is not already in use (strip > 20 chars).
3121    let prefix_base: Vec<u8> = if (*ns).prefix.is_null() {
3122        b"default\0".to_vec()
3123    } else {
3124        unsafe { str_prefix((*ns).prefix, 20) }
3125    };
3126    let mut counter: c_int = 1;
3127    let mut buf: Vec<u8> = prefix_base.clone();
3128    loop {
3129        let res = unsafe { tree::search_ns(ptr::null_mut(), tree, buf.as_ptr() as *const xmlChar) };
3130        if res.is_null() {
3131            break;
3132        }
3133        if counter > 1000 {
3134            return ptr::null_mut();
3135        }
3136        let base = &prefix_base[..prefix_base.len().saturating_sub(1)];
3137        let s = format!("{}{}", String::from_utf8_lossy(base), counter);
3138        buf = s.into_bytes();
3139        buf.push(0);
3140        counter += 1;
3141    }
3142    unsafe { tree::new_ns(tree, (*ns).href, buf.as_ptr() as *const xmlChar) }
3143}
3144
3145/// This function checks that all the namespaces declared within the given
3146/// tree are properly declared.
3147///
3148/// # UPSTREAM-PARITY
3149///
3150/// ```c
3151/// int xmlReconciliateNs(xmlDoc *doc, xmlNode *tree);
3152/// ```
3153///
3154/// # SAFETY
3155///
3156/// - `tree` must be an element node of `doc`, or NULL.
3157#[no_mangle]
3158pub unsafe extern "C" fn xmlReconciliateNs(doc: *mut _xmlDoc, tree: *mut _xmlNode) -> c_int {
3159    let mut cache: Vec<(*mut _xmlNs, *mut _xmlNs)> = Vec::new();
3160    let mut ret = 0;
3161
3162    if tree.is_null() || (*tree).type_ != XML_ELEMENT_NODE as c_int {
3163        return -1;
3164    }
3165    if (*tree).doc != doc {
3166        return -1;
3167    }
3168    let mut node = tree;
3169    loop {
3170        // Reconciliate the node namespace.
3171        if !(*node).ns.is_null() {
3172            let mut i = 0;
3173            let mut found = false;
3174            while i < cache.len() {
3175                if cache[i].0 == (*node).ns {
3176                    (*node).ns = cache[i].1;
3177                    found = true;
3178                    break;
3179                }
3180                i += 1;
3181            }
3182            if !found {
3183                let n = unsafe { new_reconciled_ns(tree, (*node).ns) };
3184                if n.is_null() {
3185                    ret = -1;
3186                } else {
3187                    cache.push(((*node).ns, n));
3188                }
3189                (*node).ns = n;
3190            }
3191        }
3192        // Check namespaces held by attributes.
3193        if (*node).type_ == XML_ELEMENT_NODE as c_int {
3194            let mut attr = (*node).properties;
3195            while !attr.is_null() {
3196                if !(*attr).ns.is_null() {
3197                    let mut i = 0;
3198                    let mut found = false;
3199                    while i < cache.len() {
3200                        if cache[i].0 == (*attr).ns {
3201                            (*attr).ns = cache[i].1;
3202                            found = true;
3203                            break;
3204                        }
3205                        i += 1;
3206                    }
3207                    if !found {
3208                        let n = unsafe { new_reconciled_ns(tree, (*attr).ns) };
3209                        if n.is_null() {
3210                            ret = -1;
3211                        } else {
3212                            cache.push(((*attr).ns, n));
3213                        }
3214                        (*attr).ns = n;
3215                    }
3216                }
3217                attr = (*attr).next;
3218            }
3219        }
3220        // Browse the full subtree, deep first.
3221        if !(*node).children.is_null() && (*node).type_ != XML_ENTITY_REF_NODE as c_int {
3222            node = (*node).children;
3223        } else if node != tree && !(*node).next.is_null() {
3224            node = (*node).next;
3225        } else if node != tree {
3226            // Go up to parents->next if needed (upstream `while (node != tree)`
3227            // re-checks the condition on every climb).
3228            loop {
3229                if node == tree {
3230                    break;
3231                }
3232                if !(*node).parent.is_null() {
3233                    node = (*node).parent;
3234                }
3235                if node != tree && !(*node).next.is_null() {
3236                    node = (*node).next;
3237                    break;
3238                }
3239                if (*node).parent.is_null() {
3240                    node = ptr::null_mut();
3241                    break;
3242                }
3243            }
3244            if node == tree {
3245                node = ptr::null_mut();
3246            }
3247        } else {
3248            break;
3249        }
3250        if node.is_null() {
3251            break;
3252        }
3253    }
3254    ret
3255}
3256
3257// ═══════════════════════════════════════════════════════════════════════════════
3258// Compression mode
3259// ═══════════════════════════════════════════════════════════════════════════════
3260
3261static XML_COMPRESS_MODE: AtomicI32 = AtomicI32::new(0);
3262
3263/// Get the global compression level, ZLIB based.
3264///
3265/// # UPSTREAM-PARITY
3266///
3267/// ```c
3268/// int xmlGetCompressMode(void);
3269/// ```
3270#[no_mangle]
3271pub unsafe extern "C" fn xmlGetCompressMode() -> c_int {
3272    XML_COMPRESS_MODE.load(Ordering::Relaxed)
3273}
3274
3275/// Set the global compression level, ZLIB based.
3276///
3277/// Correct values: 0 (uncompressed) to 9 (max compression)
3278///
3279/// # UPSTREAM-PARITY
3280///
3281/// ```c
3282/// void xmlSetCompressMode(int mode);
3283/// ```
3284#[no_mangle]
3285pub unsafe extern "C" fn xmlSetCompressMode(mode: c_int) {
3286    let m = if mode < 0 {
3287        0
3288    } else if mode > 9 {
3289        9
3290    } else {
3291        mode
3292    };
3293    XML_COMPRESS_MODE.store(m, Ordering::Relaxed);
3294}
3295
3296// ═══════════════════════════════════════════════════════════════════════════════
3297// DOM wrapper
3298// ═══════════════════════════════════════════════════════════════════════════════
3299
3300/// Allocates and initializes a new DOM-wrapper context.
3301///
3302/// # UPSTREAM-PARITY
3303///
3304/// ```c
3305/// xmlDOMWrapCtxt *xmlDOMWrapNewCtxt(void);
3306/// ```
3307#[no_mangle]
3308pub unsafe extern "C" fn xmlDOMWrapNewCtxt() -> *mut _xmlDOMWrapCtxt {
3309    unsafe { xmlMallocZero(size_of::<_xmlDOMWrapCtxt>()) as *mut _xmlDOMWrapCtxt }
3310}
3311
3312/// Frees the DOM-wrapper context.
3313///
3314/// # UPSTREAM-PARITY
3315///
3316/// ```c
3317/// void xmlDOMWrapFreeCtxt(xmlDOMWrapCtxt *ctxt);
3318/// ```
3319///
3320/// # SAFETY
3321///
3322/// - `ctxt` must be a valid pointer returned by xmlDOMWrapNewCtxt, or NULL.
3323#[no_mangle]
3324pub unsafe extern "C" fn xmlDOMWrapFreeCtxt(ctxt: *mut _xmlDOMWrapCtxt) {
3325    if ctxt.is_null() {
3326        return;
3327    }
3328    if !(*ctxt).namespaceMap.is_null() {
3329        unsafe { ns_map_free((*ctxt).namespaceMap as *mut NsMap) };
3330    }
3331    unsafe { xmlFreeImpl(ctxt as *mut c_void) };
3332}
3333
3334/// Unlinks the given node from its owner, substituting ns-references to
3335/// `node->nsDef` for ns-references to `doc->oldNs`.
3336///
3337/// # UPSTREAM-PARITY
3338///
3339/// ```c
3340/// int xmlDOMWrapRemoveNode(xmlDOMWrapCtxt *ctxt, xmlDoc *doc,
3341///                          xmlNode *node, int options);
3342/// ```
3343///
3344/// # SAFETY
3345///
3346/// - `doc`/`node` must be valid pointers.
3347#[no_mangle]
3348pub unsafe extern "C" fn xmlDOMWrapRemoveNode(
3349    ctxt: *mut _xmlDOMWrapCtxt,
3350    doc: *mut _xmlDoc,
3351    node: *mut _xmlNode,
3352    options: c_int,
3353) -> c_int {
3354    let _ = options;
3355    let mut list: *mut *mut _xmlNs = ptr::null_mut();
3356    let mut size_list: c_int = 0;
3357    let mut nb_list: c_int = 0;
3358    let mut ret = 0;
3359
3360    if node.is_null() || doc.is_null() || (*node).doc != doc {
3361        return -1;
3362    }
3363    if (*node).parent.is_null() {
3364        return 0;
3365    }
3366    match (*node).type_ as u32 {
3367        t if t == XML_TEXT_NODE as u32
3368            || t == XML_CDATA_SECTION_NODE as u32
3369            || t == XML_ENTITY_REF_NODE as u32
3370            || t == XML_PI_NODE as u32
3371            || t == XML_COMMENT_NODE as u32 =>
3372        {
3373            unsafe { tree::unlink_node(node) };
3374            return 0;
3375        }
3376        t if t == XML_ELEMENT_NODE as u32 || t == XML_ATTRIBUTE_NODE as u32 => {}
3377        _ => return 1,
3378    }
3379    unsafe { tree::unlink_node(node) };
3380
3381    let mut cur = node;
3382    'outer: loop {
3383        let node_type = (*cur).type_;
3384        match node_type as u32 {
3385            t if t == XML_ELEMENT_NODE as u32 => {
3386                if ctxt.is_null() && !(*cur).nsDef.is_null() {
3387                    let mut ns = (*cur).nsDef;
3388                    loop {
3389                        if unsafe {
3390                            ns_norm_add_ns_map_item2(
3391                                &mut list,
3392                                &mut size_list,
3393                                &mut nb_list,
3394                                ns,
3395                                ns,
3396                            )
3397                        } == -1
3398                        {
3399                            ret = -1;
3400                        }
3401                        if (*ns).next.is_null() {
3402                            break;
3403                        }
3404                        ns = (*ns).next;
3405                    }
3406                }
3407                if !(*cur).ns.is_null() {
3408                    let mut mapped = false;
3409                    if !list.is_null() {
3410                        let mut i = 0;
3411                        let mut j = 0;
3412                        while i < nb_list {
3413                            if (*cur).ns == *list.add(j as usize) {
3414                                (*cur).ns = *list.add((j + 1) as usize);
3415                                mapped = true;
3416                                break;
3417                            }
3418                            i += 1;
3419                            j += 2;
3420                        }
3421                    }
3422                    if !mapped {
3423                        let mut ns: *mut _xmlNs = ptr::null_mut();
3424                        if ctxt.is_null() {
3425                            ns = unsafe { store_ns(doc, (*(*cur).ns).href, (*(*cur).ns).prefix) };
3426                            if ns.is_null() {
3427                                ret = -1;
3428                            }
3429                        }
3430                        if !ns.is_null() {
3431                            if unsafe {
3432                                ns_norm_add_ns_map_item2(
3433                                    &mut list,
3434                                    &mut size_list,
3435                                    &mut nb_list,
3436                                    (*cur).ns,
3437                                    ns,
3438                                )
3439                            } == -1
3440                            {
3441                                ret = -1;
3442                            }
3443                        }
3444                        (*cur).ns = ns;
3445                    }
3446                }
3447                if !(*cur).properties.is_null() {
3448                    cur = (*cur).properties as *mut _xmlNode;
3449                    continue 'outer;
3450                }
3451            }
3452            t if t == XML_ATTRIBUTE_NODE as u32 => {
3453                if !(*cur).ns.is_null() {
3454                    let mut mapped = false;
3455                    if !list.is_null() {
3456                        let mut i = 0;
3457                        let mut j = 0;
3458                        while i < nb_list {
3459                            if (*cur).ns == *list.add(j as usize) {
3460                                (*cur).ns = *list.add((j + 1) as usize);
3461                                mapped = true;
3462                                break;
3463                            }
3464                            i += 1;
3465                            j += 2;
3466                        }
3467                    }
3468                    if !mapped {
3469                        let mut ns: *mut _xmlNs = ptr::null_mut();
3470                        if ctxt.is_null() {
3471                            ns = unsafe { store_ns(doc, (*(*cur).ns).href, (*(*cur).ns).prefix) };
3472                            if ns.is_null() {
3473                                ret = -1;
3474                            }
3475                        }
3476                        if !ns.is_null() {
3477                            if unsafe {
3478                                ns_norm_add_ns_map_item2(
3479                                    &mut list,
3480                                    &mut size_list,
3481                                    &mut nb_list,
3482                                    (*cur).ns,
3483                                    ns,
3484                                )
3485                            } == -1
3486                            {
3487                                ret = -1;
3488                            }
3489                        }
3490                        (*cur).ns = ns;
3491                    }
3492                }
3493            }
3494            _ => {}
3495        }
3496        // Descend into children.
3497        if node_type == XML_ELEMENT_NODE as c_int && !(*cur).children.is_null() {
3498            cur = (*cur).children;
3499            continue 'outer;
3500        }
3501        // Advance: next sibling, or up.
3502        loop {
3503            if cur.is_null() {
3504                break 'outer;
3505            }
3506            if !(*cur).next.is_null() {
3507                cur = (*cur).next;
3508                break;
3509            }
3510            let t = (*cur).type_;
3511            cur = (*cur).parent;
3512            if t == XML_ATTRIBUTE_NODE as c_int && !cur.is_null() && !(*cur).children.is_null() {
3513                cur = (*cur).children;
3514                break;
3515            }
3516            // else goto next_sibling again
3517            if cur.is_null() {
3518                break 'outer;
3519            }
3520        }
3521    }
3522
3523    if !list.is_null() {
3524        unsafe { xmlFreeImpl(list as *mut c_void) };
3525    }
3526    ret
3527}
3528
3529/// Fix up namespaces: ensures ns-references point to ns-decls held on
3530/// element-nodes and creates additional ns-decls where needed.
3531///
3532/// # UPSTREAM-PARITY
3533///
3534/// ```c
3535/// int xmlDOMWrapReconcileNamespaces(xmlDOMWrapCtxt *ctxt, xmlNode *elem, int options);
3536/// ```
3537///
3538/// # SAFETY
3539///
3540/// - `elem` must be an element node, or NULL.
3541#[no_mangle]
3542pub unsafe extern "C" fn xmlDOMWrapReconcileNamespaces(
3543    ctxt: *mut _xmlDOMWrapCtxt,
3544    elem: *mut _xmlNode,
3545    options: c_int,
3546) -> c_int {
3547    let _ = ctxt;
3548    let mut depth: c_int = -1;
3549    let mut adoptns: c_int = 0;
3550    let mut parnsdone: c_int = 0;
3551    let mut ns_map: *mut NsMap = ptr::null_mut();
3552    let ancestors_only: c_int = 0;
3553    let opt_remove_redundant = (options & XML_DOM_RECONNS_REMOVEREDUND) != 0;
3554    let mut list_redund: *mut *mut _xmlNs = ptr::null_mut();
3555    let mut size_redund: c_int = 0;
3556    let mut nb_redund: c_int = 0;
3557    let mut ret = 0;
3558
3559    if elem.is_null() || (*elem).doc.is_null() || (*elem).type_ != XML_ELEMENT_NODE as c_int {
3560        return -1;
3561    }
3562    let doc = (*elem).doc;
3563    let mut cur = elem;
3564    let mut cur_elem: *mut _xmlNode = ptr::null_mut();
3565
3566    'outer: loop {
3567        match (*cur).type_ as u32 {
3568            t if t == XML_ELEMENT_NODE as u32 => {
3569                adoptns = 1;
3570                cur_elem = cur;
3571                depth += 1;
3572                if !(*cur).nsDef.is_null() {
3573                    let mut prevns: *mut _xmlNs = ptr::null_mut();
3574                    let mut ns = (*cur).nsDef;
3575                    loop {
3576                        if parnsdone == 0 {
3577                            if !(*elem).parent.is_null()
3578                                && (*elem).parent != (*(*elem).parent).doc as *mut _xmlNode
3579                            {
3580                                if unsafe { gather_in_scope_ns(&mut ns_map, (*elem).parent) } == -1
3581                                {
3582                                    ret = -1;
3583                                }
3584                            }
3585                            parnsdone = 1;
3586                        }
3587                        // Remove redundant ns-decls.
3588                        if opt_remove_redundant && unsafe { ns_map_not_empty(ns_map) } {
3589                            let mut mi = (*ns_map).first;
3590                            let mut removed = false;
3591                            while !mi.is_null() {
3592                                if (*mi).depth >= XML_TREE_NSMAP_PARENT
3593                                    && (*mi).shadowDepth == -1
3594                                    && !(*mi).newNs.is_null()
3595                                    && unsafe { str_eq_or_ptr((*ns).prefix, (*(*mi).newNs).prefix) }
3596                                    && unsafe { str_eq_or_ptr((*ns).href, (*(*mi).newNs).href) }
3597                                {
3598                                    if unsafe {
3599                                        ns_norm_add_ns_map_item2(
3600                                            &mut list_redund,
3601                                            &mut size_redund,
3602                                            &mut nb_redund,
3603                                            ns,
3604                                            (*mi).newNs,
3605                                        )
3606                                    } == -1
3607                                    {
3608                                        ret = -1;
3609                                    } else {
3610                                        let next_ns = (*ns).next;
3611                                        if !prevns.is_null() {
3612                                            (*prevns).next = next_ns;
3613                                        } else {
3614                                            (*cur).nsDef = next_ns;
3615                                        }
3616                                        ns = next_ns;
3617                                        removed = true;
3618                                        break;
3619                                    }
3620                                }
3621                                mi = (*mi).next;
3622                            }
3623                            if removed {
3624                                if ns.is_null() {
3625                                    break;
3626                                }
3627                                continue;
3628                            }
3629                        }
3630                        // Skip ns-references handling if the referenced ns-decl
3631                        // is declared on the same element.
3632                        if !(*cur).ns.is_null() && adoptns != 0 && (*cur).ns == ns {
3633                            adoptns = 0;
3634                        }
3635                        // Does it shadow any ns-decl?
3636                        if unsafe { ns_map_not_empty(ns_map) } {
3637                            let mut mi = (*ns_map).first;
3638                            while !mi.is_null() {
3639                                if (*mi).depth >= XML_TREE_NSMAP_PARENT
3640                                    && (*mi).shadowDepth == -1
3641                                    && !(*mi).newNs.is_null()
3642                                    && unsafe { str_eq_or_ptr((*ns).prefix, (*(*mi).newNs).prefix) }
3643                                {
3644                                    (*mi).shadowDepth = depth;
3645                                }
3646                                mi = (*mi).next;
3647                            }
3648                        }
3649                        // Push mapping.
3650                        if unsafe { ns_map_add_item(&mut ns_map, -1, ns, ns, depth) }.is_null() {
3651                            ret = -1;
3652                        }
3653                        prevns = ns;
3654                        if (*ns).next.is_null() {
3655                            break;
3656                        }
3657                        ns = (*ns).next;
3658                    }
3659                }
3660                if adoptns == 0 {
3661                    // goto ns_end
3662                } else {
3663                    // Falls through to the ns-reference handling.
3664                    let _ = unsafe {
3665                        reconcile_ns_reference(
3666                            doc,
3667                            elem,
3668                            cur,
3669                            cur_elem,
3670                            &mut ns_map,
3671                            depth,
3672                            ancestors_only,
3673                            &mut ret,
3674                            &mut parnsdone,
3675                            &mut list_redund,
3676                            &mut size_redund,
3677                            &mut nb_redund,
3678                        )
3679                    };
3680                }
3681                // ns_end:
3682                if (*cur).type_ == XML_ELEMENT_NODE as c_int && !(*cur).properties.is_null() {
3683                    cur = (*cur).properties as *mut _xmlNode;
3684                    continue 'outer;
3685                }
3686            }
3687            t if t == XML_ATTRIBUTE_NODE as u32 => {
3688                let _ = unsafe {
3689                    reconcile_ns_reference(
3690                        doc,
3691                        elem,
3692                        cur,
3693                        cur_elem,
3694                        &mut ns_map,
3695                        depth,
3696                        ancestors_only,
3697                        &mut ret,
3698                        &mut parnsdone,
3699                        &mut list_redund,
3700                        &mut size_redund,
3701                        &mut nb_redund,
3702                    )
3703                };
3704            }
3705            _ => {
3706                // goto next_sibling
3707            }
3708        }
3709        // into_content
3710        if (*cur).type_ == XML_ELEMENT_NODE as c_int && !(*cur).children.is_null() {
3711            cur = (*cur).children;
3712            continue 'outer;
3713        }
3714        // next_sibling
3715        if cur == elem {
3716            break;
3717        }
3718        if (*cur).type_ == XML_ELEMENT_NODE as c_int {
3719            unsafe { ns_map_pop_depth(ns_map, depth) };
3720            depth -= 1;
3721        }
3722        if !(*cur).next.is_null() {
3723            cur = (*cur).next;
3724        } else {
3725            if (*cur).type_ == XML_ATTRIBUTE_NODE as c_int {
3726                cur = (*cur).parent;
3727                // goto into_content
3728                if (*cur).type_ == XML_ELEMENT_NODE as c_int && !(*cur).children.is_null() {
3729                    cur = (*cur).children;
3730                    continue 'outer;
3731                }
3732                // fall through to next_sibling
3733                if cur == elem {
3734                    break;
3735                }
3736                if (*cur).type_ == XML_ELEMENT_NODE as c_int {
3737                    unsafe { ns_map_pop_depth(ns_map, depth) };
3738                    depth -= 1;
3739                }
3740                if !(*cur).next.is_null() {
3741                    cur = (*cur).next;
3742                } else {
3743                    cur = (*cur).parent;
3744                    if cur.is_null() {
3745                        break;
3746                    }
3747                }
3748                continue 'outer;
3749            }
3750            cur = (*cur).parent;
3751            if cur.is_null() {
3752                break;
3753            }
3754            // goto next_sibling (loop around)
3755        }
3756    }
3757
3758    if !list_redund.is_null() {
3759        let mut i = 0;
3760        let mut j = 0;
3761        while i < nb_redund {
3762            unsafe { free_ns_impl(*list_redund.add(j as usize)) };
3763            i += 1;
3764            j += 2;
3765        }
3766        unsafe { xmlFreeImpl(list_redund as *mut c_void) };
3767    }
3768    if !ns_map.is_null() {
3769        unsafe { ns_map_free(ns_map) };
3770    }
3771    ret
3772}
3773
3774/// Shared ns-reference handling of xmlDOMWrapReconcileNamespaces (the
3775/// `XML_ATTRIBUTE_NODE` case body, also reached by element fall-through).
3776unsafe fn reconcile_ns_reference(
3777    doc: *mut _xmlDoc,
3778    elem: *mut _xmlNode,
3779    cur: *mut _xmlNode,
3780    cur_elem: *mut _xmlNode,
3781    ns_map: *mut *mut NsMap,
3782    depth: c_int,
3783    ancestors_only: c_int,
3784    ret: &mut c_int,
3785    parnsdone: &mut c_int,
3786    list_redund: *mut *mut *mut _xmlNs,
3787    _size_redund: *mut c_int,
3788    nb_redund: *mut c_int,
3789) -> c_int {
3790    if (*cur).ns.is_null() {
3791        return 0;
3792    }
3793    if *parnsdone == 0 {
3794        if !elem.is_null()
3795            && !(*elem).parent.is_null()
3796            && (*elem).parent != (*(*elem).parent).doc as *mut _xmlNode
3797        {
3798            if unsafe { gather_in_scope_ns(ns_map, (*elem).parent) } == -1 {
3799                *ret = -1;
3800            }
3801        }
3802        *parnsdone = 1;
3803    }
3804    // Adjust the reference if this was a redundant ns-decl.
3805    if !list_redund.is_null() && !(*list_redund).is_null() && *nb_redund > 0 {
3806        let list = *list_redund;
3807        let mut i = 0;
3808        let mut j = 0;
3809        while i < *nb_redund {
3810            if (*cur).ns == *list.add(j as usize) {
3811                (*cur).ns = *list.add((j + 1) as usize);
3812                return 0;
3813            }
3814            i += 1;
3815            j += 2;
3816        }
3817    }
3818    // Adopt ns-references.
3819    if unsafe { ns_map_not_empty(*ns_map) } {
3820        let mut mi = (*(*ns_map)).first;
3821        while !mi.is_null() {
3822            if (*mi).shadowDepth == -1 && (*cur).ns == (*mi).oldNs {
3823                (*cur).ns = (*mi).newNs;
3824                return 0;
3825            }
3826            mi = (*mi).next;
3827        }
3828    }
3829    // Acquire a normalized ns-decl and add it to the map.
3830    let mut ns: *mut _xmlNs = ptr::null_mut();
3831    if unsafe {
3832        acquire_normalized_ns(
3833            doc,
3834            cur_elem,
3835            (*cur).ns,
3836            &mut ns,
3837            ns_map,
3838            depth,
3839            ancestors_only,
3840            if (*cur).type_ == XML_ATTRIBUTE_NODE as c_int {
3841                1
3842            } else {
3843                0
3844            },
3845        )
3846    } == -1
3847    {
3848        *ret = -1;
3849    }
3850    (*cur).ns = ns;
3851    0
3852}
3853
3854/// Upstream `xmlDOMWrapAdoptBranch` — fix up namespaces and move `node` to
3855/// `destDoc`, declaring namespaces on `destParent` when given.
3856unsafe fn domwrap_adopt_branch(
3857    ctxt: *mut _xmlDOMWrapCtxt,
3858    node: *mut _xmlNode,
3859    dest_doc: *mut _xmlDoc,
3860    dest_parent: *mut _xmlNode,
3861) -> c_int {
3862    let mut ret = 0;
3863    let mut cur = node;
3864    let mut cur_elem: *mut _xmlNode = ptr::null_mut();
3865    let mut ns_map: *mut NsMap = ptr::null_mut();
3866    let mut ns: *mut _xmlNs = ptr::null_mut();
3867    let mut depth: c_int = -1;
3868    let mut parnsdone: c_int;
3869    let ancestors_only: c_int = 0;
3870    let mut leave = false;
3871
3872    if !ctxt.is_null() {
3873        ns_map = (*ctxt).namespaceMap as *mut NsMap;
3874    }
3875    // Disable search for ns-decls in the parent-axis of the destination
3876    // element if there's no destination parent or custom handling is used.
3877    if dest_parent.is_null() || (!ctxt.is_null() && (*ctxt).getNsForNodeFunc.is_some()) {
3878        parnsdone = 1;
3879    } else {
3880        parnsdone = 0;
3881    }
3882
3883    'outer: loop {
3884        if !leave {
3885            if (*cur).doc != dest_doc {
3886                if unsafe { node_set_doc_impl(cur, dest_doc) } < 0 {
3887                    ret = -1;
3888                }
3889            }
3890            match (*cur).type_ as u32 {
3891                t if t == XML_XINCLUDE_START as u32 || t == XML_XINCLUDE_END as u32 => {
3892                    ret = -1;
3893                    leave = true;
3894                }
3895                t if t == XML_ELEMENT_NODE as u32 => {
3896                    cur_elem = cur;
3897                    depth += 1;
3898                    if !(*cur).nsDef.is_null()
3899                        && (ctxt.is_null() || (*ctxt).getNsForNodeFunc.is_none())
3900                    {
3901                        if parnsdone == 0 {
3902                            if unsafe { gather_in_scope_ns(&mut ns_map, dest_parent) } == -1 {
3903                                ret = -1;
3904                            }
3905                            parnsdone = 1;
3906                        }
3907                        let mut ns2 = (*cur).nsDef;
3908                        loop {
3909                            if unsafe { ns_map_not_empty(ns_map) } {
3910                                let mut mi = (*ns_map).first;
3911                                while !mi.is_null() {
3912                                    if (*mi).depth >= XML_TREE_NSMAP_PARENT
3913                                        && (*mi).shadowDepth == -1
3914                                        && !(*mi).newNs.is_null()
3915                                        && unsafe {
3916                                            str_eq_or_ptr((*ns2).prefix, (*(*mi).newNs).prefix)
3917                                        }
3918                                    {
3919                                        (*mi).shadowDepth = depth;
3920                                    }
3921                                    mi = (*mi).next;
3922                                }
3923                            }
3924                            if unsafe { ns_map_add_item(&mut ns_map, -1, ns2, ns2, depth) }
3925                                .is_null()
3926                            {
3927                                ret = -1;
3928                            }
3929                            if (*ns2).next.is_null() {
3930                                break;
3931                            }
3932                            ns2 = (*ns2).next;
3933                        }
3934                    }
3935                    if !(*cur).ns.is_null() {
3936                        if parnsdone == 0 {
3937                            if unsafe { gather_in_scope_ns(&mut ns_map, dest_parent) } == -1 {
3938                                ret = -1;
3939                            }
3940                            parnsdone = 1;
3941                        }
3942                        let mut mapped = false;
3943                        if unsafe { ns_map_not_empty(ns_map) } {
3944                            let mut mi = (*ns_map).first;
3945                            while !mi.is_null() {
3946                                if (*mi).shadowDepth == -1 && (*cur).ns == (*mi).oldNs {
3947                                    (*cur).ns = (*mi).newNs;
3948                                    mapped = true;
3949                                    break;
3950                                }
3951                                mi = (*mi).next;
3952                            }
3953                        }
3954                        if !mapped {
3955                            if !ctxt.is_null() && (*ctxt).getNsForNodeFunc.is_some() {
3956                                let f = (*ctxt).getNsForNodeFunc.unwrap();
3957                                ns =
3958                                    unsafe { f(ctxt, cur, (*(*cur).ns).href, (*(*cur).ns).prefix) };
3959                                unsafe {
3960                                    ns_map_add_item(
3961                                        &mut ns_map,
3962                                        -1,
3963                                        (*cur).ns,
3964                                        ns,
3965                                        XML_TREE_NSMAP_CUSTOM,
3966                                    )
3967                                };
3968                                (*cur).ns = ns;
3969                            } else {
3970                                if unsafe {
3971                                    acquire_normalized_ns(
3972                                        dest_doc,
3973                                        if dest_parent.is_null() {
3974                                            ptr::null_mut()
3975                                        } else {
3976                                            cur_elem
3977                                        },
3978                                        (*cur).ns,
3979                                        &mut ns,
3980                                        &mut ns_map,
3981                                        depth,
3982                                        ancestors_only,
3983                                        0,
3984                                    )
3985                                } == -1
3986                                {
3987                                    ret = -1;
3988                                }
3989                                (*cur).ns = ns;
3990                            }
3991                        }
3992                    }
3993                    (*cur).psvi = ptr::null_mut();
3994                    (*cur).line = 0;
3995                    (*cur).extra = 0;
3996                    if !(*cur).properties.is_null() {
3997                        cur = (*cur).properties as *mut _xmlNode;
3998                        continue 'outer;
3999                    }
4000                }
4001                t if t == XML_ATTRIBUTE_NODE as u32 => {
4002                    if !(*cur).ns.is_null() {
4003                        if parnsdone == 0 {
4004                            if unsafe { gather_in_scope_ns(&mut ns_map, dest_parent) } == -1 {
4005                                ret = -1;
4006                            }
4007                            parnsdone = 1;
4008                        }
4009                        let mut mapped = false;
4010                        if unsafe { ns_map_not_empty(ns_map) } {
4011                            let mut mi = (*ns_map).first;
4012                            while !mi.is_null() {
4013                                if (*mi).shadowDepth == -1 && (*cur).ns == (*mi).oldNs {
4014                                    (*cur).ns = (*mi).newNs;
4015                                    mapped = true;
4016                                    break;
4017                                }
4018                                mi = (*mi).next;
4019                            }
4020                        }
4021                        if !mapped {
4022                            if !ctxt.is_null() && (*ctxt).getNsForNodeFunc.is_some() {
4023                                let f = (*ctxt).getNsForNodeFunc.unwrap();
4024                                ns =
4025                                    unsafe { f(ctxt, cur, (*(*cur).ns).href, (*(*cur).ns).prefix) };
4026                                unsafe {
4027                                    ns_map_add_item(
4028                                        &mut ns_map,
4029                                        -1,
4030                                        (*cur).ns,
4031                                        ns,
4032                                        XML_TREE_NSMAP_CUSTOM,
4033                                    )
4034                                };
4035                                (*cur).ns = ns;
4036                            } else {
4037                                if unsafe {
4038                                    acquire_normalized_ns(
4039                                        dest_doc,
4040                                        if dest_parent.is_null() {
4041                                            ptr::null_mut()
4042                                        } else {
4043                                            cur_elem
4044                                        },
4045                                        (*cur).ns,
4046                                        &mut ns,
4047                                        &mut ns_map,
4048                                        depth,
4049                                        ancestors_only,
4050                                        1,
4051                                    )
4052                                } == -1
4053                                {
4054                                    ret = -1;
4055                                }
4056                                (*cur).ns = ns;
4057                            }
4058                        }
4059                    }
4060                }
4061                t if t == XML_TEXT_NODE as u32
4062                    || t == XML_CDATA_SECTION_NODE as u32
4063                    || t == XML_PI_NODE as u32
4064                    || t == XML_COMMENT_NODE as u32
4065                    || t == XML_ENTITY_REF_NODE as u32 =>
4066                {
4067                    leave = true;
4068                }
4069                _ => {
4070                    ret = -1;
4071                    leave = true;
4072                }
4073            }
4074            if !leave {
4075                if !(*cur).children.is_null() {
4076                    cur = (*cur).children;
4077                    continue 'outer;
4078                }
4079                leave = true;
4080            }
4081        }
4082        // === leave_node ===
4083        leave = false;
4084        if cur == node {
4085            break;
4086        }
4087        if (*cur).type_ == XML_ELEMENT_NODE as c_int
4088            || (*cur).type_ == XML_XINCLUDE_START as c_int
4089            || (*cur).type_ == XML_XINCLUDE_END as c_int
4090        {
4091            unsafe { ns_map_pop_depth(ns_map, depth) };
4092            depth -= 1;
4093        }
4094        if !(*cur).next.is_null() {
4095            cur = (*cur).next;
4096        } else if (*cur).type_ == XML_ATTRIBUTE_NODE as c_int
4097            && !(*cur).parent.is_null()
4098            && !(*(*cur).parent).children.is_null()
4099        {
4100            cur = (*(*cur).parent).children;
4101        } else {
4102            cur = (*cur).parent;
4103            if cur.is_null() {
4104                break;
4105            }
4106            // goto leave_node
4107            leave = true;
4108        }
4109    }
4110
4111    if !ns_map.is_null() {
4112        if !ctxt.is_null() && (*ctxt).namespaceMap == ns_map as *mut c_void {
4113            // Just cleanup the map but don't free.
4114            if !(*ns_map).first.is_null() {
4115                if !(*ns_map).pool.is_null() {
4116                    (*(*ns_map).last).next = (*ns_map).pool;
4117                }
4118                (*ns_map).pool = (*ns_map).first;
4119                (*ns_map).first = ptr::null_mut();
4120            }
4121        } else {
4122            unsafe { ns_map_free(ns_map) };
4123        }
4124    }
4125    ret
4126}
4127
4128/// Upstream `xmlDOMWrapAdoptAttr`.
4129unsafe fn domwrap_adopt_attr(
4130    ctxt: *mut _xmlDOMWrapCtxt,
4131    attr: *mut _xmlAttr,
4132    dest_doc: *mut _xmlDoc,
4133    dest_parent: *mut _xmlNode,
4134) -> c_int {
4135    let _ = ctxt;
4136    let mut ret = 0;
4137    if attr.is_null() || dest_doc.is_null() {
4138        return -1;
4139    }
4140    if (*attr).doc != dest_doc {
4141        if unsafe { set_tree_doc_impl(attr as *mut _xmlNode, dest_doc) } < 0 {
4142            ret = -1;
4143        }
4144    }
4145    if !(*attr).ns.is_null() {
4146        let mut ns: *mut _xmlNs = ptr::null_mut();
4147        if unsafe { is_str_xml((*(*attr).ns).prefix) } {
4148            ns = unsafe { ensure_xml_decl(dest_doc) };
4149        } else if dest_parent.is_null() {
4150            ns = unsafe { store_ns(dest_doc, (*(*attr).ns).href, (*(*attr).ns).prefix) };
4151        } else {
4152            if unsafe {
4153                search_ns_by_namespace_strict(dest_doc, dest_parent, (*(*attr).ns).href, &mut ns, 1)
4154            } == -1
4155            {
4156                ret = -1;
4157            }
4158            if ns.is_null() {
4159                ns = unsafe {
4160                    declare_ns_forced(
4161                        dest_doc,
4162                        dest_parent,
4163                        (*(*attr).ns).href,
4164                        (*(*attr).ns).prefix,
4165                        1,
4166                    )
4167                };
4168            }
4169        }
4170        if ns.is_null() {
4171            ret = -1;
4172        }
4173        (*attr).ns = ns;
4174    }
4175    ret
4176}
4177
4178/// Fix up namespaces before moving a node.
4179///
4180/// # UPSTREAM-PARITY
4181///
4182/// ```c
4183/// int xmlDOMWrapAdoptNode(xmlDOMWrapCtxt *ctxt, xmlDoc *sourceDoc,
4184///                         xmlNode *node, xmlDoc *destDoc,
4185///                         xmlNode *destParent, int options);
4186/// ```
4187///
4188/// # SAFETY
4189///
4190/// - `node`/`destDoc` must be valid pointers.
4191#[no_mangle]
4192pub unsafe extern "C" fn xmlDOMWrapAdoptNode(
4193    ctxt: *mut _xmlDOMWrapCtxt,
4194    sourceDoc: *mut _xmlDoc,
4195    node: *mut _xmlNode,
4196    destDoc: *mut _xmlDoc,
4197    destParent: *mut _xmlNode,
4198    options: c_int,
4199) -> c_int {
4200    let _ = options;
4201    let mut ret = 0;
4202    if node.is_null()
4203        || (*node).type_ == XML_NAMESPACE_DECL as c_int
4204        || destDoc.is_null()
4205        || (!destParent.is_null() && (*destParent).doc != destDoc)
4206    {
4207        return -1;
4208    }
4209    let mut sourceDoc = sourceDoc;
4210    if sourceDoc.is_null() {
4211        sourceDoc = (*node).doc;
4212    } else if (*node).doc != sourceDoc {
4213        return -1;
4214    }
4215    if sourceDoc == destDoc {
4216        return -1;
4217    }
4218    match (*node).type_ as u32 {
4219        t if t == XML_ELEMENT_NODE as u32
4220            || t == XML_ATTRIBUTE_NODE as u32
4221            || t == XML_TEXT_NODE as u32
4222            || t == XML_CDATA_SECTION_NODE as u32
4223            || t == XML_ENTITY_REF_NODE as u32
4224            || t == XML_PI_NODE as u32
4225            || t == XML_COMMENT_NODE as u32 => {}
4226        t if t == XML_DOCUMENT_FRAG_NODE as u32 => return 2,
4227        _ => return 1,
4228    }
4229    // Unlink only if @node was not already added to @destParent.
4230    if !(*node).parent.is_null() && destParent != (*node).parent {
4231        unsafe { tree::unlink_node(node) };
4232    }
4233    if (*node).type_ == XML_ELEMENT_NODE as c_int {
4234        return unsafe { domwrap_adopt_branch(ctxt, node, destDoc, destParent) };
4235    } else if (*node).type_ == XML_ATTRIBUTE_NODE as c_int {
4236        return unsafe { domwrap_adopt_attr(ctxt, node as *mut _xmlAttr, destDoc, destParent) };
4237    } else {
4238        if (*node).doc != destDoc {
4239            if unsafe { node_set_doc_impl(node, destDoc) } < 0 {
4240                ret = -1;
4241            }
4242        }
4243    }
4244    ret
4245}
4246
4247/// Clone a node and fix namespaces.
4248///
4249/// # UPSTREAM-PARITY
4250///
4251/// ```c
4252/// int xmlDOMWrapCloneNode(xmlDOMWrapCtxt *ctxt, xmlDoc *sourceDoc,
4253///                         xmlNode *node, xmlNode **clonedNode,
4254///                         xmlDoc *destDoc, xmlNode *destParent,
4255///                         int deep, int options);
4256/// ```
4257///
4258/// # SAFETY
4259///
4260/// - `node`/`resNode`/`destDoc` must be valid pointers.
4261#[no_mangle]
4262pub unsafe extern "C" fn xmlDOMWrapCloneNode(
4263    ctxt: *mut _xmlDOMWrapCtxt,
4264    sourceDoc: *mut _xmlDoc,
4265    node: *mut _xmlNode,
4266    resNode: *mut *mut _xmlNode,
4267    destDoc: *mut _xmlDoc,
4268    destParent: *mut _xmlNode,
4269    deep: c_int,
4270    options: c_int,
4271) -> c_int {
4272    let _ = options;
4273    const ST_NORMAL: u8 = 0;
4274    const ST_INTO_CONTENT: u8 = 1;
4275    const ST_LEAVE: u8 = 2;
4276    let mut ret = 0;
4277    let mut cur = node;
4278    let mut clone_elem: *mut _xmlNode = ptr::null_mut();
4279    let mut ns_map: *mut NsMap = ptr::null_mut();
4280    let mut depth: c_int = -1;
4281    let mut parnsdone: c_int = 0;
4282    let ancestors_only: c_int = 0;
4283    let mut result_clone: *mut _xmlNode = ptr::null_mut();
4284    let mut clone: *mut _xmlNode = ptr::null_mut();
4285    let mut parent_clone: *mut _xmlNode = ptr::null_mut();
4286    let mut prev_clone: *mut _xmlNode = ptr::null_mut();
4287    let mut clone_ns: *mut _xmlNs = ptr::null_mut();
4288    let mut clone_ns_def_slot: *mut *mut _xmlNs = ptr::null_mut();
4289    let mut state: u8 = ST_NORMAL;
4290
4291    if node.is_null()
4292        || resNode.is_null()
4293        || destDoc.is_null()
4294        || (!destParent.is_null() && (*destParent).doc != destDoc)
4295    {
4296        return -1;
4297    }
4298    if (*node).type_ != XML_ELEMENT_NODE as c_int {
4299        return 1;
4300    }
4301    if !(*node).doc.is_null() && !sourceDoc.is_null() && (*node).doc != sourceDoc {
4302        return -1;
4303    }
4304    let mut sourceDoc = sourceDoc;
4305    if sourceDoc.is_null() {
4306        sourceDoc = (*node).doc;
4307    }
4308    if sourceDoc.is_null() {
4309        return -1;
4310    }
4311    if !ctxt.is_null() {
4312        ns_map = (*ctxt).namespaceMap as *mut NsMap;
4313    }
4314    *resNode = ptr::null_mut();
4315
4316    'outer: loop {
4317        if state == ST_NORMAL {
4318            if (*cur).doc != sourceDoc {
4319                // We'll assume XIncluded nodes if the doc differs.
4320                ret = -1;
4321                break;
4322            }
4323            let cur_type = (*cur).type_;
4324            match cur_type as u32 {
4325                t if t == XML_XINCLUDE_START as u32 || t == XML_XINCLUDE_END as u32 => {
4326                    ret = -1;
4327                    break;
4328                }
4329                t if t == XML_ELEMENT_NODE as u32
4330                    || t == XML_TEXT_NODE as u32
4331                    || t == XML_CDATA_SECTION_NODE as u32
4332                    || t == XML_COMMENT_NODE as u32
4333                    || t == XML_PI_NODE as u32
4334                    || t == XML_DOCUMENT_FRAG_NODE as u32
4335                    || t == XML_ENTITY_REF_NODE as u32 =>
4336                {
4337                    clone = unsafe { xmlMallocZero(size_of::<_xmlNode>()) } as *mut _xmlNode;
4338                    if clone.is_null() {
4339                        ret = -1;
4340                        break;
4341                    }
4342                    if !result_clone.is_null() {
4343                        (*clone).parent = parent_clone;
4344                        if !prev_clone.is_null() {
4345                            (*prev_clone).next = clone;
4346                            (*clone).prev = prev_clone;
4347                        } else {
4348                            (*parent_clone).children = clone;
4349                        }
4350                        (*parent_clone).last = clone;
4351                    } else {
4352                        result_clone = clone;
4353                    }
4354                }
4355                t if t == XML_ATTRIBUTE_NODE as u32 => {
4356                    clone = unsafe { xmlMallocZero(size_of::<_xmlAttr>()) } as *mut _xmlNode;
4357                    if clone.is_null() {
4358                        ret = -1;
4359                        break;
4360                    }
4361                    if !result_clone.is_null() {
4362                        (*clone).parent = parent_clone;
4363                        if !prev_clone.is_null() {
4364                            (*prev_clone).next = clone;
4365                            (*clone).prev = prev_clone;
4366                        } else {
4367                            (*parent_clone).properties = clone as *mut _xmlAttr;
4368                        }
4369                    } else {
4370                        result_clone = clone;
4371                    }
4372                }
4373                _ => {
4374                    ret = -1;
4375                    break;
4376                }
4377            }
4378            if ret == -1 {
4379                break;
4380            }
4381            (*clone).type_ = cur_type;
4382            (*clone).doc = destDoc;
4383            // Clone the name of the node if any.
4384            if !(*cur).name.is_null() {
4385                (*clone).name = unsafe { dup_str((*cur).name) };
4386                if (*clone).name.is_null() {
4387                    ret = -1;
4388                    break;
4389                }
4390            }
4391            match cur_type as u32 {
4392                t if t == XML_ELEMENT_NODE as u32 => {
4393                    clone_elem = clone;
4394                    depth += 1;
4395                    if !(*cur).nsDef.is_null() {
4396                        if parnsdone == 0 {
4397                            if !destParent.is_null() && ctxt.is_null() {
4398                                if unsafe { gather_in_scope_ns(&mut ns_map, destParent) } == -1 {
4399                                    ret = -1;
4400                                    break;
4401                                }
4402                            }
4403                            parnsdone = 1;
4404                        }
4405                        // Clone namespace declarations.
4406                        clone_ns_def_slot = core::ptr::addr_of_mut!((*clone).nsDef);
4407                        let mut ns = (*cur).nsDef;
4408                        loop {
4409                            clone_ns = unsafe { xmlMallocZero(size_of::<_xmlNs>()) } as *mut _xmlNs;
4410                            if clone_ns.is_null() {
4411                                ret = -1;
4412                                break;
4413                            }
4414                            (*clone_ns).type_ = XML_LOCAL_NAMESPACE as c_int;
4415                            if !(*ns).href.is_null() {
4416                                (*clone_ns).href = unsafe { dup_str((*ns).href) };
4417                                if (*clone_ns).href.is_null() {
4418                                    unsafe { free_ns_impl(clone_ns) };
4419                                    ret = -1;
4420                                    break;
4421                                }
4422                            }
4423                            if !(*ns).prefix.is_null() {
4424                                (*clone_ns).prefix = unsafe { dup_str((*ns).prefix) };
4425                                if (*clone_ns).prefix.is_null() {
4426                                    unsafe { free_ns_impl(clone_ns) };
4427                                    ret = -1;
4428                                    break;
4429                                }
4430                            }
4431                            *clone_ns_def_slot = clone_ns;
4432                            clone_ns_def_slot = core::ptr::addr_of_mut!((*clone_ns).next);
4433                            if ctxt.is_null() || (*ctxt).getNsForNodeFunc.is_none() {
4434                                if unsafe { ns_map_not_empty(ns_map) } {
4435                                    let mut mi = (*ns_map).first;
4436                                    while !mi.is_null() {
4437                                        if (*mi).depth >= XML_TREE_NSMAP_PARENT
4438                                            && (*mi).shadowDepth == -1
4439                                            && !(*mi).newNs.is_null()
4440                                            && unsafe {
4441                                                str_eq_or_ptr((*ns).prefix, (*(*mi).newNs).prefix)
4442                                            }
4443                                        {
4444                                            (*mi).shadowDepth = depth;
4445                                        }
4446                                        mi = (*mi).next;
4447                                    }
4448                                }
4449                                if unsafe { ns_map_add_item(&mut ns_map, -1, ns, clone_ns, depth) }
4450                                    .is_null()
4451                                {
4452                                    ret = -1;
4453                                    break;
4454                                }
4455                            }
4456                            if (*ns).next.is_null() {
4457                                break;
4458                            }
4459                            ns = (*ns).next;
4460                        }
4461                        if ret == -1 {
4462                            break;
4463                        }
4464                    }
4465                }
4466                t if t == XML_PI_NODE as u32
4467                    || t == XML_COMMENT_NODE as u32
4468                    || t == XML_TEXT_NODE as u32
4469                    || t == XML_CDATA_SECTION_NODE as u32 =>
4470                {
4471                    if !(*cur).content.is_null() {
4472                        (*clone).content = unsafe { dup_str((*cur).content) };
4473                        if (*clone).content.is_null() {
4474                            ret = -1;
4475                            break;
4476                        }
4477                    }
4478                    state = ST_LEAVE;
4479                    continue 'outer;
4480                }
4481                t if t == XML_ENTITY_REF_NODE as u32 => {
4482                    if sourceDoc != destDoc {
4483                        if !(*destDoc).intSubset.is_null() || !(*destDoc).extSubset.is_null() {
4484                            let ent = unsafe { tree::get_doc_entity(destDoc, (*cur).name) };
4485                            if !ent.is_null() {
4486                                (*clone).content = (*ent).content;
4487                                (*clone).children = ent as *mut _xmlNode;
4488                                (*clone).last = ent as *mut _xmlNode;
4489                            }
4490                        }
4491                    } else {
4492                        (*clone).content = (*cur).content;
4493                        (*clone).children = (*cur).children;
4494                        (*clone).last = (*cur).last;
4495                    }
4496                    state = ST_LEAVE;
4497                    continue 'outer;
4498                }
4499                _ => {
4500                    ret = -1;
4501                    break;
4502                }
4503            }
4504            if ret == -1 {
4505                break;
4506            }
4507            // ns-reference handling (element and attribute nodes).
4508            if !(*cur).ns.is_null() {
4509                if parnsdone == 0 {
4510                    if !destParent.is_null() && ctxt.is_null() {
4511                        if unsafe { gather_in_scope_ns(&mut ns_map, destParent) } == -1 {
4512                            ret = -1;
4513                            break;
4514                        }
4515                    }
4516                    parnsdone = 1;
4517                }
4518                let mut mapped = false;
4519                if unsafe { ns_map_not_empty(ns_map) } {
4520                    let mut mi = (*ns_map).first;
4521                    while !mi.is_null() {
4522                        if (*mi).shadowDepth == -1 && (*cur).ns == (*mi).oldNs {
4523                            (*clone).ns = (*mi).newNs;
4524                            mapped = true;
4525                            break;
4526                        }
4527                        mi = (*mi).next;
4528                    }
4529                }
4530                if !mapped {
4531                    if !ctxt.is_null() && (*ctxt).getNsForNodeFunc.is_some() {
4532                        let f = (*ctxt).getNsForNodeFunc.unwrap();
4533                        let ns = unsafe { f(ctxt, cur, (*(*cur).ns).href, (*(*cur).ns).prefix) };
4534                        unsafe {
4535                            ns_map_add_item(&mut ns_map, -1, (*cur).ns, ns, XML_TREE_NSMAP_CUSTOM)
4536                        };
4537                        (*clone).ns = ns;
4538                    } else {
4539                        let mut ns: *mut _xmlNs = ptr::null_mut();
4540                        if unsafe {
4541                            acquire_normalized_ns(
4542                                destDoc,
4543                                if destParent.is_null() {
4544                                    ptr::null_mut()
4545                                } else {
4546                                    clone_elem
4547                                },
4548                                (*cur).ns,
4549                                &mut ns,
4550                                &mut ns_map,
4551                                depth,
4552                                ancestors_only,
4553                                if (*cur).type_ == XML_ATTRIBUTE_NODE as c_int {
4554                                    1
4555                                } else {
4556                                    0
4557                                },
4558                            )
4559                        } == -1
4560                        {
4561                            ret = -1;
4562                            break;
4563                        }
4564                        (*clone).ns = ns;
4565                    }
4566                }
4567            }
4568            // Walk the element's attributes before descending into child-nodes.
4569            if (*cur).type_ == XML_ELEMENT_NODE as c_int && !(*cur).properties.is_null() {
4570                prev_clone = ptr::null_mut();
4571                parent_clone = clone;
4572                cur = (*cur).properties as *mut _xmlNode;
4573                continue 'outer; // state stays ST_NORMAL
4574            }
4575            state = ST_INTO_CONTENT;
4576            continue 'outer;
4577        } else if state == ST_INTO_CONTENT {
4578            if !(*cur).children.is_null() {
4579                if deep != 0 || (*cur).type_ == XML_ATTRIBUTE_NODE as c_int {
4580                    prev_clone = ptr::null_mut();
4581                    parent_clone = clone;
4582                    cur = (*cur).children;
4583                    state = ST_NORMAL;
4584                    continue 'outer;
4585                }
4586            }
4587            state = ST_LEAVE;
4588        }
4589        // === leave_node ===
4590        if cur == node {
4591            break;
4592        }
4593        if (*cur).type_ == XML_ELEMENT_NODE as c_int
4594            || (*cur).type_ == XML_XINCLUDE_START as c_int
4595            || (*cur).type_ == XML_XINCLUDE_END as c_int
4596        {
4597            unsafe { ns_map_pop_depth(ns_map, depth) };
4598            depth -= 1;
4599        }
4600        if !(*cur).next.is_null() {
4601            prev_clone = clone;
4602            cur = (*cur).next;
4603            state = ST_NORMAL;
4604        } else if (*cur).type_ != XML_ATTRIBUTE_NODE as c_int {
4605            clone = (*clone).parent;
4606            if !clone.is_null() {
4607                parent_clone = (*clone).parent;
4608            }
4609            cur = (*cur).parent;
4610            if cur.is_null() {
4611                break;
4612            }
4613            // goto leave_node
4614            state = ST_LEAVE;
4615        } else {
4616            // This is for attributes only.
4617            clone = (*clone).parent;
4618            parent_clone = (*clone).parent;
4619            cur = (*cur).parent;
4620            if cur.is_null() {
4621                break;
4622            }
4623            // goto into_content
4624            state = ST_INTO_CONTENT;
4625        }
4626    }
4627
4628    // Cleanup.
4629    if !ns_map.is_null() {
4630        if !ctxt.is_null() && (*ctxt).namespaceMap == ns_map as *mut c_void {
4631            // Just cleanup the map but don't free.
4632            if !(*ns_map).first.is_null() {
4633                if !(*ns_map).pool.is_null() {
4634                    (*(*ns_map).last).next = (*ns_map).pool;
4635                }
4636                (*ns_map).pool = (*ns_map).first;
4637                (*ns_map).first = ptr::null_mut();
4638            }
4639        } else {
4640            unsafe { ns_map_free(ns_map) };
4641        }
4642    }
4643    *resNode = result_clone;
4644    ret
4645}
4646
4647// ═══════════════════════════════════════════════════════════════════════════════
4648// Entities tables
4649// ═══════════════════════════════════════════════════════════════════════════════
4650
4651/// Create an empty entities hash table.
4652///
4653/// # UPSTREAM-PARITY
4654///
4655/// ```c
4656/// xmlEntitiesTable *xmlCreateEntitiesTable(void);
4657/// ```
4658#[no_mangle]
4659pub unsafe extern "C" fn xmlCreateEntitiesTable() -> *mut c_void {
4660    hash::hash_create(0) as *mut c_void
4661}
4662
4663unsafe extern "C" fn free_entity_wrapper(payload: *mut c_void, _name: *mut xmlChar) {
4664    if !payload.is_null() {
4665        unsafe { entities::free_entity(payload as *mut _xmlEntity) };
4666    }
4667}
4668
4669/// Free an entities hash table.
4670///
4671/// # UPSTREAM-PARITY
4672///
4673/// ```c
4674/// void xmlFreeEntitiesTable(xmlEntitiesTable *table);
4675/// ```
4676///
4677/// # SAFETY
4678///
4679/// - `table` must be a valid entities table or NULL.
4680#[no_mangle]
4681pub unsafe extern "C" fn xmlFreeEntitiesTable(table: *mut c_void) {
4682    unsafe { hash::hash_free(table as *mut hash::HashTable, Some(free_entity_wrapper)) };
4683}
4684
4685/// Create a new input stream based on an xmlEntity.
4686///
4687/// # UPSTREAM-PARITY
4688///
4689/// ```c
4690/// xmlParserInput *xmlNewEntityInputStream(xmlParserCtxt *ctxt, xmlEntity *entity);
4691/// ```
4692///
4693/// # SAFETY
4694///
4695/// - `ctxt`/`entity` must be valid pointers or NULL.
4696#[no_mangle]
4697pub unsafe extern "C" fn xmlNewEntityInputStream(
4698    ctxt: *mut _xmlParserCtxt,
4699    entity: *mut _xmlEntity,
4700) -> *mut _xmlParserInput {
4701    if ctxt.is_null() || entity.is_null() {
4702        return ptr::null_mut();
4703    }
4704    if !(*entity).content.is_null() {
4705        let input = unsafe { xmlMallocZero(size_of::<_xmlParserInput>()) } as *mut _xmlParserInput;
4706        if input.is_null() {
4707            return ptr::null_mut();
4708        }
4709        let len = unsafe { str_len((*entity).content) } as usize;
4710        (*input).line = 1;
4711        (*input).col = 1;
4712        (*input).base = (*entity).content;
4713        (*input).cur = (*entity).content;
4714        (*input).end = (*entity).content.add(len);
4715        (*input).length = len as c_int;
4716        (*input).entity = entity;
4717        (*input).buf = ptr::null_mut();
4718        return input;
4719    }
4720    // External entities require the resource loader (xmlLoadResource); not
4721    // ported here — upstream returns NULL in this case as well when the
4722    // resource cannot be loaded.
4723    if !(*entity).URI.is_null() {
4724        return ptr::null_mut();
4725    }
4726    ptr::null_mut()
4727}
4728
4729// ═══════════════════════════════════════════════════════════════════════════════
4730// valid.h: enumerations and content models
4731// ═══════════════════════════════════════════════════════════════════════════════
4732
4733/// Create an enumeration value.
4734///
4735/// # UPSTREAM-PARITY
4736///
4737/// ```c
4738/// xmlEnumeration *xmlCreateEnumeration(const xmlChar *name);
4739/// ```
4740///
4741/// # SAFETY
4742///
4743/// - `name` must be a valid null-terminated string or NULL.
4744#[no_mangle]
4745pub unsafe extern "C" fn xmlCreateEnumeration(name: *const xmlChar) -> *mut _xmlEnumeration {
4746    let ret = unsafe { xmlMallocZero(size_of::<_xmlEnumeration>()) } as *mut _xmlEnumeration;
4747    if ret.is_null() {
4748        return ptr::null_mut();
4749    }
4750    if !name.is_null() {
4751        (*ret).name = unsafe { dup_str(name) };
4752        if (*ret).name.is_null() {
4753            unsafe { xmlFreeImpl(ret as *mut c_void) };
4754            return ptr::null_mut();
4755        }
4756    }
4757    ret
4758}
4759
4760/// Free an enumeration list.
4761///
4762/// # UPSTREAM-PARITY
4763///
4764/// ```c
4765/// void xmlFreeEnumeration(xmlEnumeration *cur);
4766/// ```
4767///
4768/// # SAFETY
4769///
4770/// - `cur` must be a valid enumeration or NULL.
4771#[no_mangle]
4772pub unsafe extern "C" fn xmlFreeEnumeration(cur: *mut _xmlEnumeration) {
4773    let mut c = cur;
4774    while !c.is_null() {
4775        let next = (*c).next;
4776        if !(*c).name.is_null() {
4777            unsafe { xmlFreeImpl((*c).name as *mut c_void) };
4778        }
4779        unsafe { xmlFreeImpl(c as *mut c_void) };
4780        c = next;
4781    }
4782}
4783
4784/// Free a content model tree (iterative; shared by xmlFreeDocElementContent).
4785unsafe fn free_elem_content_internal(cur: *mut _xmlElementContent) {
4786    if cur.is_null() {
4787        return;
4788    }
4789    let mut depth: usize = 0;
4790    let mut cur = cur;
4791    loop {
4792        while !(*cur).c1.is_null() || !(*cur).c2.is_null() {
4793            cur = if !(*cur).c1.is_null() {
4794                (*cur).c1
4795            } else {
4796                (*cur).c2
4797            };
4798            depth += 1;
4799        }
4800        if !(*cur).name.is_null() {
4801            unsafe { xmlFreeImpl((*cur).name as *mut c_void) };
4802        }
4803        if !(*cur).prefix.is_null() {
4804            unsafe { xmlFreeImpl((*cur).prefix as *mut c_void) };
4805        }
4806        let parent = (*cur).parent;
4807        if depth == 0 || parent.is_null() {
4808            unsafe { xmlFreeImpl(cur as *mut c_void) };
4809            break;
4810        }
4811        if cur == (*parent).c1 {
4812            (*parent).c1 = ptr::null_mut();
4813        } else {
4814            (*parent).c2 = ptr::null_mut();
4815        }
4816        unsafe { xmlFreeImpl(cur as *mut c_void) };
4817        if !(*parent).c2.is_null() {
4818            cur = (*parent).c2;
4819        } else {
4820            depth -= 1;
4821            cur = parent;
4822        }
4823    }
4824}
4825
4826/// Free an element content model.
4827///
4828/// # UPSTREAM-PARITY
4829///
4830/// ```c
4831/// void xmlFreeDocElementContent(xmlDoc *doc, xmlElementContent *cur);
4832/// ```
4833///
4834/// # SAFETY
4835///
4836/// - `cur` must be a valid content model or NULL.
4837#[no_mangle]
4838pub unsafe extern "C" fn xmlFreeDocElementContent(doc: *mut _xmlDoc, cur: *mut _xmlElementContent) {
4839    let _ = doc;
4840    unsafe { free_elem_content_internal(cur) };
4841}
4842
4843// ═══════════════════════════════════════════════════════════════════════════════
4844// valid.h: declaration tables
4845// ═══════════════════════════════════════════════════════════════════════════════
4846
4847unsafe extern "C" fn free_attribute_table_entry(payload: *mut c_void, _name: *mut xmlChar) {
4848    if !payload.is_null() {
4849        unsafe { crate::xml::dtd::free_attribute(payload as *mut _xmlAttribute) };
4850    }
4851}
4852
4853/// Free an attribute declaration table.
4854///
4855/// # UPSTREAM-PARITY
4856///
4857/// ```c
4858/// void xmlFreeAttributeTable(xmlAttributeTable *table);
4859/// ```
4860///
4861/// # SAFETY
4862///
4863/// - `table` must be a valid attribute table or NULL.
4864#[no_mangle]
4865pub unsafe extern "C" fn xmlFreeAttributeTable(table: *mut c_void) {
4866    unsafe {
4867        hash::hash_free(
4868            table as *mut hash::HashTable,
4869            Some(free_attribute_table_entry),
4870        )
4871    };
4872}
4873
4874unsafe extern "C" fn free_element_table_entry(payload: *mut c_void, _name: *mut xmlChar) {
4875    if !payload.is_null() {
4876        unsafe { crate::xml::dtd::free_element(payload as *mut _xmlElement) };
4877    }
4878}
4879
4880/// Free an element declaration table.
4881///
4882/// # UPSTREAM-PARITY
4883///
4884/// ```c
4885/// void xmlFreeElementTable(xmlElementTable *table);
4886/// ```
4887///
4888/// # SAFETY
4889///
4890/// - `table` must be a valid element table or NULL.
4891#[no_mangle]
4892pub unsafe extern "C" fn xmlFreeElementTable(table: *mut c_void) {
4893    unsafe {
4894        hash::hash_free(
4895            table as *mut hash::HashTable,
4896            Some(free_element_table_entry),
4897        )
4898    };
4899}
4900
4901unsafe extern "C" fn free_notation_table_entry(payload: *mut c_void, _name: *mut xmlChar) {
4902    if !payload.is_null() {
4903        unsafe { crate::xml::dtd::free_notation(payload as *mut _xmlNotation) };
4904    }
4905}
4906
4907/// Free a notation declaration table.
4908///
4909/// # UPSTREAM-PARITY
4910///
4911/// ```c
4912/// void xmlFreeNotationTable(xmlNotationTable *table);
4913/// ```
4914///
4915/// # SAFETY
4916///
4917/// - `table` must be a valid notation table or NULL.
4918#[no_mangle]
4919pub unsafe extern "C" fn xmlFreeNotationTable(table: *mut c_void) {
4920    unsafe {
4921        hash::hash_free(
4922            table as *mut hash::HashTable,
4923            Some(free_notation_table_entry),
4924        )
4925    };
4926}
4927
4928// ═══════════════════════════════════════════════════════════════════════════════
4929// xmlwriter.h
4930// ═══════════════════════════════════════════════════════════════════════════════
4931
4932unsafe extern "C" fn writer_push_write_cb(
4933    ctx: *mut c_void,
4934    buffer: *const c_char,
4935    len: c_int,
4936) -> c_int {
4937    let ctxt = ctx as *mut _xmlParserCtxt;
4938    if ctxt.is_null() || buffer.is_null() {
4939        return -1;
4940    }
4941    let rc = unsafe { crate::abi::exports_xml2::xmlParseChunk(ctxt, buffer, len, 0) };
4942    if rc != 0 {
4943        return -1;
4944    }
4945    len
4946}
4947
4948unsafe extern "C" fn writer_push_close_cb(ctx: *mut c_void) -> c_int {
4949    let ctxt = ctx as *mut _xmlParserCtxt;
4950    if ctxt.is_null() {
4951        return -1;
4952    }
4953    let rc = unsafe { crate::abi::exports_xml2::xmlParseChunk(ctxt, ptr::null(), 0, 1) };
4954    if rc != 0 {
4955        return -1;
4956    }
4957    0
4958}
4959
4960/// Create a new xmlTextWriter structure with `ctxt` as output.
4961///
4962/// NOTE: the `ctxt` context will be freed with the resulting writer
4963/// (if the call succeeds).
4964///
4965/// # UPSTREAM-PARITY
4966///
4967/// ```c
4968/// xmlTextWriter *xmlNewTextWriterPushParser(xmlParserCtxt *ctxt, int compression);
4969/// ```
4970///
4971/// # SAFETY
4972///
4973/// - `ctxt` must be a valid parser context.
4974#[no_mangle]
4975pub unsafe extern "C" fn xmlNewTextWriterPushParser(
4976    ctxt: *mut _xmlParserCtxt,
4977    compression: c_int,
4978) -> *mut crate::xml::writer::XmlTextWriter {
4979    let _ = compression;
4980    if ctxt.is_null() {
4981        return ptr::null_mut();
4982    }
4983    let out = crate::xml::io::output_buffer_create_io(
4984        Some(writer_push_write_cb),
4985        Some(writer_push_close_cb),
4986        ctxt as *mut c_void,
4987        ptr::null_mut(),
4988    );
4989    if out.is_null() {
4990        return ptr::null_mut();
4991    }
4992    let ret = crate::xml::writer::xmlNewTextWriter(out);
4993    if ret.is_null() {
4994        crate::xml::io::output_buffer_close(out);
4995        return ptr::null_mut();
4996    }
4997    ret
4998}
4999
5000// ═══════════════════════════════════════════════════════════════════════════════
5001// Legacy
5002// ═══════════════════════════════════════════════════════════════════════════════
5003
5004/// Creation of a Namespace, the old way using PI and without scoping.
5005///
5006/// DEPRECATED: the functionality was removed upstream; this returns NULL.
5007///
5008/// # UPSTREAM-PARITY
5009///
5010/// ```c
5011/// xmlNsPtr xmlNewGlobalNs(xmlDocPtr doc, const xmlChar *href, const xmlChar *prefix);
5012/// ```
5013///
5014/// # SAFETY
5015///
5016/// - All arguments are unused.
5017#[no_mangle]
5018pub unsafe extern "C" fn xmlNewGlobalNs(
5019    _doc: *mut _xmlDoc,
5020    _href: *const xmlChar,
5021    _prefix: *const xmlChar,
5022) -> *mut _xmlNs {
5023    ptr::null_mut()
5024}