Skip to main content

libxml_rs/abi/
exports_parserint.rs

1//! exports_parserint — xmlParse* internal parser entry points (§11.1-I).
2//!
3//! C ABI exports for the classic libxml2 parser-internals family
4//! (parserInternals.h + a few parser.h / xmlIO.h / xmlerror.h entries).
5//! These are the recursive-descent parser primitives (`xmlParseName`,
6//! `xmlParseAttValue`, `xmlParseStartTag`, `xmlParseElement`, ...) that
7//! custom SAX consumers call directly on a `xmlParserCtxtPtr`.
8//!
9//! # Implementation strategy
10//!
11//! The functions are ported from `archaeology/libxml2-git/parser.c` and
12//! `parserInternals.c`, operating directly on the `_xmlParserCtxt` /
13//! `_xmlParserInput` field layout (same structs the crate's own parser
14//! entry points produce via `crate::xml::parser::helpers`), so they work
15//! on contexts created by `xmlCreateDocParserCtxt` /
16//! `xmlCreateFileParserCtxt` / `xmlCreatePushParserCtxt`.
17//!
18//! The crate's internal engine (`src/xml/parser/state.rs`) consumes the
19//! buffered input wholesale and does not expose this primitive
20//! granularity, so these primitives are ported directly rather than wired
21//! to the internal tokenizer. SAX events are dispatched through
22//! `crate::xml::sax::dispatch::SaxDispatcher` / the raw `_xmlSAXHandler`
23//! callbacks, preferring the SAX2 variants (`startElementNs`) when the
24//! handler provides them, exactly like upstream.
25//!
26//! All strings returned to C are allocated with the crate allocator
27//! (`xmlMalloc`) and must be freed by the caller with `xmlFree`, matching
28//! the upstream contract when `ctxt->dict == NULL` (which is the case for
29//! every context the crate creates).
30//!
31//! # Upstream contract
32//!
33//! Parity target is upstream `parserInternals.c` and `parser.c` (libxml2
34//! 2.15.3) with the `parserInternals.h`/`parser.h`/`xmlIO.h`/`xmlerror.h`
35//! signatures — the recursive-descent primitives (`xmlParseName`,
36//! `xmlParseAttValue`, `xmlParseStartTag`, `xmlParseElement`, the namespace
37//! parsers, `xmlParseQuotedString`, ...) that custom SAX consumers call
38//! directly on a `xmlParserCtxtPtr`. R-000165 and R-000169 both touch this
39//! module.
40//!
41//! # Conceptual behavior
42//!
43//! This module implements the classic parser-internals primitives as faithful
44//! ports operating directly on the `_xmlParserCtxt`/`_xmlParserInput` field
45//! layout, dispatching SAX events through `crate::xml::sax::dispatch` exactly
46//! like upstream (SAX2 variants preferred when present).
47//!
48//! # Ownership & safety invariants
49//!
50//! Strings returned to C are allocated with the crate allocator (`xmlMalloc`)
51//! and must be freed by the caller with `xmlFree`, matching the upstream
52//! contract when `ctxt->dict == NULL`. Parser inputs are owned by the context;
53//! the `_xmlParserInput.filename` is an owned copy — R-000169 made every
54//! construction path own its filename (xml_strndup) and every free path
55//! symmetric with `free_parser_input`.
56//!
57//! # Historical quirks & epochs
58//!
59//! These primitives are the 2.0-era recursive-descent parser surface
60//! (`legacy_parser` epoch in HISTORY.md) that has stayed exported for
61//! custom-SAX consumers; the internal `src/xml/parser/state.rs` engine is the
62//! modern non-recursive path (E-002 epoch for diagnostics). R-000169 (11.1-X)
63//! fixed the dangling-filename defect class in the four parserInternals entry
64//! points.
65//!
66//! # Deliberate oddities
67//!
68//! The primitives are deliberately ported standalone rather than wired to the
69//! internal tokenizer (documented in the header above): the internal engine
70//! consumes input wholesale and cannot expose this granularity. The `xmlParse*`
71//! namespace parsers follow upstreams exact token-by-token consumption
72//! including its error returns.
73//!
74//! # Proving courts
75//!
76//! The PARSER court family plus the DSO-LOADER and HEADER-COMPILE
77//! courts cover this module; the TREE-001 probe exercises the
78//! structures these primitives build; the parse-helper unit tests run under
79//! cargo test.
80//!
81//! # Tempting simplifications that would break parity
82//!
83//! A tempting simplification is to build the parsed name strings with
84//! Rust-owned buffers and hand their pointers to C — the strings must be
85//! `xmlMalloc`-allocated so `xmlFree` releases them (ownership contract
86//! above); and a tempting shortcut to store the input filename by borrowing
87//! the Rust String is exactly the R-000169 defect (dangling pointer after
88//! context free). Both must not be simplified.
89
90#![allow(missing_docs)]
91#![allow(non_snake_case)]
92#![allow(non_camel_case_types)]
93#![allow(non_upper_case_globals)]
94#![allow(unused_variables)]
95#![allow(private_interfaces)]
96#![allow(clippy::missing_safety_doc)]
97#![allow(clippy::not_unsafe_ptr_arg_deref)]
98#![allow(clippy::too_many_arguments)]
99#![allow(dead_code)]
100#![allow(unused_assignments)]
101
102// SAFETY-SCOPE: EXPORT-PARSERINT-MECHANICAL-001
103// (11.1-Z.3 proof scope, classified-generated) — this module is the
104// mechanical extern-"C" export surface: every `unsafe` block in it is
105// the documented indirection/registry-access pattern whose validity
106// rests on the upstream C contract, and the exported signatures are
107// machine-measured by the ABI-FUNCTION-SIGNATURE and DSO-LOADER
108// courts and the C-API differential probes. The safety contract of
109// each export is stated in its own doc comment; this scope covers the
110// mechanical wrappers' unsafe blocks.
111
112use core::ffi::c_void;
113use core::ptr;
114use std::mem::size_of;
115use std::os::raw::{c_char, c_int, c_uint, c_ulong};
116
117use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl, xmlMallocZero, xmlReallocImpl};
118use crate::abi::callbacks::_xmlSAXLocator;
119use crate::abi::structs::*;
120use crate::abi::types::*;
121use crate::xml::dtd::{create_content_model, create_int_subset, free_content_model};
122use crate::xml::parser::helpers::{
123    create_parser_ctxt, free_parser_ctxt, input_from_file, input_from_memory, setup_parser_input,
124};
125use crate::xml::sax::dispatch::SaxDispatcher;
126use crate::xml::validation::{is_xml_name_char, is_xml_name_start};
127
128// ═══════════════════════════════════════════════════════════════════════════════
129// Local constants
130// ═══════════════════════════════════════════════════════════════════════════════
131
132const XML_PARSER_EOF_STATE: c_int = 9; // XML_PARSER_EOF
133const XML_PARSER_BUFFER_SIZE: usize = 512;
134const LINE_LEN: usize = 80;
135
136// Enum-derived integer constants (the crate keeps these as `repr(C)` enums
137// in `crate::abi::types`; define plain `c_int` aliases for the values used
138// by the parser primitives).
139const XML_ENTITY_DECL: c_int = xmlElementType::XML_ENTITY_DECL as c_int;
140const XML_INTERNAL_GENERAL_ENTITY: c_int = xmlEntityType::XML_INTERNAL_GENERAL_ENTITY as c_int;
141const XML_EXTERNAL_GENERAL_PARSED_ENTITY: c_int =
142    xmlEntityType::XML_EXTERNAL_GENERAL_PARSED_ENTITY as c_int;
143const XML_INTERNAL_PARAMETER_ENTITY: c_int = xmlEntityType::XML_INTERNAL_PARAMETER_ENTITY as c_int;
144const XML_EXTERNAL_PARAMETER_ENTITY: c_int = xmlEntityType::XML_EXTERNAL_PARAMETER_ENTITY as c_int;
145const XML_INTERNAL_PREDEFINED_ENTITY: c_int =
146    xmlEntityType::XML_INTERNAL_PREDEFINED_ENTITY as c_int;
147const XML_ATTRIBUTE_CDATA: c_int = xmlAttributeType::XML_ATTRIBUTE_CDATA as c_int;
148const XML_ATTRIBUTE_ID: c_int = xmlAttributeType::XML_ATTRIBUTE_ID as c_int;
149const XML_ATTRIBUTE_IDREF: c_int = xmlAttributeType::XML_ATTRIBUTE_IDREF as c_int;
150const XML_ATTRIBUTE_IDREFS: c_int = xmlAttributeType::XML_ATTRIBUTE_IDREFS as c_int;
151const XML_ATTRIBUTE_ENTITY: c_int = xmlAttributeType::XML_ATTRIBUTE_ENTITY as c_int;
152const XML_ATTRIBUTE_ENTITIES: c_int = xmlAttributeType::XML_ATTRIBUTE_ENTITIES as c_int;
153const XML_ATTRIBUTE_NMTOKEN: c_int = xmlAttributeType::XML_ATTRIBUTE_NMTOKEN as c_int;
154const XML_ATTRIBUTE_NMTOKENS: c_int = xmlAttributeType::XML_ATTRIBUTE_NMTOKENS as c_int;
155const XML_ATTRIBUTE_ENUMERATION: c_int = xmlAttributeType::XML_ATTRIBUTE_ENUMERATION as c_int;
156const XML_ATTRIBUTE_NOTATION: c_int = xmlAttributeType::XML_ATTRIBUTE_NOTATION as c_int;
157const XML_ATTRIBUTE_NONE: c_int = xmlAttributeDefault::XML_ATTRIBUTE_NONE as c_int;
158const XML_ATTRIBUTE_REQUIRED: c_int = xmlAttributeDefault::XML_ATTRIBUTE_REQUIRED as c_int;
159const XML_ATTRIBUTE_IMPLIED: c_int = xmlAttributeDefault::XML_ATTRIBUTE_IMPLIED as c_int;
160const XML_ATTRIBUTE_FIXED: c_int = xmlAttributeDefault::XML_ATTRIBUTE_FIXED as c_int;
161const XML_ELEMENT_CONTENT_PCDATA: c_int =
162    xmlElementContentType::XML_ELEMENT_CONTENT_PCDATA as c_int;
163const XML_ELEMENT_CONTENT_ELEMENT: c_int =
164    xmlElementContentType::XML_ELEMENT_CONTENT_ELEMENT as c_int;
165const XML_ELEMENT_CONTENT_SEQ: c_int = xmlElementContentType::XML_ELEMENT_CONTENT_SEQ as c_int;
166const XML_ELEMENT_CONTENT_OR: c_int = xmlElementContentType::XML_ELEMENT_CONTENT_OR as c_int;
167const XML_ELEMENT_CONTENT_ONCE: c_int = xmlElementContentOccur::XML_ELEMENT_CONTENT_ONCE as c_int;
168const XML_ELEMENT_CONTENT_OPT: c_int = xmlElementContentOccur::XML_ELEMENT_CONTENT_OPT as c_int;
169const XML_ELEMENT_CONTENT_MULT: c_int = xmlElementContentOccur::XML_ELEMENT_CONTENT_MULT as c_int;
170const XML_ELEMENT_CONTENT_PLUS: c_int = xmlElementContentOccur::XML_ELEMENT_CONTENT_PLUS as c_int;
171const XML_ELEMENT_TYPE_EMPTY: c_int = xmlElementTypeVal::XML_ELEMENT_TYPE_EMPTY as c_int;
172const XML_ELEMENT_TYPE_ANY: c_int = xmlElementTypeVal::XML_ELEMENT_TYPE_ANY as c_int;
173const XML_ELEMENT_TYPE_MIXED: c_int = xmlElementTypeVal::XML_ELEMENT_TYPE_MIXED as c_int;
174const XML_ELEMENT_TYPE_ELEMENT: c_int = xmlElementTypeVal::XML_ELEMENT_TYPE_ELEMENT as c_int;
175const XML_DOC_INTERNAL: c_int = xmlDocProperties::XML_DOC_INTERNAL as c_int;
176
177// Error codes that exist upstream (parser.c error paths) but are missing
178// from the crate's renumbered `XML_ERR_*` list. Only used to flag errors
179// through `ctxt->errNo`; the exact numeric values are not part of any
180// upstream enum in this crate.
181const XML_ERR_URI_REQUIRED: c_int = 100;
182const XML_ERR_PUBID_REQUIRED: c_int = 101;
183const XML_ERR_RESERVED_XML_NAME: c_int = 102;
184const XML_ERR_HYPHEN_IN_COMMENT: c_int = 103;
185const XML_ERR_EQUAL_REQUIRED: c_int = 104;
186const XML_ERR_SEPARATOR_REQUIRED: c_int = 105;
187const XML_ERR_INT_SUBSET_NOT_FINISHED: c_int = 106;
188const XML_ERR_UNKNOWN_VERSION: c_int = 107;
189const XML_ERR_ENCODING_NAME: c_int = 108;
190const XML_IO_UNKNOWN: c_int = 109;
191const XML_ERR_PCDATA_REQUIRED: c_int = 110;
192const XML_ERR_RESOURCE_LIMIT: c_int = 111;
193const XML_ERR_LTSLASH_REQUIRED: c_int = 112;
194
195// The static predefined-entity table is immutable; mark the struct Sync so
196// it can live in a `static` (same pattern as `xmlChRangeGroup` in structs.rs).
197unsafe impl Sync for _xmlEntity {}
198
199// ═══════════════════════════════════════════════════════════════════════════════
200// Predefined entities (static instances, upstream entities.c)
201// ═══════════════════════════════════════════════════════════════════════════════
202
203static PREDEF_AMP_NAME: [xmlChar; 4] = *b"amp\0";
204static PREDEF_LT_NAME: [xmlChar; 3] = *b"lt\0";
205static PREDEF_GT_NAME: [xmlChar; 3] = *b"gt\0";
206static PREDEF_QUOT_NAME: [xmlChar; 5] = *b"quot\0";
207static PREDEF_APOS_NAME: [xmlChar; 5] = *b"apos\0";
208static PREDEF_AMP_CONTENT: [xmlChar; 2] = *b"&\0";
209static PREDEF_LT_CONTENT: [xmlChar; 2] = *b"<\0";
210static PREDEF_GT_CONTENT: [xmlChar; 2] = *b">\0";
211static PREDEF_QUOT_CONTENT: [xmlChar; 2] = *b"\"\0";
212static PREDEF_APOS_CONTENT: [xmlChar; 2] = *b"'\0";
213
214static PREDEFINED_ENTITIES: [_xmlEntity; 5] = [
215    _xmlEntity {
216        _private: ptr::null_mut(),
217        type_: XML_ENTITY_DECL as c_int,
218        name: PREDEF_AMP_NAME.as_ptr(),
219        children: ptr::null_mut(),
220        last: ptr::null_mut(),
221        parent: ptr::null_mut(),
222        next: ptr::null_mut(),
223        prev: ptr::null_mut(),
224        doc: ptr::null_mut(),
225        orig: ptr::null_mut(),
226        content: PREDEF_AMP_CONTENT.as_ptr() as *mut xmlChar,
227        length: 1,
228        etype: XML_INTERNAL_PREDEFINED_ENTITY as c_int,
229        ExternalID: ptr::null(),
230        SystemID: ptr::null(),
231        nexte: ptr::null_mut(),
232        URI: ptr::null(),
233        owner: 0,
234        flags: 0,
235        expandedSize: 0,
236    },
237    _xmlEntity {
238        _private: ptr::null_mut(),
239        type_: XML_ENTITY_DECL as c_int,
240        name: PREDEF_LT_NAME.as_ptr(),
241        children: ptr::null_mut(),
242        last: ptr::null_mut(),
243        parent: ptr::null_mut(),
244        next: ptr::null_mut(),
245        prev: ptr::null_mut(),
246        doc: ptr::null_mut(),
247        orig: ptr::null_mut(),
248        content: PREDEF_LT_CONTENT.as_ptr() as *mut xmlChar,
249        length: 1,
250        etype: XML_INTERNAL_PREDEFINED_ENTITY as c_int,
251        ExternalID: ptr::null(),
252        SystemID: ptr::null(),
253        nexte: ptr::null_mut(),
254        URI: ptr::null(),
255        owner: 0,
256        flags: 0,
257        expandedSize: 0,
258    },
259    _xmlEntity {
260        _private: ptr::null_mut(),
261        type_: XML_ENTITY_DECL as c_int,
262        name: PREDEF_GT_NAME.as_ptr(),
263        children: ptr::null_mut(),
264        last: ptr::null_mut(),
265        parent: ptr::null_mut(),
266        next: ptr::null_mut(),
267        prev: ptr::null_mut(),
268        doc: ptr::null_mut(),
269        orig: ptr::null_mut(),
270        content: PREDEF_GT_CONTENT.as_ptr() as *mut xmlChar,
271        length: 1,
272        etype: XML_INTERNAL_PREDEFINED_ENTITY as c_int,
273        ExternalID: ptr::null(),
274        SystemID: ptr::null(),
275        nexte: ptr::null_mut(),
276        URI: ptr::null(),
277        owner: 0,
278        flags: 0,
279        expandedSize: 0,
280    },
281    _xmlEntity {
282        _private: ptr::null_mut(),
283        type_: XML_ENTITY_DECL as c_int,
284        name: PREDEF_QUOT_NAME.as_ptr(),
285        children: ptr::null_mut(),
286        last: ptr::null_mut(),
287        parent: ptr::null_mut(),
288        next: ptr::null_mut(),
289        prev: ptr::null_mut(),
290        doc: ptr::null_mut(),
291        orig: ptr::null_mut(),
292        content: PREDEF_QUOT_CONTENT.as_ptr() as *mut xmlChar,
293        length: 1,
294        etype: XML_INTERNAL_PREDEFINED_ENTITY as c_int,
295        ExternalID: ptr::null(),
296        SystemID: ptr::null(),
297        nexte: ptr::null_mut(),
298        URI: ptr::null(),
299        owner: 0,
300        flags: 0,
301        expandedSize: 0,
302    },
303    _xmlEntity {
304        _private: ptr::null_mut(),
305        type_: XML_ENTITY_DECL as c_int,
306        name: PREDEF_APOS_NAME.as_ptr(),
307        children: ptr::null_mut(),
308        last: ptr::null_mut(),
309        parent: ptr::null_mut(),
310        next: ptr::null_mut(),
311        prev: ptr::null_mut(),
312        doc: ptr::null_mut(),
313        orig: ptr::null_mut(),
314        content: PREDEF_APOS_CONTENT.as_ptr() as *mut xmlChar,
315        length: 1,
316        etype: XML_INTERNAL_PREDEFINED_ENTITY as c_int,
317        ExternalID: ptr::null(),
318        SystemID: ptr::null(),
319        nexte: ptr::null_mut(),
320        URI: ptr::null(),
321        owner: 0,
322        flags: 0,
323        expandedSize: 0,
324    },
325];
326
327// ═══════════════════════════════════════════════════════════════════════════════
328// Character classification helpers (upstream parserInternals.h macros)
329// ═══════════════════════════════════════════════════════════════════════════════
330
331#[inline]
332const fn pi_is_blank_ch(c: u8) -> bool {
333    c == b' ' || c == b'\t' || c == b'\n' || c == b'\r'
334}
335
336#[inline]
337fn pi_is_byte_char(c: u8) -> bool {
338    c == 0x09 || c == 0x0A || c == 0x0D || (0x20..=0x7E).contains(&c) || (0xA0..=0xFF).contains(&c)
339}
340
341#[inline]
342const fn pi_is_pubidchar(c: u8) -> bool {
343    c == 0x20
344        || c == 0x0D
345        || c == 0x0A
346        || c.is_ascii_alphanumeric()
347        || matches!(
348            c,
349            b'-' | b'\''
350                | b'('
351                | b')'
352                | b'+'
353                | b','
354                | b'.'
355                | b'/'
356                | b':'
357                | b'='
358                | b'?'
359                | b';'
360                | b'!'
361                | b'*'
362                | b'#'
363                | b'@'
364                | b'$'
365                | b'_'
366                | b'%'
367        )
368}
369
370/// The XML `Char` production.
371#[inline]
372const fn pi_is_char(c: c_int) -> bool {
373    matches!(c, 0x9 | 0xA | 0xD | 0x20..=0xD7FF | 0xE000..=0xFFFD | 0x10000..=0x10FFFF)
374}
375
376/// `IS_LETTER_CH` — ASCII letter.
377#[inline]
378const fn pi_is_letter_ch(c: u8) -> bool {
379    c.is_ascii_alphabetic()
380}
381
382/// `IS_DIGIT_CH` — ASCII digit.
383#[inline]
384const fn pi_is_digit_ch(c: u8) -> bool {
385    c.is_ascii_digit()
386}
387
388/// `IS_NAME_START_CHAR` — XML NameStartChar (including ':').
389const fn pi_is_name_start_char(c: c_int) -> bool {
390    if c < 0x80 {
391        return (c as u8).is_ascii_alphabetic() || c == b'_' as c_int || c == b':' as c_int;
392    }
393    if c > 0x10FFFF {
394        return false;
395    }
396    match char::from_u32(c as u32) {
397        Some(ch) => is_xml_name_start(ch),
398        None => false,
399    }
400}
401
402/// `IS_NAME_CHAR` — XML NameChar (including ':').
403const fn pi_is_name_char(c: c_int) -> bool {
404    if c < 0x80 {
405        let b = c as u8;
406        return b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.' || b == b':';
407    }
408    if c > 0x10FFFF {
409        return false;
410    }
411    match char::from_u32(c as u32) {
412        Some(ch) => is_xml_name_char(ch),
413        None => false,
414    }
415}
416
417// ═══════════════════════════════════════════════════════════════════════════════
418// Input access primitives (upstream parser.c macros CUR/RAW/NXT/NEXT/SKIP/...)
419// ═══════════════════════════════════════════════════════════════════════════════
420
421/// Current input of the context, or NULL.
422#[inline]
423unsafe fn pi_input(ctxt: *mut _xmlParserCtxt) -> *mut _xmlParserInput {
424    if ctxt.is_null() {
425        return ptr::null_mut();
426    }
427    unsafe { (*ctxt).input }
428}
429
430/// `CUR` — current byte.
431#[inline]
432unsafe fn pi_raw(ctxt: *mut _xmlParserCtxt) -> u8 {
433    let input = unsafe { pi_input(ctxt) };
434    if input.is_null() || unsafe { (*input).cur.is_null() } {
435        return 0;
436    }
437    unsafe { *(*input).cur }
438}
439
440/// `NXT(val)` — byte at offset.
441#[inline]
442unsafe fn pi_nxt(ctxt: *mut _xmlParserCtxt, off: isize) -> u8 {
443    let input = unsafe { pi_input(ctxt) };
444    if input.is_null() || unsafe { (*input).cur.is_null() } {
445        return 0;
446    }
447    let cur = unsafe { (*input).cur };
448    let end = unsafe { (*input).end };
449    if off >= 0 {
450        if cur.offset(off) >= end {
451            return 0;
452        }
453        unsafe { *cur.offset(off) }
454    } else {
455        // negative offsets are only ever used for already-consumed bytes
456        let base = unsafe { (*input).base };
457        if cur.offset(off) < base {
458            return 0;
459        }
460        unsafe { *cur.offset(off) }
461    }
462}
463
464/// `CUR_PTR`.
465#[inline]
466unsafe fn pi_cur_ptr(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
467    let input = unsafe { pi_input(ctxt) };
468    if input.is_null() {
469        return ptr::null();
470    }
471    unsafe { (*input).cur }
472}
473
474/// `BASE_PTR`.
475#[inline]
476unsafe fn pi_base_ptr(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
477    let input = unsafe { pi_input(ctxt) };
478    if input.is_null() {
479        return ptr::null();
480    }
481    unsafe { (*input).base }
482}
483
484/// `SKIP(val)` — advance `val` bytes, updating the column.
485unsafe fn pi_skip(ctxt: *mut _xmlParserCtxt, val: isize) {
486    let input = unsafe { pi_input(ctxt) };
487    if input.is_null() {
488        return;
489    }
490    unsafe {
491        let cur = (*input).cur;
492        let end = (*input).end;
493        let mut n = val;
494        while n > 0 && cur.offset(n) > end {
495            n -= 1;
496        }
497        (*input).col += val as c_int;
498        (*input).cur = cur.offset(val).min(end);
499    }
500}
501
502/// `NEXT1` — advance one byte.
503unsafe fn pi_next1(ctxt: *mut _xmlParserCtxt) {
504    let input = unsafe { pi_input(ctxt) };
505    if input.is_null() {
506        return;
507    }
508    unsafe {
509        if (*input).cur < (*input).end {
510            (*input).cur = (*input).cur.add(1);
511            (*input).col += 1;
512        }
513    }
514}
515
516/// `NEXTL(l)` — advance `l` bytes, tracking line/col.
517unsafe fn pi_nextl(ctxt: *mut _xmlParserCtxt, l: usize) {
518    let input = unsafe { pi_input(ctxt) };
519    if input.is_null() {
520        return;
521    }
522    unsafe {
523        let cur = (*input).cur;
524        if cur < (*input).end {
525            if *cur == b'\n' {
526                (*input).line += 1;
527                (*input).col = 1;
528            } else {
529                (*input).col += 1;
530            }
531            (*input).cur = cur.add(l).min((*input).end);
532        }
533    }
534}
535
536/// `NEXT` — advance one Unicode character (upstream xmlNextChar).
537unsafe fn pi_next_char(ctxt: *mut _xmlParserCtxt) {
538    let input = unsafe { pi_input(ctxt) };
539    if input.is_null() {
540        return;
541    }
542    unsafe {
543        let cur = (*input).cur;
544        if cur >= (*input).end {
545            return;
546        }
547        let c = *cur;
548        if c < 0x80 {
549            if c == b'\n' {
550                (*input).cur = cur.add(1);
551                (*input).line += 1;
552                (*input).col = 1;
553            } else if c == b'\r' {
554                if cur.add(1) < (*input).end && *cur.add(1) == b'\n' {
555                    (*input).cur = cur.add(2);
556                } else {
557                    (*input).cur = cur.add(1);
558                }
559                (*input).line += 1;
560                (*input).col = 1;
561            } else {
562                (*input).cur = cur.add(1);
563                (*input).col += 1;
564            }
565        } else {
566            (*input).col += 1;
567            let (_, l) = pi_decode_utf8(cur, (*input).end);
568            if l == 0 {
569                (*input).cur = cur.add(1);
570            } else {
571                (*input).cur = cur.add(l);
572            }
573        }
574    }
575}
576
577/// Decode a UTF-8 character at `ptr` (bounded by `end`).
578///
579/// Returns `(codepoint, byte_len)`. At EOF returns `(0, 0)`; on encoding
580/// errors returns `(0xFFFD, 1)` (the recovery character).
581unsafe fn pi_decode_utf8(ptr: *const u8, end: *const u8) -> (c_int, usize) {
582    unsafe {
583        if ptr >= end {
584            return (0, 0);
585        }
586        let c = *ptr;
587        if c < 0x80 {
588            return (c as c_int, 1);
589        }
590        let avail = end.offset_from(ptr) as usize;
591        if (0xC2..=0xDF).contains(&c) && avail >= 2 && (*ptr.add(1) & 0xC0) == 0x80 {
592            let v = (((c as c_int) & 0x1F) << 6) | ((*ptr.add(1) as c_int) & 0x3F);
593            return (v, 2);
594        }
595        if c >= 0xE0 && avail >= 3 && (*ptr.add(1) & 0xC0) == 0x80 && (*ptr.add(2) & 0xC0) == 0x80 {
596            let v = (((c as c_int) & 0x0F) << 12)
597                | (((*ptr.add(1) as c_int) & 0x3F) << 6)
598                | ((*ptr.add(2) as c_int) & 0x3F);
599            if v >= 0x800 && !(0xD800..=0xDFFF).contains(&v) {
600                return (v, 3);
601            }
602            return (0xFFFD, 1);
603        }
604        if c >= 0xF0
605            && avail >= 4
606            && (*ptr.add(1) & 0xC0) == 0x80
607            && (*ptr.add(2) & 0xC0) == 0x80
608            && (*ptr.add(3) & 0xC0) == 0x80
609        {
610            let v = (((c as c_int) & 0x07) << 18)
611                | (((*ptr.add(1) as c_int) & 0x3F) << 12)
612                | (((*ptr.add(2) as c_int) & 0x3F) << 6)
613                | ((*ptr.add(3) as c_int) & 0x3F);
614            if (0x10000..=0x10FFFF).contains(&v) {
615                return (v, 4);
616            }
617            return (0xFFFD, 1);
618        }
619        (0xFFFD, 1)
620    }
621}
622
623/// `xmlCurrentChar` — current Unicode char + byte length; `(0, 0)` at EOF.
624unsafe fn pi_current_char(ctxt: *mut _xmlParserCtxt) -> (c_int, usize) {
625    let input = unsafe { pi_input(ctxt) };
626    if input.is_null() || unsafe { (*input).cur.is_null() } {
627        return (0, 0);
628    }
629    unsafe {
630        let cur = (*input).cur;
631        if cur >= (*input).end {
632            return (0, 0);
633        }
634        pi_decode_utf8(cur, (*input).end)
635    }
636}
637
638/// `xmlCurrentCharRecover` — maps EOF/invalid to 0xFFFD.
639unsafe fn pi_current_char_recover(ctxt: *mut _xmlParserCtxt) -> (c_int, usize) {
640    let (c, l) = unsafe { pi_current_char(ctxt) };
641    if c == 0 {
642        (0xFFFD, 1)
643    } else {
644        (c, l)
645    }
646}
647
648/// `PARSER_STOPPED`.
649#[inline]
650unsafe fn pi_stopped(ctxt: *mut _xmlParserCtxt) -> bool {
651    unsafe { (*ctxt).errNo != XML_ERR_OK || (*ctxt).disableSAX != 0 }
652}
653
654/// `xmlFatalErr` equivalent: record the error and stop the parser.
655unsafe fn pi_fatal_err(ctxt: *mut _xmlParserCtxt, code: c_int) {
656    unsafe {
657        let c = &mut *ctxt;
658        if c.errNo == XML_ERR_OK {
659            c.errNo = code;
660        }
661        c.wellFormed = 0;
662    }
663}
664
665/// `xmlErrMemory` equivalent.
666unsafe fn pi_err_memory(ctxt: *mut _xmlParserCtxt) {
667    unsafe {
668        let c = &mut *ctxt;
669        if c.errNo == XML_ERR_OK {
670            c.errNo = XML_ERR_NO_MEMORY;
671        }
672        c.wellFormed = 0;
673    }
674}
675
676/// Duplicate `len` bytes into a null-terminated xmlChar buffer.
677unsafe fn pi_strndup_bytes(start: *const xmlChar, len: usize) -> *mut xmlChar {
678    unsafe {
679        let buf = xmlMallocImpl(len + 1) as *mut xmlChar;
680        if buf.is_null() {
681            return ptr::null_mut();
682        }
683        ptr::copy_nonoverlapping(start, buf, len);
684        *buf.add(len) = 0;
685        buf
686    }
687}
688
689/// Encode a codepoint as UTF-8 into the buffer (upstream `COPY_BUF`).
690fn pi_push_codepoint(buf: &mut Vec<u8>, c: c_int) {
691    if c < 0x80 {
692        buf.push(c as u8);
693    } else if c < 0x800 {
694        buf.push((0xC0 | ((c >> 6) & 0x1F)) as u8);
695        buf.push((0x80 | (c & 0x3F)) as u8);
696    } else if c < 0x10000 {
697        buf.push((0xE0 | ((c >> 12) & 0x0F)) as u8);
698        buf.push((0x80 | ((c >> 6) & 0x3F)) as u8);
699        buf.push((0x80 | (c & 0x3F)) as u8);
700    } else {
701        buf.push((0xF0 | ((c >> 18) & 0x07)) as u8);
702        buf.push((0x80 | ((c >> 12) & 0x3F)) as u8);
703        buf.push((0x80 | ((c >> 6) & 0x3F)) as u8);
704        buf.push((0x80 | (c & 0x3F)) as u8);
705    }
706}
707
708/// `SKIP_BLANKS` — skip whitespace, popping parameter entities at end of
709/// input. Returns the number of characters skipped.
710unsafe fn pi_skip_blanks(ctxt: *mut _xmlParserCtxt) -> c_int {
711    let mut res: c_int = 0;
712    unsafe {
713        loop {
714            if pi_stopped(ctxt) {
715                break;
716            }
717            let input = pi_input(ctxt);
718            if input.is_null() {
719                break;
720            }
721            if (*input).cur >= (*input).end || *(*input).cur == 0 {
722                // End of a parameter-entity input: pop it (upstream
723                // xmlSkipBlankCharsPE). The main input is never popped.
724                if (*input).entity.is_null() || (*ctxt).inputNr <= 1 {
725                    break;
726                }
727                pi_pop_pe(ctxt);
728                res = res.saturating_add(1);
729                continue;
730            }
731            let c = *(*input).cur;
732            if pi_is_blank_ch(c) {
733                pi_next_char(ctxt);
734                res = res.saturating_add(1);
735            } else {
736                break;
737            }
738        }
739    }
740    res
741}
742
743/// Compare six bytes at the current position against a literal.
744#[inline]
745unsafe fn pi_cmp6(ctxt: *mut _xmlParserCtxt, s: &[u8; 6]) -> bool {
746    unsafe {
747        pi_nxt(ctxt, 0) == s[0]
748            && pi_nxt(ctxt, 1) == s[1]
749            && pi_nxt(ctxt, 2) == s[2]
750            && pi_nxt(ctxt, 3) == s[3]
751            && pi_nxt(ctxt, 4) == s[4]
752            && pi_nxt(ctxt, 5) == s[5]
753    }
754}
755
756#[inline]
757unsafe fn pi_cmp5(ctxt: *mut _xmlParserCtxt, s: &[u8; 5]) -> bool {
758    unsafe {
759        pi_nxt(ctxt, 0) == s[0]
760            && pi_nxt(ctxt, 1) == s[1]
761            && pi_nxt(ctxt, 2) == s[2]
762            && pi_nxt(ctxt, 3) == s[3]
763            && pi_nxt(ctxt, 4) == s[4]
764    }
765}
766
767#[inline]
768unsafe fn pi_cmp7(ctxt: *mut _xmlParserCtxt, s: &[u8; 7]) -> bool {
769    unsafe { pi_cmp6(ctxt, &[s[0], s[1], s[2], s[3], s[4], s[5]]) && pi_nxt(ctxt, 6) == s[6] }
770}
771
772#[inline]
773unsafe fn pi_cmp8(ctxt: *mut _xmlParserCtxt, s: &[u8; 8]) -> bool {
774    unsafe { pi_cmp7(ctxt, &[s[0], s[1], s[2], s[3], s[4], s[5], s[6]]) && pi_nxt(ctxt, 7) == s[7] }
775}
776
777#[inline]
778unsafe fn pi_cmp9(ctxt: *mut _xmlParserCtxt, s: &[u8; 9]) -> bool {
779    unsafe {
780        pi_cmp8(ctxt, &[s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]) && pi_nxt(ctxt, 8) == s[8]
781    }
782}
783
784#[inline]
785unsafe fn pi_cmp10(ctxt: *mut _xmlParserCtxt, s: &[u8; 10]) -> bool {
786    unsafe {
787        pi_cmp9(
788            ctxt,
789            &[s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8]],
790        ) && pi_nxt(ctxt, 9) == s[9]
791    }
792}
793
794// ═══════════════════════════════════════════════════════════════════════════════
795// Stack helpers (name / space / node / input stacks of _xmlParserCtxt)
796// ═══════════════════════════════════════════════════════════════════════════════
797
798/// `namePush` — push a name onto ctxt->nameTab (takes ownership).
799unsafe fn pi_name_push(ctxt: *mut _xmlParserCtxt, name: *const xmlChar) -> c_int {
800    unsafe {
801        let c = &mut *ctxt;
802        if c.nameNr >= c.nameMax {
803            let new_max = if c.nameMax == 0 { 10 } else { c.nameMax * 2 };
804            let new_tab = xmlReallocImpl(
805                c.nameTab as *mut c_void,
806                (new_max as usize) * size_of::<*const xmlChar>(),
807            ) as *mut *const xmlChar;
808            if new_tab.is_null() {
809                return -1;
810            }
811            c.nameTab = new_tab;
812            c.nameMax = new_max;
813        }
814        *c.nameTab.add(c.nameNr as usize) = name;
815        c.nameNr += 1;
816        c.name = name;
817    }
818    0
819}
820
821/// `namePop` — pop and free the top name.
822unsafe fn pi_name_pop(ctxt: *mut _xmlParserCtxt) {
823    unsafe {
824        let c = &mut *ctxt;
825        if c.nameNr <= 0 {
826            return;
827        }
828        c.nameNr -= 1;
829        let old = *c.nameTab.add(c.nameNr as usize);
830        *c.nameTab.add(c.nameNr as usize) = ptr::null();
831        if c.nameNr == 0 {
832            c.name = ptr::null();
833        } else {
834            c.name = *c.nameTab.add((c.nameNr - 1) as usize);
835        }
836        if !old.is_null() {
837            xmlFreeImpl(old as *mut c_void);
838        }
839    }
840}
841
842/// `spacePush`.
843unsafe fn pi_space_push(ctxt: *mut _xmlParserCtxt, val: c_int) -> c_int {
844    unsafe {
845        let c = &mut *ctxt;
846        if c.spaceNr >= c.spaceMax {
847            let new_max = if c.spaceMax == 0 { 10 } else { c.spaceMax * 2 };
848            let new_tab = xmlReallocImpl(
849                c.spaceTab as *mut c_void,
850                (new_max as usize) * size_of::<c_int>(),
851            ) as *mut c_int;
852            if new_tab.is_null() {
853                return -1;
854            }
855            c.spaceTab = new_tab;
856            c.spaceMax = new_max;
857        }
858        *c.spaceTab.add(c.spaceNr as usize) = val;
859        c.spaceNr += 1;
860        c.space = c.spaceTab.add((c.spaceNr - 1) as usize);
861    }
862    0
863}
864
865/// `spacePop`.
866unsafe fn pi_space_pop(ctxt: *mut _xmlParserCtxt) {
867    unsafe {
868        let c = &mut *ctxt;
869        if c.spaceNr <= 0 {
870            return;
871        }
872        c.spaceNr -= 1;
873        if c.spaceNr == 0 {
874            c.space = ptr::null_mut();
875        } else {
876            c.space = c.spaceTab.add((c.spaceNr - 1) as usize);
877        }
878    }
879}
880
881/// `nodePush` — push a node onto ctxt->nodeTab.
882unsafe fn pi_node_push(ctxt: *mut _xmlParserCtxt, node: *mut _xmlNode) -> c_int {
883    unsafe {
884        let c = &mut *ctxt;
885        if c.nodeNr >= c.nodeMax {
886            let new_max = if c.nodeMax == 0 { 10 } else { c.nodeMax * 2 };
887            let new_tab = xmlReallocImpl(
888                c.nodeTab as *mut c_void,
889                (new_max as usize) * size_of::<*mut _xmlNode>(),
890            ) as *mut *mut _xmlNode;
891            if new_tab.is_null() {
892                return -1;
893            }
894            c.nodeTab = new_tab;
895            c.nodeMax = new_max;
896        }
897        *c.nodeTab.add(c.nodeNr as usize) = node;
898        c.nodeNr += 1;
899        c.node = node;
900    }
901    0
902}
903
904/// `nodePop`.
905unsafe fn pi_node_pop(ctxt: *mut _xmlParserCtxt) {
906    unsafe {
907        let c = &mut *ctxt;
908        if c.nodeNr <= 0 {
909            return;
910        }
911        c.nodeNr -= 1;
912        if c.nodeNr == 0 {
913            c.node = ptr::null_mut();
914        } else {
915            c.node = *c.nodeTab.add((c.nodeNr - 1) as usize);
916        }
917    }
918}
919
920/// `xmlCtxtPushInput` — push an input onto the input stack.
921unsafe fn pi_input_push(ctxt: *mut _xmlParserCtxt, input: *mut _xmlParserInput) -> c_int {
922    unsafe {
923        if ctxt.is_null() || input.is_null() {
924            return -1;
925        }
926        let c = &mut *ctxt;
927        if c.inputNr >= c.inputMax {
928            let new_max = if c.inputMax == 0 { 4 } else { c.inputMax * 2 };
929            let new_tab = xmlReallocImpl(
930                c.inputTab as *mut c_void,
931                (new_max as usize) * size_of::<*mut _xmlParserInput>(),
932            ) as *mut *mut _xmlParserInput;
933            if new_tab.is_null() {
934                return -1;
935            }
936            c.inputTab = new_tab;
937            c.inputMax = new_max;
938        }
939        *c.inputTab.add(c.inputNr as usize) = input;
940        c.input = input;
941        c.inputNr += 1;
942    }
943    0
944}
945
946/// `xmlCtxtPopInput` — pop the top input from the stack.
947unsafe fn pi_input_pop(ctxt: *mut _xmlParserCtxt) -> *mut _xmlParserInput {
948    unsafe {
949        if ctxt.is_null() {
950            return ptr::null_mut();
951        }
952        let c = &mut *ctxt;
953        if c.inputNr <= 0 {
954            return ptr::null_mut();
955        }
956        c.inputNr -= 1;
957        if c.inputNr > 0 {
958            c.input = *c.inputTab.add((c.inputNr - 1) as usize);
959        } else {
960            c.input = ptr::null_mut();
961        }
962        let ret = *c.inputTab.add(c.inputNr as usize);
963        *c.inputTab.add(c.inputNr as usize) = ptr::null_mut();
964        ret
965    }
966}
967
968/// `xmlPopPE` — pop a parameter-entity input, releasing its buffer.
969unsafe fn pi_pop_pe(ctxt: *mut _xmlParserCtxt) {
970    unsafe {
971        let input = pi_input_pop(ctxt);
972        if input.is_null() {
973            return;
974        }
975        let base = (*input).base;
976        if !base.is_null() && !(*input).entity.is_null() {
977            xmlFreeImpl(base as *mut c_void);
978        }
979        // Free the owned filename copy (alloc_parser_input/parserint dup) so
980        // the pop path is symmetric with free_parser_input.
981        if !(*input).filename.is_null() {
982            xmlFreeImpl((*input).filename as *mut c_void);
983        }
984        xmlFreeImpl(input as *mut c_void);
985    }
986}
987
988/// `xmlCtxtInitializeLate` — detect SAX2 handlers.
989unsafe fn pi_ctxt_late_init(ctxt: *mut _xmlParserCtxt) {
990    unsafe {
991        if ctxt.is_null() {
992            return;
993        }
994        let c = &mut *ctxt;
995        if !c.sax.is_null() && (*c.sax).initialized == XML_SAX2_MAGIC as c_uint {
996            c.sax2 = 1;
997        }
998    }
999}
1000
1001/// Split a QName into (localname, prefix) sub-pointers.
1002const unsafe fn pi_split_qname(qname: *const xmlChar) -> (*const xmlChar, *const xmlChar) {
1003    if qname.is_null() {
1004        return (ptr::null(), ptr::null());
1005    }
1006    unsafe {
1007        let mut p = qname;
1008        while *p != 0 {
1009            if *p == b':' {
1010                return (p.add(1), qname);
1011            }
1012            p = p.add(1);
1013        }
1014    }
1015    (qname, ptr::null())
1016}
1017
1018/// Whether the null-terminated string equals `bytes`.
1019const unsafe fn pi_cstr_eq(s: *const xmlChar, bytes: &[u8]) -> bool {
1020    if s.is_null() {
1021        return bytes.is_empty();
1022    }
1023    unsafe {
1024        let mut i = 0usize;
1025        loop {
1026            let b = *s.add(i);
1027            if i >= bytes.len() {
1028                return b == 0;
1029            }
1030            if b != bytes[i] {
1031                return false;
1032            }
1033            i += 1;
1034        }
1035    }
1036}
1037
1038// ═══════════════════════════════════════════════════════════════════════════════
1039// Entity lookup
1040// ═══════════════════════════════════════════════════════════════════════════════
1041
1042/// `xmlGetPredefinedEntity` equivalent.
1043unsafe fn pi_get_predefined_entity(name: *const xmlChar) -> *mut _xmlEntity {
1044    if name.is_null() {
1045        return ptr::null_mut();
1046    }
1047    for e in PREDEFINED_ENTITIES.iter() {
1048        if unsafe { crate::abi::exports_xml2::xmlStrcmp(e.name, name) == 0 } {
1049            return e as *const _xmlEntity as *mut _xmlEntity;
1050        }
1051    }
1052    ptr::null_mut()
1053}
1054
1055/// `xmlLookupGeneralEntity` equivalent (without the unparsed-entity
1056/// validation, which needs error reporting paths).
1057unsafe fn pi_lookup_general_entity(
1058    ctxt: *mut _xmlParserCtxt,
1059    name: *const xmlChar,
1060) -> *mut _xmlEntity {
1061    unsafe {
1062        // Predefined entities override any extra definition (unless OLDSAX).
1063        if (*ctxt).options & XML_PARSE_OLDSAX == 0 {
1064            let ent = pi_get_predefined_entity(name);
1065            if !ent.is_null() {
1066                return ent;
1067            }
1068        }
1069        let c = &*ctxt;
1070        if !c.sax.is_null() {
1071            let ent = SaxDispatcher::get_entity(&*c.sax, c.userData, name);
1072            if !ent.is_null() {
1073                return ent;
1074            }
1075            if c.userData == ctxt as *mut c_void {
1076                let ent2 = crate::abi::exports_xml2::xmlSAX2GetEntity(c.userData, name);
1077                if !ent2.is_null() {
1078                    return ent2;
1079                }
1080            }
1081        }
1082        ptr::null_mut()
1083    }
1084}
1085
1086/// Lookup a parameter entity (sax getParameterEntity, then doc fallback).
1087unsafe fn pi_lookup_parameter_entity(
1088    ctxt: *mut _xmlParserCtxt,
1089    name: *const xmlChar,
1090) -> *mut _xmlEntity {
1091    unsafe {
1092        let c = &*ctxt;
1093        if !c.sax.is_null() {
1094            let ent = SaxDispatcher::get_parameter_entity(&*c.sax, c.userData, name);
1095            if !ent.is_null() {
1096                return ent;
1097            }
1098            if c.userData == ctxt as *mut c_void {
1099                let ent2 = crate::abi::exports_xml2::xmlSAX2GetParameterEntity(c.userData, name);
1100                if !ent2.is_null() {
1101                    return ent2;
1102                }
1103            }
1104        }
1105        ptr::null_mut()
1106    }
1107}
1108
1109// ═══════════════════════════════════════════════════════════════════════════════
1110// SAX dispatch helpers
1111// ═══════════════════════════════════════════════════════════════════════════════
1112
1113/// Dispatch a SAX characters/ignorableWhitespace event with `bytes`.
1114unsafe fn pi_sax_chars(ctxt: *mut _xmlParserCtxt, bytes: &[u8], ignorable: bool) {
1115    if bytes.is_empty() {
1116        return;
1117    }
1118    unsafe {
1119        let c = &*ctxt;
1120        if c.sax.is_null() || c.disableSAX != 0 {
1121            return;
1122        }
1123        let buf = pi_strndup_bytes(bytes.as_ptr(), bytes.len());
1124        if buf.is_null() {
1125            return;
1126        }
1127        if ignorable {
1128            SaxDispatcher::ignorable_whitespace(&*c.sax, c.userData, buf, bytes.len() as c_int);
1129        } else {
1130            SaxDispatcher::characters(&*c.sax, c.userData, buf, bytes.len() as c_int);
1131        }
1132        xmlFreeImpl(buf as *mut c_void);
1133    }
1134}
1135
1136/// Dispatch a SAX comment event with `bytes`.
1137unsafe fn pi_sax_comment(ctxt: *mut _xmlParserCtxt, bytes: &[u8]) {
1138    unsafe {
1139        let c = &*ctxt;
1140        if c.sax.is_null() || c.disableSAX != 0 {
1141            return;
1142        }
1143        let buf = if bytes.is_empty() {
1144            ptr::null()
1145        } else {
1146            pi_strndup_bytes(bytes.as_ptr(), bytes.len())
1147        };
1148        if bytes.is_empty() || !buf.is_null() {
1149            SaxDispatcher::comment(&*c.sax, c.userData, buf);
1150        }
1151        if !buf.is_null() {
1152            xmlFreeImpl(buf as *mut c_void);
1153        }
1154    }
1155}
1156
1157/// Dispatch a SAX processingInstruction event.
1158unsafe fn pi_sax_pi(ctxt: *mut _xmlParserCtxt, target: *const xmlChar, data: &[u8]) {
1159    unsafe {
1160        let c = &*ctxt;
1161        if c.sax.is_null() || c.disableSAX != 0 {
1162            return;
1163        }
1164        let buf = if data.is_empty() {
1165            ptr::null()
1166        } else {
1167            pi_strndup_bytes(data.as_ptr(), data.len())
1168        };
1169        if data.is_empty() || !buf.is_null() {
1170            SaxDispatcher::processing_instruction(&*c.sax, c.userData, target, buf);
1171        }
1172        if !buf.is_null() {
1173            xmlFreeImpl(buf as *mut c_void);
1174        }
1175    }
1176}
1177
1178/// Dispatch a SAX start-element event, preferring SAX2 (`startElementNs`)
1179/// over SAX1 (`startElement`) — upstream SAX2.c behaviour.
1180///
1181/// `atts` is the SAX1 attribute array (`[name, value, ...]`, `nbatts`
1182/// entries). The SAX2 array is derived from it: `xmlns` attributes become
1183/// namespace declarations, everything else becomes attributes.
1184unsafe fn pi_dispatch_start_element(
1185    ctxt: *mut _xmlParserCtxt,
1186    qname: *const xmlChar,
1187    atts: *mut *const xmlChar,
1188    nbatts: usize,
1189) {
1190    unsafe {
1191        let c = &*ctxt;
1192        if c.sax.is_null() || c.disableSAX != 0 {
1193            return;
1194        }
1195        let sax = &*c.sax;
1196        let (local, prefix) = pi_split_qname(qname);
1197        if let Some(cb2) = sax.startElementNs {
1198            let nattr = nbatts / 2;
1199            let mut namespaces: Vec<*const xmlChar> = Vec::new();
1200            let mut attrs2: Vec<*const xmlChar> = Vec::new();
1201            for k in 0..nattr {
1202                let aname = *atts.add(k * 2);
1203                let aval = *atts.add(k * 2 + 1);
1204                if aname.is_null() {
1205                    continue;
1206                }
1207                let (alocal, apref) = pi_split_qname(aname);
1208                if apref.is_null() && pi_cstr_eq(aname, b"xmlns") {
1209                    namespaces.push(ptr::null());
1210                    namespaces.push(aval);
1211                } else if !apref.is_null() && pi_cstr_eq(apref, b"xmlns") {
1212                    namespaces.push(alocal);
1213                    namespaces.push(aval);
1214                } else {
1215                    attrs2.push(alocal);
1216                    attrs2.push(apref);
1217                    attrs2.push(ptr::null());
1218                    attrs2.push(aval);
1219                    attrs2.push(aval.add(crate::abi::exports_xml2::xmlStrlen(aval) as usize));
1220                }
1221            }
1222            cb2(
1223                c.userData,
1224                local,
1225                prefix,
1226                ptr::null(),
1227                (namespaces.len() / 2) as c_int,
1228                namespaces.as_mut_ptr(),
1229                (attrs2.len() / 5) as c_int,
1230                0,
1231                attrs2.as_mut_ptr(),
1232            );
1233        } else if let Some(cb1) = sax.startElement {
1234            let sa1 = if nbatts > 0 {
1235                // ensure NULL-terminated pair list
1236                let mut arr: Vec<*const xmlChar> = Vec::with_capacity(nbatts + 2);
1237                for i in 0..nbatts {
1238                    arr.push(*atts.add(i));
1239                }
1240                arr.push(ptr::null());
1241                arr.push(ptr::null());
1242                arr.as_mut_ptr()
1243            } else {
1244                ptr::null_mut()
1245            };
1246            cb1(c.userData, qname, sa1);
1247        }
1248    }
1249}
1250
1251/// Dispatch a SAX end-element event, preferring SAX2 (`endElementNs`).
1252unsafe fn pi_dispatch_end_element(ctxt: *mut _xmlParserCtxt, qname: *const xmlChar) {
1253    unsafe {
1254        let c = &*ctxt;
1255        if c.sax.is_null() || c.disableSAX != 0 {
1256            return;
1257        }
1258        let sax = &*c.sax;
1259        let (local, prefix) = pi_split_qname(qname);
1260        if let Some(cb2) = sax.endElementNs {
1261            cb2(c.userData, local, prefix, ptr::null());
1262        } else if let Some(cb1) = sax.endElement {
1263            cb1(c.userData, qname);
1264        }
1265    }
1266}
1267
1268// ═══════════════════════════════════════════════════════════════════════════════
1269// Core parsing primitives (ports of parser.c / parserInternals.c)
1270// ═══════════════════════════════════════════════════════════════════════════════
1271
1272/// `xmlParseName` — returns a malloc'd copy of the name (upstream returns
1273/// a dict pointer; with a NULL dict upstream copies with xmlStrdup too).
1274unsafe fn pi_parse_name(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
1275    unsafe {
1276        let input = pi_input(ctxt);
1277        if input.is_null() || (*input).cur.is_null() {
1278            return ptr::null();
1279        }
1280        let in_ptr = (*input).cur;
1281        let end = (*input).end;
1282        let first = *in_ptr;
1283
1284        // Accelerator for simple ASCII names.
1285        if (first.is_ascii_lowercase()
1286            || first.is_ascii_uppercase()
1287            || first == b'_'
1288            || first == b':')
1289            && in_ptr < end
1290        {
1291            let mut p = in_ptr.add(1);
1292            while p < end {
1293                let b = *p;
1294                if b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b':' || b == b'.' {
1295                    p = p.add(1);
1296                } else {
1297                    break;
1298                }
1299            }
1300            if p >= end || (*p > 0 && *p < 0x80) {
1301                let count = p.offset_from(in_ptr) as usize;
1302                if count > 0 {
1303                    let ret = pi_strndup_bytes(in_ptr, count);
1304                    if ret.is_null() {
1305                        pi_err_memory(ctxt);
1306                        return ptr::null();
1307                    }
1308                    (*input).cur = p;
1309                    (*input).col += count as c_int;
1310                    return ret;
1311                }
1312            }
1313        }
1314
1315        // Complex path: full Unicode handling.
1316        let start = (*input).cur;
1317        let (c, l) = pi_current_char(ctxt);
1318        if !pi_is_name_start_char(c) {
1319            return ptr::null();
1320        }
1321        let mut len = l;
1322        pi_nextl(ctxt, l);
1323        loop {
1324            let (c2, l2) = pi_current_char(ctxt);
1325            if !pi_is_name_char(c2) {
1326                break;
1327            }
1328            len += l2;
1329            pi_nextl(ctxt, l2);
1330        }
1331        let ret = pi_strndup_bytes(start, len);
1332        if ret.is_null() {
1333            pi_err_memory(ctxt);
1334        }
1335        ret
1336    }
1337}
1338
1339/// `xmlParseNmtoken` — returns a malloc'd copy.
1340unsafe fn pi_parse_nmtoken(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
1341    unsafe {
1342        let mut buf: Vec<u8> = Vec::new();
1343        loop {
1344            let (c, l) = pi_current_char(ctxt);
1345            if !pi_is_name_char(c) {
1346                break;
1347            }
1348            pi_push_codepoint(&mut buf, c);
1349            pi_nextl(ctxt, l);
1350        }
1351        if buf.is_empty() {
1352            return ptr::null_mut();
1353        }
1354        let ret = pi_strndup_bytes(buf.as_ptr(), buf.len());
1355        if ret.is_null() {
1356            pi_err_memory(ctxt);
1357        }
1358        ret
1359    }
1360}
1361
1362/// `xmlParseNameAndCompare` — fast path returning `1` on match.
1363unsafe fn pi_parse_name_and_compare(
1364    ctxt: *mut _xmlParserCtxt,
1365    other: *const xmlChar,
1366) -> *const xmlChar {
1367    unsafe {
1368        let input = pi_input(ctxt);
1369        if input.is_null() || other.is_null() {
1370            return ptr::null();
1371        }
1372        let mut in_ptr = (*input).cur;
1373        let mut cmp = other;
1374        while !in_ptr.is_null() && *in_ptr != 0 && *in_ptr == *cmp {
1375            in_ptr = in_ptr.add(1);
1376            cmp = cmp.add(1);
1377        }
1378        if *cmp == 0 && (*in_ptr == b'>' || pi_is_blank_ch(*in_ptr)) {
1379            (*input).col += in_ptr.offset_from((*input).cur) as c_int;
1380            (*input).cur = in_ptr;
1381            return std::ptr::dangling::<xmlChar>();
1382        }
1383        let ret = pi_parse_name(ctxt);
1384        if !ret.is_null() && crate::abi::exports_xml2::xmlStrcmp(ret, other) == 0 {
1385            xmlFreeImpl(ret as *mut c_void);
1386            return std::ptr::dangling::<xmlChar>();
1387        }
1388        ret
1389    }
1390}
1391
1392/// `xmlParseCharRef` — parse `&#...;` / `&#x...;`, consuming the '&'.
1393unsafe fn pi_parse_char_ref(ctxt: *mut _xmlParserCtxt) -> c_int {
1394    unsafe {
1395        let mut val: c_int = 0;
1396        let mut count: c_int = 0;
1397        if pi_raw(ctxt) == b'&' && pi_nxt(ctxt, 1) == b'#' && pi_nxt(ctxt, 2) == b'x' {
1398            pi_skip(ctxt, 3);
1399            while pi_raw(ctxt) != b';' && !pi_stopped(ctxt) {
1400                if count > 20 {
1401                    count = 0;
1402                }
1403                let c = pi_raw(ctxt);
1404                if c.is_ascii_digit() {
1405                    val = val * 16 + (c - b'0') as c_int;
1406                } else if (b'a'..=b'f').contains(&c) {
1407                    val = val * 16 + (c - b'a') as c_int + 10;
1408                } else if (b'A'..=b'F').contains(&c) {
1409                    val = val * 16 + (c - b'A') as c_int + 10;
1410                } else {
1411                    pi_fatal_err(ctxt, XML_ERR_INVALID_HEX_CHARREF);
1412                    val = 0;
1413                    break;
1414                }
1415                if val > 0x110000 {
1416                    val = 0x110000;
1417                }
1418                pi_next1(ctxt);
1419                count += 1;
1420            }
1421            if pi_raw(ctxt) == b';' {
1422                pi_next1(ctxt);
1423            }
1424        } else if pi_raw(ctxt) == b'&' && pi_nxt(ctxt, 1) == b'#' {
1425            pi_skip(ctxt, 2);
1426            while pi_raw(ctxt) != b';' {
1427                if count > 20 {
1428                    count = 0;
1429                }
1430                let c = pi_raw(ctxt);
1431                if c.is_ascii_digit() {
1432                    val = val * 10 + (c - b'0') as c_int;
1433                } else {
1434                    pi_fatal_err(ctxt, XML_ERR_INVALID_DEC_CHARREF);
1435                    val = 0;
1436                    break;
1437                }
1438                if val > 0x110000 {
1439                    val = 0x110000;
1440                }
1441                pi_next1(ctxt);
1442                count += 1;
1443            }
1444            if pi_raw(ctxt) == b';' {
1445                pi_next1(ctxt);
1446            }
1447        } else {
1448            if pi_raw(ctxt) == b'&' {
1449                pi_skip(ctxt, 1);
1450            }
1451            pi_fatal_err(ctxt, XML_ERR_INVALID_CHARREF);
1452        }
1453
1454        // [WFC: Legal Character]
1455        if val >= 0x110000 {
1456            pi_fatal_err(ctxt, XML_ERR_INVALID_CHAR);
1457            val = 0xFFFD;
1458        } else if !pi_is_char(val) {
1459            pi_fatal_err(ctxt, XML_ERR_INVALID_CHAR);
1460        }
1461        val
1462    }
1463}
1464
1465/// `xmlParseEntityRef` — parse `&name;`, returning the entity.
1466unsafe fn pi_parse_entity_ref(ctxt: *mut _xmlParserCtxt) -> *mut _xmlEntity {
1467    unsafe {
1468        if ctxt.is_null() {
1469            return ptr::null_mut();
1470        }
1471        let name = pi_parse_entity_ref_name(ctxt);
1472        if name.is_null() {
1473            return ptr::null_mut();
1474        }
1475        let ent = pi_lookup_general_entity(ctxt, name);
1476        xmlFreeImpl(name as *mut c_void);
1477        ent
1478    }
1479}
1480
1481/// `xmlParseEntityRefInternal` — parse `&name;`, returning the name.
1482unsafe fn pi_parse_entity_ref_name(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
1483    unsafe {
1484        if pi_raw(ctxt) != b'&' {
1485            return ptr::null();
1486        }
1487        pi_next1(ctxt);
1488        let name = pi_parse_name(ctxt);
1489        if name.is_null() {
1490            pi_fatal_err(ctxt, XML_ERR_ENTITYREF_NO_NAME);
1491            return ptr::null();
1492        }
1493        if pi_raw(ctxt) != b';' {
1494            pi_fatal_err(ctxt, XML_ERR_ENTITYREF_SEMICOL_MISSING);
1495            xmlFreeImpl(name as *mut c_void);
1496            return ptr::null();
1497        }
1498        pi_next1(ctxt);
1499        name
1500    }
1501}
1502
1503/// `xmlParseEntityValue` — parse a quoted entity value.
1504unsafe fn pi_parse_entity_value(
1505    ctxt: *mut _xmlParserCtxt,
1506    orig: *mut *mut xmlChar,
1507) -> *mut xmlChar {
1508    unsafe {
1509        let quote = pi_raw(ctxt);
1510        if quote != b'"' && quote != b'\'' {
1511            pi_fatal_err(ctxt, XML_ERR_ENTITY_NOT_STARTED);
1512            return ptr::null_mut();
1513        }
1514        let start = pi_cur_ptr(ctxt);
1515        pi_next1(ctxt);
1516        let mut len: usize = 0;
1517        loop {
1518            if pi_stopped(ctxt) {
1519                return ptr::null_mut();
1520            }
1521            let input = pi_input(ctxt);
1522            if input.is_null() || (*input).cur >= (*input).end {
1523                pi_fatal_err(ctxt, XML_ERR_ENTITY_NOT_FINISHED);
1524                return ptr::null_mut();
1525            }
1526            let c = pi_raw(ctxt);
1527            if c == 0 {
1528                pi_fatal_err(ctxt, XML_ERR_INVALID_CHAR);
1529                return ptr::null_mut();
1530            }
1531            if c == quote {
1532                break;
1533            }
1534            pi_next1(ctxt);
1535            len += 1;
1536        }
1537        if !orig.is_null() {
1538            *orig = pi_strndup_bytes(start, len);
1539        }
1540        let val = pi_strndup_bytes(start, len);
1541        pi_next1(ctxt);
1542        val
1543    }
1544}
1545
1546/// `xmlParseAttValue` — parse an attribute value (entity references are
1547/// preserved as `&name;` unless substitution is enabled).
1548unsafe fn pi_parse_att_value(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
1549    unsafe {
1550        if ctxt.is_null() || pi_input(ctxt).is_null() {
1551            return ptr::null_mut();
1552        }
1553        let quote = pi_raw(ctxt);
1554        if quote != b'"' && quote != b'\'' {
1555            pi_fatal_err(ctxt, XML_ERR_ATTRIBUTE_NOT_STARTED);
1556            return ptr::null_mut();
1557        }
1558        pi_next1(ctxt);
1559        let replace_entities = (*ctxt).replaceEntities != 0;
1560        let mut out: Vec<u8> = Vec::new();
1561        loop {
1562            if pi_stopped(ctxt) {
1563                return ptr::null_mut();
1564            }
1565            let input = pi_input(ctxt);
1566            if input.is_null() {
1567                return ptr::null_mut();
1568            }
1569            if (*input).cur >= (*input).end {
1570                pi_fatal_err(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED);
1571                return ptr::null_mut();
1572            }
1573            let c = pi_raw(ctxt);
1574            if c == quote {
1575                break;
1576            }
1577            if c >= 0x80 {
1578                let (ch, l) = pi_current_char(ctxt);
1579                if ch == 0 {
1580                    pi_fatal_err(ctxt, XML_ERR_INVALID_CHAR);
1581                    return ptr::null_mut();
1582                }
1583                pi_push_codepoint(&mut out, ch);
1584                pi_nextl(ctxt, l);
1585            } else if c == b'&' {
1586                if pi_nxt(ctxt, 1) == b'#' {
1587                    let val = pi_parse_char_ref(ctxt);
1588                    if val == 0 {
1589                        return ptr::null_mut();
1590                    }
1591                    if val == b'&' as c_int && !replace_entities {
1592                        out.extend_from_slice(b"&#38;");
1593                    } else {
1594                        pi_push_codepoint(&mut out, val);
1595                    }
1596                } else {
1597                    let name = pi_parse_entity_ref_name(ctxt);
1598                    if name.is_null() {
1599                        return ptr::null_mut();
1600                    }
1601                    let ent = pi_lookup_general_entity(ctxt, name);
1602                    let mut expanded = false;
1603                    if !ent.is_null() {
1604                        let etype = (*ent).etype;
1605                        let content = (*ent).content;
1606                        if !content.is_null() {
1607                            if etype == XML_INTERNAL_PREDEFINED_ENTITY as c_int {
1608                                if *content == b'&' && !replace_entities {
1609                                    out.extend_from_slice(b"&#38;");
1610                                } else {
1611                                    let l = crate::abi::exports_xml2::xmlStrlen(content) as usize;
1612                                    out.extend_from_slice(core::slice::from_raw_parts(content, l));
1613                                }
1614                                expanded = true;
1615                            } else if replace_entities
1616                                && etype == XML_INTERNAL_GENERAL_ENTITY as c_int
1617                            {
1618                                let l = crate::abi::exports_xml2::xmlStrlen(content) as usize;
1619                                out.extend_from_slice(core::slice::from_raw_parts(content, l));
1620                                expanded = true;
1621                            }
1622                        }
1623                    }
1624                    if !expanded {
1625                        out.push(b'&');
1626                        let l = crate::abi::exports_xml2::xmlStrlen(name) as usize;
1627                        out.extend_from_slice(core::slice::from_raw_parts(name, l));
1628                        out.push(b';');
1629                    }
1630                    xmlFreeImpl(name as *mut c_void);
1631                }
1632            } else {
1633                if c == b'<' {
1634                    pi_fatal_err(ctxt, XML_ERR_LT_IN_ATTRIBUTE);
1635                }
1636                if c < 0x20 {
1637                    // Whitespace is converted to space; CRLF collapses.
1638                    out.push(b' ');
1639                    if c == b'\r' && pi_nxt(ctxt, 1) == b'\n' {
1640                        pi_next1(ctxt);
1641                    }
1642                } else {
1643                    out.push(c);
1644                }
1645                pi_next1(ctxt);
1646            }
1647        }
1648        pi_next1(ctxt);
1649        out.push(0);
1650
1651        pi_strndup_bytes(out.as_ptr(), out.len() - 1)
1652    }
1653}
1654
1655/// `xmlParseSystemLiteral`.
1656unsafe fn pi_parse_system_literal(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
1657    unsafe {
1658        let stop = if pi_raw(ctxt) == b'"' {
1659            pi_next1(ctxt);
1660            b'"'
1661        } else if pi_raw(ctxt) == b'\'' {
1662            pi_next1(ctxt);
1663            b'\''
1664        } else {
1665            pi_fatal_err(ctxt, XML_ERR_LITERAL_NOT_STARTED);
1666            return ptr::null_mut();
1667        };
1668        let mut buf: Vec<u8> = Vec::new();
1669        loop {
1670            let (cur, l) = pi_current_char_recover(ctxt);
1671            if !pi_is_char(cur) || cur == stop as c_int {
1672                break;
1673            }
1674            pi_push_codepoint(&mut buf, cur);
1675            pi_nextl(ctxt, l);
1676        }
1677        let cur = pi_raw(ctxt);
1678        if !pi_is_char(cur as c_int) {
1679            pi_fatal_err(ctxt, XML_ERR_LITERAL_NOT_FINISHED);
1680        } else if cur == stop {
1681            pi_next1(ctxt);
1682        }
1683        pi_strndup_bytes(buf.as_ptr(), buf.len())
1684    }
1685}
1686
1687/// `xmlParsePubidLiteral`.
1688unsafe fn pi_parse_pubid_literal(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
1689    unsafe {
1690        let stop = if pi_raw(ctxt) == b'"' {
1691            pi_next1(ctxt);
1692            b'"'
1693        } else if pi_raw(ctxt) == b'\'' {
1694            pi_next1(ctxt);
1695            b'\''
1696        } else {
1697            pi_fatal_err(ctxt, XML_ERR_LITERAL_NOT_STARTED);
1698            return ptr::null_mut();
1699        };
1700        let mut buf: Vec<u8> = Vec::new();
1701        loop {
1702            let cur = pi_raw(ctxt);
1703            if !pi_is_pubidchar(cur) || cur == stop {
1704                break;
1705            }
1706            buf.push(cur);
1707            pi_next1(ctxt);
1708        }
1709        if pi_raw(ctxt) != stop {
1710            pi_fatal_err(ctxt, XML_ERR_LITERAL_NOT_FINISHED);
1711        } else {
1712            pi_next1(ctxt);
1713        }
1714        pi_strndup_bytes(buf.as_ptr(), buf.len())
1715    }
1716}
1717
1718/// `xmlParseQuotedString` — parse and return a quoted string.
1719unsafe fn pi_parse_quoted_string(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
1720    unsafe {
1721        if ctxt.is_null() || pi_input(ctxt).is_null() {
1722            return ptr::null_mut();
1723        }
1724        let ret = pi_parse_att_value(ctxt);
1725        if ret.is_null() {
1726            pi_fatal_err(ctxt, XML_ERR_STRING_NOT_STARTED);
1727        }
1728        ret
1729    }
1730}
1731
1732/// `xmlParseExternalID` — returns the URI; sets `*publicId` if PUBLIC.
1733unsafe fn pi_parse_external_id(
1734    ctxt: *mut _xmlParserCtxt,
1735    public_id: *mut *mut xmlChar,
1736    strict: c_int,
1737) -> *mut xmlChar {
1738    unsafe {
1739        *public_id = ptr::null_mut();
1740        let mut uri: *mut xmlChar = ptr::null_mut();
1741        if pi_cmp6(ctxt, b"SYSTEM") {
1742            pi_skip(ctxt, 6);
1743            pi_skip_blanks(ctxt);
1744            uri = pi_parse_system_literal(ctxt);
1745            if uri.is_null() {
1746                pi_fatal_err(ctxt, XML_ERR_URI_REQUIRED);
1747            }
1748        } else if pi_cmp6(ctxt, b"PUBLIC") {
1749            pi_skip(ctxt, 6);
1750            pi_skip_blanks(ctxt);
1751            *public_id = pi_parse_pubid_literal(ctxt);
1752            if public_id.is_null() || (*public_id).is_null() {
1753                pi_fatal_err(ctxt, XML_ERR_PUBID_REQUIRED);
1754            }
1755            if strict != 0 {
1756                if pi_skip_blanks(ctxt) == 0 {
1757                    pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
1758                }
1759            } else {
1760                if pi_skip_blanks(ctxt) == 0 {
1761                    return ptr::null_mut();
1762                }
1763                if pi_raw(ctxt) != b'\'' && pi_raw(ctxt) != b'"' {
1764                    return ptr::null_mut();
1765                }
1766            }
1767            uri = pi_parse_system_literal(ctxt);
1768            if uri.is_null() {
1769                pi_fatal_err(ctxt, XML_ERR_URI_REQUIRED);
1770            }
1771        }
1772        uri
1773    }
1774}
1775
1776/// `xmlParsePITarget`.
1777unsafe fn pi_parse_pi_target(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
1778    unsafe {
1779        let name = pi_parse_name(ctxt);
1780        if name.is_null() {
1781            return ptr::null();
1782        }
1783        let len = crate::abi::exports_xml2::xmlStrlen(name) as usize;
1784        if len >= 3 {
1785            let c0 = *name;
1786            let c1 = *name.add(1);
1787            let c2 = *name.add(2);
1788            if (c0 == b'x' || c0 == b'X')
1789                && (c1 == b'm' || c1 == b'M')
1790                && (c2 == b'l' || c2 == b'L')
1791            {
1792                // Reserved "xml*" names are reported but still returned.
1793                if len == 3 || !(c0 == b'x' && c1 == b'm' && c2 == b'l') {
1794                    pi_fatal_err(ctxt, XML_ERR_RESERVED_XML_NAME);
1795                }
1796            }
1797        }
1798        if !crate::abi::exports_xml2::xmlStrchr(name, b':').is_null() {
1799            // colons are forbidden from PI names (warning-level upstream)
1800        }
1801        name
1802    }
1803}
1804
1805/// `xmlParsePI`.
1806unsafe fn pi_parse_pi(ctxt: *mut _xmlParserCtxt) {
1807    unsafe {
1808        if pi_raw(ctxt) == b'<' && pi_nxt(ctxt, 1) == b'?' {
1809            pi_skip(ctxt, 2);
1810            let target = pi_parse_pi_target(ctxt);
1811            if !target.is_null() {
1812                if pi_raw(ctxt) == b'?' && pi_nxt(ctxt, 1) == b'>' {
1813                    pi_skip(ctxt, 2);
1814                    pi_sax_pi(ctxt, target, &[]);
1815                    xmlFreeImpl(target as *mut c_void);
1816                    return;
1817                }
1818                if pi_skip_blanks(ctxt) == 0 {
1819                    pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
1820                }
1821                let mut buf: Vec<u8> = Vec::new();
1822                loop {
1823                    let (cur, l) = pi_current_char_recover(ctxt);
1824                    if !pi_is_char(cur) || (cur == b'?' as c_int && pi_nxt(ctxt, 1) == b'>') {
1825                        break;
1826                    }
1827                    pi_push_codepoint(&mut buf, cur);
1828                    pi_nextl(ctxt, l);
1829                }
1830                if pi_raw(ctxt) != b'?' {
1831                    pi_fatal_err(ctxt, XML_ERR_PI_NOT_FINISHED);
1832                } else {
1833                    pi_skip(ctxt, 2);
1834                    pi_sax_pi(ctxt, target, &buf);
1835                }
1836                xmlFreeImpl(target as *mut c_void);
1837            } else {
1838                pi_fatal_err(ctxt, XML_ERR_PI_NOT_STARTED);
1839            }
1840        }
1841    }
1842}
1843
1844/// `xmlParseComment` — parse a comment (assumes `<!--` position).
1845unsafe fn pi_parse_comment(ctxt: *mut _xmlParserCtxt) {
1846    unsafe {
1847        if pi_raw(ctxt) != b'<' || pi_nxt(ctxt, 1) != b'!' {
1848            return;
1849        }
1850        pi_skip(ctxt, 2);
1851        if pi_raw(ctxt) != b'-' || pi_nxt(ctxt, 1) != b'-' {
1852            return;
1853        }
1854        pi_skip(ctxt, 2);
1855
1856        let mut buf: Vec<u8> = Vec::new();
1857        let (mut q, ql) = pi_current_char_recover(ctxt);
1858        if q == 0 {
1859            pi_fatal_err(ctxt, XML_ERR_COMMENT_NOT_FINISHED);
1860            return;
1861        }
1862        pi_nextl(ctxt, ql);
1863        let (mut r, rl) = pi_current_char_recover(ctxt);
1864        if r == 0 {
1865            pi_fatal_err(ctxt, XML_ERR_COMMENT_NOT_FINISHED);
1866            return;
1867        }
1868        pi_nextl(ctxt, rl);
1869        let (mut cur, mut l) = pi_current_char_recover(ctxt);
1870        while pi_is_char(cur) && !(cur == b'>' as c_int && r == b'-' as c_int && q == b'-' as c_int)
1871        {
1872            if r == b'-' as c_int && q == b'-' as c_int {
1873                pi_fatal_err(ctxt, XML_ERR_HYPHEN_IN_COMMENT);
1874            }
1875            pi_push_codepoint(&mut buf, q);
1876            q = r;
1877            r = cur;
1878            pi_nextl(ctxt, l);
1879            let (c2, l2) = pi_current_char_recover(ctxt);
1880            cur = c2;
1881            l = l2;
1882        }
1883        if cur == 0 {
1884            pi_fatal_err(ctxt, XML_ERR_COMMENT_NOT_FINISHED);
1885            return;
1886        }
1887        if !pi_is_char(cur) {
1888            pi_fatal_err(ctxt, XML_ERR_INVALID_CHAR);
1889            return;
1890        }
1891        pi_next1(ctxt);
1892        pi_sax_comment(ctxt, &buf);
1893    }
1894}
1895
1896/// `xmlParseCharData` — parse character data until '<' or '&'.
1897unsafe fn pi_parse_char_data(ctxt: *mut _xmlParserCtxt, _cdata: c_int) {
1898    unsafe {
1899        let mut buf: Vec<u8> = Vec::new();
1900        loop {
1901            if pi_stopped(ctxt) {
1902                break;
1903            }
1904            let input = pi_input(ctxt);
1905            if input.is_null() {
1906                break;
1907            }
1908            if (*input).cur >= (*input).end {
1909                break;
1910            }
1911            let c = pi_raw(ctxt);
1912            if c == b'<' || c == b'&' {
1913                break;
1914            }
1915            buf.push(c);
1916            pi_next1(ctxt);
1917        }
1918        if buf.is_empty() {
1919            return;
1920        }
1921        // Whitespace-only data is ignorable when keepBlanks is off.
1922        let all_ws = buf.iter().all(|&b| pi_is_blank_ch(b));
1923        let keep_blanks = (*ctxt).keepBlanks != 0;
1924        let ignorable = all_ws && !keep_blanks;
1925        pi_sax_chars(ctxt, &buf, ignorable);
1926    }
1927}
1928
1929/// `xmlParseCDSect` — parse `<![CDATA[...]]>`.
1930unsafe fn pi_parse_cd_sect(ctxt: *mut _xmlParserCtxt) {
1931    unsafe {
1932        if pi_raw(ctxt) != b'<' || pi_nxt(ctxt, 1) != b'!' || pi_nxt(ctxt, 2) != b'[' {
1933            return;
1934        }
1935        pi_skip(ctxt, 3);
1936        if !pi_cmp6(ctxt, b"CDATA[") {
1937            return;
1938        }
1939        pi_skip(ctxt, 6);
1940
1941        let mut buf: Vec<u8> = Vec::new();
1942        let (mut r, rl) = pi_current_char_recover(ctxt);
1943        if !pi_is_char(r) {
1944            pi_fatal_err(ctxt, XML_ERR_CDATA_NOT_FINISHED);
1945            return;
1946        }
1947        pi_nextl(ctxt, rl);
1948        let (mut s, sl) = pi_current_char_recover(ctxt);
1949        if !pi_is_char(s) {
1950            pi_fatal_err(ctxt, XML_ERR_CDATA_NOT_FINISHED);
1951            return;
1952        }
1953        pi_nextl(ctxt, sl);
1954        let (mut cur, mut l) = pi_current_char_recover(ctxt);
1955        while pi_is_char(cur) && !(r == b']' as c_int && s == b']' as c_int && cur == b'>' as c_int)
1956        {
1957            pi_push_codepoint(&mut buf, r);
1958            r = s;
1959            s = cur;
1960            pi_nextl(ctxt, l);
1961            let (c2, l2) = pi_current_char_recover(ctxt);
1962            cur = c2;
1963            l = l2;
1964        }
1965        if cur != b'>' as c_int {
1966            pi_fatal_err(ctxt, XML_ERR_CDATA_NOT_FINISHED);
1967            return;
1968        }
1969        pi_nextl(ctxt, l);
1970
1971        // OK the buffer is to be consumed as cdata.
1972        let c = &*ctxt;
1973        if !c.sax.is_null() && c.disableSAX == 0 {
1974            let buf_p = pi_strndup_bytes(buf.as_ptr(), buf.len());
1975            if !buf_p.is_null() {
1976                let sax = &*c.sax;
1977                if sax.cdataBlock.is_some() && (c.options & XML_PARSE_NOCDATA) == 0 {
1978                    SaxDispatcher::cdata_block(sax, c.userData, buf_p, buf.len() as c_int);
1979                } else {
1980                    SaxDispatcher::characters(sax, c.userData, buf_p, buf.len() as c_int);
1981                }
1982                xmlFreeImpl(buf_p as *mut c_void);
1983            }
1984        }
1985    }
1986}
1987
1988/// `xmlParseReference` — handle `&...;` in content.
1989unsafe fn pi_parse_reference(ctxt: *mut _xmlParserCtxt) {
1990    unsafe {
1991        if pi_raw(ctxt) != b'&' {
1992            return;
1993        }
1994        // Simple case of a CharRef.
1995        if pi_nxt(ctxt, 1) == b'#' {
1996            let value = pi_parse_char_ref(ctxt);
1997            if value == 0 {
1998                return;
1999            }
2000            let mut out = Vec::new();
2001            pi_push_codepoint(&mut out, value);
2002            pi_sax_chars(ctxt, &out, false);
2003            return;
2004        }
2005
2006        // Entity reference.
2007        let name = pi_parse_entity_ref_name(ctxt);
2008        if name.is_null() {
2009            return;
2010        }
2011        let ent = pi_lookup_general_entity(ctxt, name);
2012        if ent.is_null() {
2013            // Reference to undeclared entity.
2014            let c = &*ctxt;
2015            if c.replaceEntities == 0
2016                && !c.sax.is_null()
2017                && c.disableSAX == 0
2018                && (*c.sax).reference.is_some()
2019            {
2020                SaxDispatcher::reference(&*c.sax, c.userData, name);
2021            }
2022            xmlFreeImpl(name as *mut c_void);
2023            return;
2024        }
2025        if (*ctxt).wellFormed == 0 {
2026            xmlFreeImpl(name as *mut c_void);
2027            return;
2028        }
2029
2030        // Special case of predefined entities.
2031        let etype = (*ent).etype;
2032        if etype == XML_INTERNAL_PREDEFINED_ENTITY as c_int {
2033            let val = (*ent).content;
2034            if !val.is_null() {
2035                let l = crate::abi::exports_xml2::xmlStrlen(val) as usize;
2036                let bytes = core::slice::from_raw_parts(val, l);
2037                pi_sax_chars(ctxt, bytes, false);
2038            }
2039            xmlFreeImpl(name as *mut c_void);
2040            return;
2041        }
2042
2043        let c = &*ctxt;
2044        if c.replaceEntities == 0 {
2045            // Create a reference.
2046            if !c.sax.is_null() && c.disableSAX == 0 && (*c.sax).reference.is_some() {
2047                SaxDispatcher::reference(&*c.sax, c.userData, (*ent).name);
2048            }
2049        } else if etype == XML_INTERNAL_GENERAL_ENTITY as c_int && !(*ent).content.is_null() {
2050            // Substitute the replacement text inline.
2051            let val = (*ent).content;
2052            let l = crate::abi::exports_xml2::xmlStrlen(val) as usize;
2053            let bytes = core::slice::from_raw_parts(val, l);
2054            pi_sax_chars(ctxt, bytes, false);
2055        }
2056        xmlFreeImpl(name as *mut c_void);
2057    }
2058}
2059
2060/// `xmlParsePEReference` — parse `%name;` and expand internal parameter
2061/// entities by pushing a new input.
2062unsafe fn pi_parse_pe_reference(ctxt: *mut _xmlParserCtxt) {
2063    unsafe {
2064        if pi_raw(ctxt) != b'%' {
2065            return;
2066        }
2067        pi_next1(ctxt);
2068        let name = pi_parse_name(ctxt);
2069        if name.is_null() {
2070            pi_fatal_err(ctxt, XML_ERR_PEREF_NO_NAME);
2071            return;
2072        }
2073        if pi_raw(ctxt) != b';' {
2074            pi_fatal_err(ctxt, XML_ERR_PEREF_SEMICOL_MISSING);
2075            xmlFreeImpl(name as *mut c_void);
2076            return;
2077        }
2078        pi_next1(ctxt);
2079
2080        let ent = pi_lookup_parameter_entity(ctxt, name);
2081        if ent.is_null() {
2082            pi_fatal_err(ctxt, XML_ERR_UNDECLARED_ENTITY);
2083            xmlFreeImpl(name as *mut c_void);
2084            return;
2085        }
2086        (*ctxt).hasPErefs = 1;
2087
2088        if (*ent).etype == XML_INTERNAL_PARAMETER_ENTITY as c_int && !(*ent).content.is_null() {
2089            let content = (*ent).content;
2090            let clen = crate::abi::exports_xml2::xmlStrlen(content) as usize;
2091            // The spec requires one leading and one trailing space.
2092            let total = clen + 3;
2093            let buf = xmlMallocImpl(total) as *mut xmlChar;
2094            if !buf.is_null() {
2095                *buf = b' ';
2096                ptr::copy_nonoverlapping(content, buf.add(1), clen);
2097                *buf.add(clen + 1) = b' ';
2098                *buf.add(clen + 2) = 0;
2099                let input = xmlMallocZero(size_of::<_xmlParserInput>()) as *mut _xmlParserInput;
2100                if !input.is_null() {
2101                    (*input).base = buf;
2102                    (*input).cur = buf;
2103                    (*input).end = buf.add(clen + 1);
2104                    (*input).line = (*(*ctxt).input).line;
2105                    (*input).col = (*(*ctxt).input).col;
2106                    (*input).entity = ent;
2107                    pi_input_push(ctxt, input);
2108                } else {
2109                    xmlFreeImpl(buf as *mut c_void);
2110                }
2111            }
2112        }
2113        xmlFreeImpl(name as *mut c_void);
2114    }
2115}
2116
2117/// `xmlParseNotationDecl`.
2118unsafe fn pi_parse_notation_decl(ctxt: *mut _xmlParserCtxt) {
2119    unsafe {
2120        if pi_raw(ctxt) != b'<' || pi_nxt(ctxt, 1) != b'!' {
2121            return;
2122        }
2123        pi_skip(ctxt, 2);
2124        if pi_cmp8(ctxt, b"NOTATION") {
2125            pi_skip(ctxt, 8);
2126            if pi_skip_blanks(ctxt) == 0 {
2127                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2128                return;
2129            }
2130            let name = pi_parse_name(ctxt);
2131            if name.is_null() {
2132                pi_fatal_err(ctxt, XML_ERR_NOTATION_NOT_STARTED);
2133                return;
2134            }
2135            if pi_skip_blanks(ctxt) == 0 {
2136                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2137                xmlFreeImpl(name as *mut c_void);
2138                return;
2139            }
2140            let mut pubid: *mut xmlChar = ptr::null_mut();
2141            let systemid = pi_parse_external_id(ctxt, &mut pubid, 0);
2142            pi_skip_blanks(ctxt);
2143            if pi_raw(ctxt) == b'>' {
2144                pi_next1(ctxt);
2145                let c = &*ctxt;
2146                if !c.sax.is_null() && c.disableSAX == 0 {
2147                    SaxDispatcher::notation_decl(&*c.sax, c.userData, name, pubid, systemid);
2148                }
2149            } else {
2150                pi_fatal_err(ctxt, XML_ERR_NOTATION_NOT_FINISHED);
2151            }
2152            if !systemid.is_null() {
2153                xmlFreeImpl(systemid as *mut c_void);
2154            }
2155            if !pubid.is_null() {
2156                xmlFreeImpl(pubid as *mut c_void);
2157            }
2158            xmlFreeImpl(name as *mut c_void);
2159        }
2160    }
2161}
2162
2163/// `xmlParseEntityDecl`.
2164unsafe fn pi_parse_entity_decl(ctxt: *mut _xmlParserCtxt) {
2165    unsafe {
2166        if pi_raw(ctxt) != b'<' || pi_nxt(ctxt, 1) != b'!' {
2167            return;
2168        }
2169        pi_skip(ctxt, 2);
2170        if pi_cmp6(ctxt, b"ENTITY") {
2171            pi_skip(ctxt, 6);
2172            if pi_skip_blanks(ctxt) == 0 {
2173                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2174            }
2175            let mut is_parameter = false;
2176            if pi_raw(ctxt) == b'%' {
2177                pi_next1(ctxt);
2178                if pi_skip_blanks(ctxt) == 0 {
2179                    pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2180                }
2181                is_parameter = true;
2182            }
2183            let name = pi_parse_name(ctxt);
2184            if name.is_null() {
2185                pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
2186                return;
2187            }
2188            if pi_skip_blanks(ctxt) == 0 {
2189                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2190            }
2191
2192            let mut value: *mut xmlChar = ptr::null_mut();
2193            let mut uri: *mut xmlChar = ptr::null_mut();
2194            let mut literal: *mut xmlChar = ptr::null_mut();
2195            let mut ndata: *const xmlChar = ptr::null();
2196            let mut orig: *mut xmlChar = ptr::null_mut();
2197
2198            if is_parameter {
2199                if pi_raw(ctxt) == b'"' || pi_raw(ctxt) == b'\'' {
2200                    value = pi_parse_entity_value(ctxt, &mut orig);
2201                    if !value.is_null() {
2202                        let c = &*ctxt;
2203                        if !c.sax.is_null() && c.disableSAX == 0 {
2204                            SaxDispatcher::entity_decl(
2205                                &*c.sax,
2206                                c.userData,
2207                                name,
2208                                XML_INTERNAL_PARAMETER_ENTITY as c_int,
2209                                ptr::null(),
2210                                ptr::null(),
2211                                value,
2212                            );
2213                        }
2214                    }
2215                } else {
2216                    uri = pi_parse_external_id(ctxt, &mut literal, 1);
2217                    if !uri.is_null() {
2218                        let c = &*ctxt;
2219                        if !c.sax.is_null() && c.disableSAX == 0 {
2220                            SaxDispatcher::entity_decl(
2221                                &*c.sax,
2222                                c.userData,
2223                                name,
2224                                XML_EXTERNAL_PARAMETER_ENTITY as c_int,
2225                                literal,
2226                                uri,
2227                                ptr::null_mut(),
2228                            );
2229                        }
2230                    }
2231                }
2232            } else {
2233                if pi_raw(ctxt) == b'"' || pi_raw(ctxt) == b'\'' {
2234                    value = pi_parse_entity_value(ctxt, &mut orig);
2235                    let c = &*ctxt;
2236                    if !c.sax.is_null() && c.disableSAX == 0 {
2237                        SaxDispatcher::entity_decl(
2238                            &*c.sax,
2239                            c.userData,
2240                            name,
2241                            XML_INTERNAL_GENERAL_ENTITY as c_int,
2242                            ptr::null(),
2243                            ptr::null(),
2244                            value,
2245                        );
2246                    }
2247                } else {
2248                    uri = pi_parse_external_id(ctxt, &mut literal, 1);
2249                    if pi_raw(ctxt) != b'>' && pi_skip_blanks(ctxt) == 0 {
2250                        pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2251                    }
2252                    if pi_cmp5(ctxt, b"NDATA") {
2253                        pi_skip(ctxt, 5);
2254                        if pi_skip_blanks(ctxt) == 0 {
2255                            pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2256                        }
2257                        ndata = pi_parse_name(ctxt);
2258                        let c = &*ctxt;
2259                        if !c.sax.is_null() && c.disableSAX == 0 {
2260                            SaxDispatcher::unparsed_entity_decl(
2261                                &*c.sax, c.userData, name, literal, uri, ndata,
2262                            );
2263                        }
2264                        if !ndata.is_null() {
2265                            xmlFreeImpl(ndata as *mut c_void);
2266                        }
2267                    } else {
2268                        let c = &*ctxt;
2269                        if !c.sax.is_null() && c.disableSAX == 0 {
2270                            SaxDispatcher::entity_decl(
2271                                &*c.sax,
2272                                c.userData,
2273                                name,
2274                                XML_EXTERNAL_GENERAL_PARSED_ENTITY as c_int,
2275                                literal,
2276                                uri,
2277                                ptr::null_mut(),
2278                            );
2279                        }
2280                    }
2281                }
2282            }
2283
2284            pi_skip_blanks(ctxt);
2285            if pi_raw(ctxt) != b'>' {
2286                pi_fatal_err(ctxt, XML_ERR_ENTITY_NOT_FINISHED);
2287            } else {
2288                pi_next1(ctxt);
2289            }
2290
2291            if !orig.is_null() {
2292                // Attach the raw entity value to the entity if it has none.
2293                let c = &*ctxt;
2294                let mut cur: *mut _xmlEntity = ptr::null_mut();
2295                if !c.sax.is_null() {
2296                    if is_parameter {
2297                        if (*c.sax).getParameterEntity.is_some() {
2298                            cur = SaxDispatcher::get_parameter_entity(&*c.sax, c.userData, name);
2299                        }
2300                    } else if (*c.sax).getEntity.is_some() {
2301                        cur = SaxDispatcher::get_entity(&*c.sax, c.userData, name);
2302                    }
2303                }
2304                if !cur.is_null() && (*cur).orig.is_null() {
2305                    (*cur).orig = orig;
2306                    orig = ptr::null_mut();
2307                }
2308            }
2309
2310            if !value.is_null() {
2311                xmlFreeImpl(value as *mut c_void);
2312            }
2313            if !uri.is_null() {
2314                xmlFreeImpl(uri as *mut c_void);
2315            }
2316            if !literal.is_null() {
2317                xmlFreeImpl(literal as *mut c_void);
2318            }
2319            if !orig.is_null() {
2320                xmlFreeImpl(orig as *mut c_void);
2321            }
2322            xmlFreeImpl(name as *mut c_void);
2323        }
2324    }
2325}
2326
2327/// `xmlParseDefaultDecl`.
2328unsafe fn pi_parse_default_decl(ctxt: *mut _xmlParserCtxt, value: *mut *mut xmlChar) -> c_int {
2329    unsafe {
2330        *value = ptr::null_mut();
2331        if pi_cmp9(ctxt, b"#REQUIRED") {
2332            pi_skip(ctxt, 9);
2333            return XML_ATTRIBUTE_REQUIRED;
2334        }
2335        if pi_cmp8(ctxt, b"#IMPLIED") {
2336            pi_skip(ctxt, 8);
2337            return XML_ATTRIBUTE_IMPLIED;
2338        }
2339        let mut val = XML_ATTRIBUTE_NONE;
2340        if pi_cmp6(ctxt, b"#FIXED") {
2341            pi_skip(ctxt, 6);
2342            val = XML_ATTRIBUTE_FIXED;
2343            if pi_skip_blanks(ctxt) == 0 {
2344                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2345            }
2346        }
2347        let ret = pi_parse_att_value(ctxt);
2348        if ret.is_null() {
2349            pi_fatal_err(ctxt, (*ctxt).errNo);
2350        } else {
2351            *value = ret;
2352        }
2353        val
2354    }
2355}
2356
2357/// `xmlParseAttributeType`.
2358unsafe fn pi_parse_attribute_type(
2359    ctxt: *mut _xmlParserCtxt,
2360    tree: *mut *mut _xmlEnumeration,
2361) -> c_int {
2362    unsafe {
2363        if pi_cmp5(ctxt, b"CDATA") {
2364            pi_skip(ctxt, 5);
2365            XML_ATTRIBUTE_CDATA
2366        } else if pi_cmp6(ctxt, b"IDREFS") {
2367            pi_skip(ctxt, 6);
2368            XML_ATTRIBUTE_IDREFS
2369        } else if pi_cmp5(ctxt, b"IDREF") {
2370            pi_skip(ctxt, 5);
2371            XML_ATTRIBUTE_IDREF
2372        } else if pi_raw(ctxt) == b'I' && pi_nxt(ctxt, 1) == b'D' {
2373            pi_skip(ctxt, 2);
2374            XML_ATTRIBUTE_ID
2375        } else if pi_cmp6(ctxt, b"ENTITY") {
2376            pi_skip(ctxt, 6);
2377            XML_ATTRIBUTE_ENTITY
2378        } else if pi_cmp8(ctxt, b"ENTITIES") {
2379            pi_skip(ctxt, 8);
2380            XML_ATTRIBUTE_ENTITIES
2381        } else if pi_cmp8(ctxt, b"NMTOKENS") {
2382            pi_skip(ctxt, 8);
2383            XML_ATTRIBUTE_NMTOKENS
2384        } else if pi_cmp7(ctxt, b"NMTOKEN") {
2385            pi_skip(ctxt, 7);
2386            XML_ATTRIBUTE_NMTOKEN
2387        } else {
2388            pi_parse_enumerated_type(ctxt, tree)
2389        }
2390    }
2391}
2392
2393/// `xmlParseNotationType`.
2394unsafe fn pi_parse_notation_type(ctxt: *mut _xmlParserCtxt) -> *mut _xmlEnumeration {
2395    unsafe {
2396        if pi_raw(ctxt) != b'(' {
2397            pi_fatal_err(ctxt, XML_ERR_NOTATION_NOT_STARTED);
2398            return ptr::null_mut();
2399        }
2400        let mut ret: *mut _xmlEnumeration = ptr::null_mut();
2401        let mut last: *mut _xmlEnumeration = ptr::null_mut();
2402        loop {
2403            pi_next1(ctxt);
2404            pi_skip_blanks(ctxt);
2405            let name = pi_parse_name(ctxt);
2406            if name.is_null() {
2407                pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
2408                pi_free_enumeration(ret);
2409                return ptr::null_mut();
2410            }
2411            let cur = pi_create_enumeration(name);
2412            // ownership of `name` transfers to the enumeration node
2413            if cur.is_null() {
2414                pi_err_memory(ctxt);
2415                xmlFreeImpl(name as *mut c_void);
2416                pi_free_enumeration(ret);
2417                return ptr::null_mut();
2418            }
2419            if last.is_null() {
2420                ret = cur;
2421            } else {
2422                (*last).next = cur;
2423            }
2424            last = cur;
2425            pi_skip_blanks(ctxt);
2426            if pi_raw(ctxt) != b'|' {
2427                break;
2428            }
2429        }
2430        if pi_raw(ctxt) != b')' {
2431            pi_fatal_err(ctxt, XML_ERR_NOTATION_NOT_FINISHED);
2432            pi_free_enumeration(ret);
2433            return ptr::null_mut();
2434        }
2435        pi_next1(ctxt);
2436        ret
2437    }
2438}
2439
2440/// `xmlParseEnumerationType`.
2441unsafe fn pi_parse_enumeration_type(ctxt: *mut _xmlParserCtxt) -> *mut _xmlEnumeration {
2442    unsafe {
2443        if pi_raw(ctxt) != b'(' {
2444            pi_fatal_err(ctxt, XML_ERR_ATTLIST_NOT_STARTED);
2445            return ptr::null_mut();
2446        }
2447        let mut ret: *mut _xmlEnumeration = ptr::null_mut();
2448        let mut last: *mut _xmlEnumeration = ptr::null_mut();
2449        loop {
2450            pi_next1(ctxt);
2451            pi_skip_blanks(ctxt);
2452            let name = pi_parse_nmtoken(ctxt);
2453            if name.is_null() {
2454                pi_fatal_err(ctxt, XML_ERR_NMTOKEN_REQUIRED);
2455                return ret;
2456            }
2457            let cur = pi_create_enumeration(name);
2458            // ownership of `name` transfers to the enumeration node
2459            if cur.is_null() {
2460                pi_err_memory(ctxt);
2461                xmlFreeImpl(name as *mut c_void);
2462                pi_free_enumeration(ret);
2463                return ptr::null_mut();
2464            }
2465            if last.is_null() {
2466                ret = cur;
2467            } else {
2468                (*last).next = cur;
2469            }
2470            last = cur;
2471            pi_skip_blanks(ctxt);
2472            if pi_raw(ctxt) != b'|' {
2473                break;
2474            }
2475        }
2476        if pi_raw(ctxt) != b')' {
2477            pi_fatal_err(ctxt, XML_ERR_ATTLIST_NOT_FINISHED);
2478            return ret;
2479        }
2480        pi_next1(ctxt);
2481        ret
2482    }
2483}
2484
2485/// `xmlParseEnumeratedType`.
2486unsafe fn pi_parse_enumerated_type(
2487    ctxt: *mut _xmlParserCtxt,
2488    tree: *mut *mut _xmlEnumeration,
2489) -> c_int {
2490    unsafe {
2491        if pi_cmp8(ctxt, b"NOTATION") {
2492            pi_skip(ctxt, 8);
2493            if pi_skip_blanks(ctxt) == 0 {
2494                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2495                return 0;
2496            }
2497            *tree = pi_parse_notation_type(ctxt);
2498            if tree.is_null() || (*tree).is_null() {
2499                return 0;
2500            }
2501            return XML_ATTRIBUTE_NOTATION;
2502        }
2503        *tree = pi_parse_enumeration_type(ctxt);
2504        if tree.is_null() || (*tree).is_null() {
2505            return 0;
2506        }
2507        XML_ATTRIBUTE_ENUMERATION
2508    }
2509}
2510
2511/// `xmlParseAttributeListDecl`.
2512unsafe fn pi_parse_attribute_list_decl(ctxt: *mut _xmlParserCtxt) {
2513    unsafe {
2514        if pi_raw(ctxt) != b'<' || pi_nxt(ctxt, 1) != b'!' {
2515            return;
2516        }
2517        pi_skip(ctxt, 2);
2518        if pi_cmp7(ctxt, b"ATTLIST") {
2519            pi_skip(ctxt, 7);
2520            if pi_skip_blanks(ctxt) == 0 {
2521                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2522            }
2523            let elem_name = pi_parse_name(ctxt);
2524            if elem_name.is_null() {
2525                pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
2526                return;
2527            }
2528            pi_skip_blanks(ctxt);
2529            while pi_raw(ctxt) != b'>' && !pi_stopped(ctxt) {
2530                let mut tree: *mut _xmlEnumeration = ptr::null_mut();
2531                let attr_name = pi_parse_name(ctxt);
2532                if attr_name.is_null() {
2533                    pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
2534                    break;
2535                }
2536                if pi_skip_blanks(ctxt) == 0 {
2537                    pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2538                    xmlFreeImpl(attr_name as *mut c_void);
2539                    break;
2540                }
2541                let type_ = pi_parse_attribute_type(ctxt, &mut tree);
2542                if type_ <= 0 {
2543                    xmlFreeImpl(attr_name as *mut c_void);
2544                    break;
2545                }
2546                if pi_skip_blanks(ctxt) == 0 {
2547                    pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2548                    if !tree.is_null() {
2549                        pi_free_enumeration(tree);
2550                    }
2551                    xmlFreeImpl(attr_name as *mut c_void);
2552                    break;
2553                }
2554                let mut default_value: *mut xmlChar = ptr::null_mut();
2555                let def = pi_parse_default_decl(ctxt, &mut default_value);
2556                if def <= 0 {
2557                    if !default_value.is_null() {
2558                        xmlFreeImpl(default_value as *mut c_void);
2559                    }
2560                    if !tree.is_null() {
2561                        pi_free_enumeration(tree);
2562                    }
2563                    xmlFreeImpl(attr_name as *mut c_void);
2564                    break;
2565                }
2566                if pi_raw(ctxt) != b'>' && pi_skip_blanks(ctxt) == 0 {
2567                    pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2568                    if !default_value.is_null() {
2569                        xmlFreeImpl(default_value as *mut c_void);
2570                    }
2571                    if !tree.is_null() {
2572                        pi_free_enumeration(tree);
2573                    }
2574                    xmlFreeImpl(attr_name as *mut c_void);
2575                    break;
2576                }
2577                let c = &*ctxt;
2578                if !c.sax.is_null() && c.disableSAX == 0 {
2579                    SaxDispatcher::attribute_decl(
2580                        &*c.sax,
2581                        c.userData,
2582                        elem_name,
2583                        attr_name,
2584                        type_,
2585                        def,
2586                        default_value,
2587                        tree,
2588                    );
2589                } else if !tree.is_null() {
2590                    pi_free_enumeration(tree);
2591                }
2592                if !default_value.is_null() {
2593                    xmlFreeImpl(default_value as *mut c_void);
2594                }
2595                xmlFreeImpl(attr_name as *mut c_void);
2596            }
2597            if pi_raw(ctxt) == b'>' {
2598                pi_next1(ctxt);
2599            }
2600            xmlFreeImpl(elem_name as *mut c_void);
2601        }
2602    }
2603}
2604
2605/// Create an `_xmlEnumeration` node taking ownership of `name`.
2606unsafe fn pi_create_enumeration(name: *const xmlChar) -> *mut _xmlEnumeration {
2607    unsafe {
2608        let cur = xmlMallocZero(size_of::<_xmlEnumeration>()) as *mut _xmlEnumeration;
2609        if !cur.is_null() {
2610            (*cur).name = name;
2611            (*cur).next = ptr::null_mut();
2612        }
2613        cur
2614    }
2615}
2616
2617/// `xmlFreeEnumeration` equivalent.
2618unsafe fn pi_free_enumeration(cur: *mut _xmlEnumeration) {
2619    unsafe {
2620        let mut cur = cur;
2621        while !cur.is_null() {
2622            let next = (*cur).next;
2623            if !(*cur).name.is_null() {
2624                xmlFreeImpl((*cur).name as *mut c_void);
2625            }
2626            xmlFreeImpl(cur as *mut c_void);
2627            cur = next;
2628        }
2629    }
2630}
2631
2632/// `xmlParseElementMixedContentDecl` — the leading '(' was already consumed.
2633unsafe fn pi_parse_element_mixed_content_decl(
2634    ctxt: *mut _xmlParserCtxt,
2635    _open_input_nr: c_int,
2636) -> *mut _xmlElementContent {
2637    unsafe {
2638        if pi_cmp7(ctxt, b"#PCDATA") {
2639            pi_skip(ctxt, 7);
2640            pi_skip_blanks(ctxt);
2641            if pi_raw(ctxt) == b')' {
2642                pi_next1(ctxt);
2643                let ret = create_content_model(ptr::null(), XML_ELEMENT_CONTENT_PCDATA as c_int);
2644                if pi_raw(ctxt) == b'*' {
2645                    if !ret.is_null() {
2646                        (*ret).ocur = XML_ELEMENT_CONTENT_MULT as c_int;
2647                    }
2648                    pi_next1(ctxt);
2649                }
2650                return ret;
2651            }
2652            let mut ret: *mut _xmlElementContent =
2653                create_content_model(ptr::null(), XML_ELEMENT_CONTENT_PCDATA as c_int);
2654            let mut cur = ret;
2655            let mut elem: *const xmlChar = ptr::null();
2656            while pi_raw(ctxt) == b'|' && !pi_stopped(ctxt) {
2657                pi_next1(ctxt);
2658                let n = create_content_model(ptr::null(), XML_ELEMENT_CONTENT_OR as c_int);
2659                if n.is_null() {
2660                    pi_err_memory(ctxt);
2661                    free_content_model(ret);
2662                    return ptr::null_mut();
2663                }
2664                if elem.is_null() {
2665                    (*n).c1 = cur;
2666                    if !cur.is_null() {
2667                        (*cur).parent = n;
2668                    }
2669                    ret = n;
2670                    cur = n;
2671                } else {
2672                    (*cur).c2 = n;
2673                    (*n).parent = cur;
2674                    let c1 = create_content_model(elem, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2675                    xmlFreeImpl(elem as *mut c_void);
2676                    (*n).c1 = c1;
2677                    if !c1.is_null() {
2678                        (*c1).parent = n;
2679                    }
2680                    cur = n;
2681                }
2682                pi_skip_blanks(ctxt);
2683                elem = pi_parse_name(ctxt);
2684                if elem.is_null() {
2685                    pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
2686                    free_content_model(ret);
2687                    return ptr::null_mut();
2688                }
2689                pi_skip_blanks(ctxt);
2690            }
2691            if pi_raw(ctxt) == b')' && pi_nxt(ctxt, 1) == b'*' {
2692                if !elem.is_null() {
2693                    let c2 = create_content_model(elem, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2694                    xmlFreeImpl(elem as *mut c_void);
2695                    if !cur.is_null() {
2696                        (*cur).c2 = c2;
2697                        if !c2.is_null() {
2698                            (*c2).parent = cur;
2699                        }
2700                    }
2701                }
2702                if !ret.is_null() {
2703                    (*ret).ocur = XML_ELEMENT_CONTENT_MULT as c_int;
2704                }
2705                pi_skip(ctxt, 2);
2706            } else {
2707                if !elem.is_null() {
2708                    xmlFreeImpl(elem as *mut c_void);
2709                }
2710                free_content_model(ret);
2711                pi_fatal_err(ctxt, XML_ERR_MIXED_NOT_STARTED);
2712                return ptr::null_mut();
2713            }
2714            return ret;
2715        }
2716        pi_fatal_err(ctxt, XML_ERR_PCDATA_REQUIRED);
2717        ptr::null_mut()
2718    }
2719}
2720
2721/// `xmlParseElementChildrenContentDeclPriv`.
2722unsafe fn pi_parse_element_children_content_decl_priv(
2723    ctxt: *mut _xmlParserCtxt,
2724    _open_input_nr: c_int,
2725    depth: c_int,
2726) -> *mut _xmlElementContent {
2727    unsafe {
2728        let max_depth = if (*ctxt).options & XML_PARSE_HUGE != 0 {
2729            2048
2730        } else {
2731            256
2732        };
2733        if depth > max_depth {
2734            pi_fatal_err(ctxt, XML_ERR_RESOURCE_LIMIT);
2735            return ptr::null_mut();
2736        }
2737        pi_skip_blanks(ctxt);
2738        let mut ret: *mut _xmlElementContent = ptr::null_mut();
2739        let mut cur: *mut _xmlElementContent = ptr::null_mut();
2740        let mut last: *mut _xmlElementContent = ptr::null_mut();
2741        let mut type_: u8 = 0;
2742
2743        if pi_raw(ctxt) == b'(' {
2744            pi_next1(ctxt);
2745            cur = pi_parse_element_children_content_decl_priv(ctxt, (*ctxt).inputNr, depth + 1);
2746            if cur.is_null() {
2747                return ptr::null_mut();
2748            }
2749            ret = cur;
2750        } else {
2751            let elem = pi_parse_name(ctxt);
2752            if elem.is_null() {
2753                pi_fatal_err(ctxt, XML_ERR_ELEMCONTENT_NOT_STARTED);
2754                return ptr::null_mut();
2755            }
2756            cur = create_content_model(elem, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2757            xmlFreeImpl(elem as *mut c_void);
2758            if cur.is_null() {
2759                pi_err_memory(ctxt);
2760                return ptr::null_mut();
2761            }
2762            ret = cur;
2763            if pi_raw(ctxt) == b'?' {
2764                (*cur).ocur = XML_ELEMENT_CONTENT_OPT as c_int;
2765                pi_next1(ctxt);
2766            } else if pi_raw(ctxt) == b'*' {
2767                (*cur).ocur = XML_ELEMENT_CONTENT_MULT as c_int;
2768                pi_next1(ctxt);
2769            } else if pi_raw(ctxt) == b'+' {
2770                (*cur).ocur = XML_ELEMENT_CONTENT_PLUS as c_int;
2771                pi_next1(ctxt);
2772            } else {
2773                (*cur).ocur = XML_ELEMENT_CONTENT_ONCE as c_int;
2774            }
2775        }
2776
2777        while !pi_stopped(ctxt) {
2778            pi_skip_blanks(ctxt);
2779            if pi_raw(ctxt) == b')' {
2780                break;
2781            }
2782            if pi_raw(ctxt) == b',' || pi_raw(ctxt) == b'|' {
2783                let sep = pi_raw(ctxt);
2784                if type_ == 0 {
2785                    type_ = sep;
2786                } else if type_ != sep {
2787                    pi_fatal_err(ctxt, XML_ERR_SEPARATOR_REQUIRED);
2788                    free_content_model(ret);
2789                    return ptr::null_mut();
2790                }
2791                pi_next1(ctxt);
2792                let op_type = if sep == b',' {
2793                    XML_ELEMENT_CONTENT_SEQ as c_int
2794                } else {
2795                    XML_ELEMENT_CONTENT_OR as c_int
2796                };
2797                let op = create_content_model(ptr::null(), op_type);
2798                if op.is_null() {
2799                    pi_err_memory(ctxt);
2800                    free_content_model(ret);
2801                    return ptr::null_mut();
2802                }
2803                if last.is_null() {
2804                    (*op).c1 = ret;
2805                    if !ret.is_null() {
2806                        (*ret).parent = op;
2807                    }
2808                    ret = op;
2809                    cur = op;
2810                } else {
2811                    (*cur).c2 = op;
2812                    (*op).parent = cur;
2813                    (*op).c1 = last;
2814                    if !last.is_null() {
2815                        (*last).parent = op;
2816                    }
2817                    cur = op;
2818                    last = ptr::null_mut();
2819                }
2820            } else {
2821                pi_fatal_err(ctxt, XML_ERR_ELEMCONTENT_NOT_FINISHED);
2822                free_content_model(ret);
2823                return ptr::null_mut();
2824            }
2825
2826            pi_skip_blanks(ctxt);
2827            if pi_raw(ctxt) == b'(' {
2828                pi_next1(ctxt);
2829                last =
2830                    pi_parse_element_children_content_decl_priv(ctxt, (*ctxt).inputNr, depth + 1);
2831                if last.is_null() {
2832                    free_content_model(ret);
2833                    return ptr::null_mut();
2834                }
2835            } else {
2836                let elem = pi_parse_name(ctxt);
2837                if elem.is_null() {
2838                    pi_fatal_err(ctxt, XML_ERR_ELEMCONTENT_NOT_STARTED);
2839                    free_content_model(ret);
2840                    return ptr::null_mut();
2841                }
2842                last = create_content_model(elem, XML_ELEMENT_CONTENT_ELEMENT as c_int);
2843                xmlFreeImpl(elem as *mut c_void);
2844                if last.is_null() {
2845                    pi_err_memory(ctxt);
2846                    free_content_model(ret);
2847                    return ptr::null_mut();
2848                }
2849                if pi_raw(ctxt) == b'?' {
2850                    (*last).ocur = XML_ELEMENT_CONTENT_OPT as c_int;
2851                    pi_next1(ctxt);
2852                } else if pi_raw(ctxt) == b'*' {
2853                    (*last).ocur = XML_ELEMENT_CONTENT_MULT as c_int;
2854                    pi_next1(ctxt);
2855                } else if pi_raw(ctxt) == b'+' {
2856                    (*last).ocur = XML_ELEMENT_CONTENT_PLUS as c_int;
2857                    pi_next1(ctxt);
2858                } else {
2859                    (*last).ocur = XML_ELEMENT_CONTENT_ONCE as c_int;
2860                }
2861            }
2862        }
2863
2864        if !cur.is_null() && !last.is_null() {
2865            (*cur).c2 = last;
2866            (*last).parent = cur;
2867        }
2868        pi_next1(ctxt);
2869        if pi_raw(ctxt) == b'?' {
2870            if !ret.is_null() {
2871                (*ret).ocur = if (*ret).ocur == XML_ELEMENT_CONTENT_PLUS as c_int
2872                    || (*ret).ocur == XML_ELEMENT_CONTENT_MULT as c_int
2873                {
2874                    XML_ELEMENT_CONTENT_MULT as c_int
2875                } else {
2876                    XML_ELEMENT_CONTENT_OPT as c_int
2877                };
2878            }
2879            pi_next1(ctxt);
2880        } else if pi_raw(ctxt) == b'*' {
2881            if !ret.is_null() {
2882                (*ret).ocur = XML_ELEMENT_CONTENT_MULT as c_int;
2883            }
2884            pi_next1(ctxt);
2885        } else if pi_raw(ctxt) == b'+' {
2886            if !ret.is_null() {
2887                (*ret).ocur = if (*ret).ocur == XML_ELEMENT_CONTENT_OPT as c_int
2888                    || (*ret).ocur == XML_ELEMENT_CONTENT_MULT as c_int
2889                {
2890                    XML_ELEMENT_CONTENT_MULT as c_int
2891                } else {
2892                    XML_ELEMENT_CONTENT_PLUS as c_int
2893                };
2894            }
2895            pi_next1(ctxt);
2896        }
2897        ret
2898    }
2899}
2900
2901/// `xmlParseElementContentDecl`.
2902unsafe fn pi_parse_element_content_decl(
2903    ctxt: *mut _xmlParserCtxt,
2904    name: *const xmlChar,
2905    result: *mut *mut _xmlElementContent,
2906) -> c_int {
2907    unsafe {
2908        *result = ptr::null_mut();
2909        if pi_raw(ctxt) != b'(' {
2910            pi_fatal_err(ctxt, XML_ERR_ELEMCONTENT_NOT_STARTED);
2911            return -1;
2912        }
2913        let open_input_nr = (*ctxt).inputNr;
2914        pi_next1(ctxt);
2915        pi_skip_blanks(ctxt);
2916
2917        let (tree, res) = if pi_cmp7(ctxt, b"#PCDATA") {
2918            (
2919                pi_parse_element_mixed_content_decl(ctxt, open_input_nr),
2920                XML_ELEMENT_TYPE_MIXED,
2921            )
2922        } else {
2923            (
2924                pi_parse_element_children_content_decl_priv(ctxt, open_input_nr, 1),
2925                XML_ELEMENT_TYPE_ELEMENT,
2926            )
2927        };
2928        if tree.is_null() {
2929            return -1;
2930        }
2931        pi_skip_blanks(ctxt);
2932        *result = tree;
2933        res
2934    }
2935}
2936
2937/// `xmlParseElementDecl`.
2938unsafe fn pi_parse_element_decl(ctxt: *mut _xmlParserCtxt) -> c_int {
2939    unsafe {
2940        let mut ret = -1;
2941        if pi_raw(ctxt) != b'<' || pi_nxt(ctxt, 1) != b'!' {
2942            return ret;
2943        }
2944        pi_skip(ctxt, 2);
2945        if pi_cmp7(ctxt, b"ELEMENT") {
2946            pi_skip(ctxt, 7);
2947            if pi_skip_blanks(ctxt) == 0 {
2948                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2949                return -1;
2950            }
2951            let name = pi_parse_name(ctxt);
2952            if name.is_null() {
2953                pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
2954                return -1;
2955            }
2956            if pi_skip_blanks(ctxt) == 0 {
2957                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
2958            }
2959            let mut content: *mut _xmlElementContent = ptr::null_mut();
2960            if pi_cmp5(ctxt, b"EMPTY") {
2961                pi_skip(ctxt, 5);
2962                ret = XML_ELEMENT_TYPE_EMPTY;
2963            } else if pi_raw(ctxt) == b'A' && pi_nxt(ctxt, 1) == b'N' && pi_nxt(ctxt, 2) == b'Y' {
2964                pi_skip(ctxt, 3);
2965                ret = XML_ELEMENT_TYPE_ANY;
2966            } else if pi_raw(ctxt) == b'(' {
2967                ret = pi_parse_element_content_decl(ctxt, name, &mut content);
2968                if ret <= 0 {
2969                    xmlFreeImpl(name as *mut c_void);
2970                    return -1;
2971                }
2972            } else {
2973                pi_fatal_err(ctxt, XML_ERR_ELEMCONTENT_NOT_STARTED);
2974                xmlFreeImpl(name as *mut c_void);
2975                return -1;
2976            }
2977
2978            pi_skip_blanks(ctxt);
2979            if pi_raw(ctxt) != b'>' {
2980                pi_fatal_err(ctxt, XML_ERR_GT_REQUIRED);
2981                if !content.is_null() {
2982                    free_content_model(content);
2983                }
2984            } else {
2985                pi_next1(ctxt);
2986                let c = &*ctxt;
2987                if !c.sax.is_null() && c.disableSAX == 0 && (*c.sax).elementDecl.is_some() {
2988                    if !content.is_null() {
2989                        (*content).parent = ptr::null_mut();
2990                    }
2991                    SaxDispatcher::element_decl(&*c.sax, c.userData, name, ret, content);
2992                    if !content.is_null() && (*content).parent.is_null() {
2993                        // Not plugged into a DTD — free it.
2994                        free_content_model(content);
2995                    }
2996                } else if !content.is_null() {
2997                    free_content_model(content);
2998                }
2999            }
3000            xmlFreeImpl(name as *mut c_void);
3001        }
3002        ret
3003    }
3004}
3005
3006/// `xmlParseMarkupDecl`.
3007unsafe fn pi_parse_markup_decl(ctxt: *mut _xmlParserCtxt) {
3008    unsafe {
3009        if pi_raw(ctxt) == b'<' {
3010            if pi_nxt(ctxt, 1) == b'!' {
3011                match pi_nxt(ctxt, 2) {
3012                    b'E' => {
3013                        if pi_nxt(ctxt, 3) == b'L' {
3014                            pi_parse_element_decl(ctxt);
3015                        } else if pi_nxt(ctxt, 3) == b'N' {
3016                            pi_parse_entity_decl(ctxt);
3017                        } else {
3018                            pi_skip(ctxt, 2);
3019                        }
3020                    }
3021                    b'A' => pi_parse_attribute_list_decl(ctxt),
3022                    b'N' => pi_parse_notation_decl(ctxt),
3023                    b'-' => pi_parse_comment(ctxt),
3024                    _ => {
3025                        pi_fatal_err(
3026                            ctxt,
3027                            if (*ctxt).inSubset == 2 {
3028                                XML_ERR_EXT_SUBSET_NOT_FINISHED
3029                            } else {
3030                                XML_ERR_INT_SUBSET_NOT_FINISHED
3031                            },
3032                        );
3033                        pi_skip(ctxt, 2);
3034                    }
3035                }
3036            } else if pi_nxt(ctxt, 1) == b'?' {
3037                pi_parse_pi(ctxt);
3038            }
3039        }
3040    }
3041}
3042
3043/// `xmlParseTextDecl`.
3044unsafe fn pi_parse_text_decl(ctxt: *mut _xmlParserCtxt) {
3045    unsafe {
3046        if pi_cmp5(ctxt, b"<?xml") && pi_is_blank_ch(pi_nxt(ctxt, 5)) {
3047            pi_skip(ctxt, 5);
3048        } else {
3049            pi_fatal_err(ctxt, XML_ERR_XMLDECL_NOT_STARTED);
3050            return;
3051        }
3052        if pi_skip_blanks(ctxt) == 0 {
3053            pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
3054        }
3055        let mut version = pi_parse_version_info(ctxt);
3056        if version.is_null() {
3057            version = crate::abi::exports_xml2::xmlStrdup(c"1.0".as_ptr() as *const xmlChar);
3058        } else if pi_skip_blanks(ctxt) == 0 {
3059            pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
3060        }
3061        let input = pi_input(ctxt);
3062        if !input.is_null() {
3063            (*input).version = version;
3064        } else if !version.is_null() {
3065            xmlFreeImpl(version as *mut c_void);
3066        }
3067        pi_parse_encoding_decl(ctxt);
3068        pi_skip_blanks(ctxt);
3069        if pi_raw(ctxt) == b'?' && pi_nxt(ctxt, 1) == b'>' {
3070            pi_skip(ctxt, 2);
3071        } else if pi_raw(ctxt) == b'>' {
3072            pi_fatal_err(ctxt, XML_ERR_XMLDECL_NOT_FINISHED);
3073            pi_next1(ctxt);
3074        } else {
3075            pi_fatal_err(ctxt, XML_ERR_XMLDECL_NOT_FINISHED);
3076            while !pi_stopped(ctxt) && pi_raw(ctxt) != 0 {
3077                let c = pi_raw(ctxt);
3078                pi_next1(ctxt);
3079                if c == b'>' {
3080                    break;
3081                }
3082            }
3083        }
3084    }
3085}
3086
3087/// `xmlParseExternalSubset`.
3088#[allow(clippy::while_immutable_condition)]
3089unsafe fn pi_parse_external_subset(
3090    ctxt: *mut _xmlParserCtxt,
3091    public_id: *const xmlChar,
3092    system_id: *const xmlChar,
3093) {
3094    unsafe {
3095        pi_ctxt_late_init(ctxt);
3096        if pi_cmp5(ctxt, b"<?xml") && pi_is_blank_ch(pi_nxt(ctxt, 5)) {
3097            pi_parse_text_decl(ctxt);
3098        }
3099        if (*ctxt).myDoc.is_null() {
3100            (*ctxt).myDoc = crate::xml::tree::new_doc(c"1.0".as_ptr() as *const xmlChar);
3101            if (*ctxt).myDoc.is_null() {
3102                pi_err_memory(ctxt);
3103                return;
3104            }
3105            (*(*ctxt).myDoc).properties |= XML_DOC_INTERNAL as c_int;
3106        }
3107        if (*(*ctxt).myDoc).intSubset.is_null() {
3108            create_int_subset((*ctxt).myDoc, ptr::null(), public_id, system_id);
3109        }
3110        (*ctxt).inSubset = 2;
3111        let old_input_nr = (*ctxt).inputNr;
3112        pi_skip_blanks(ctxt);
3113        while !pi_stopped(ctxt) {
3114            let input = pi_input(ctxt);
3115            if input.is_null() {
3116                break;
3117            }
3118            if (*input).cur >= (*input).end {
3119                if (*ctxt).inputNr <= old_input_nr {
3120                    pi_fatal_err(ctxt, XML_ERR_EXT_SUBSET_NOT_FINISHED);
3121                    break;
3122                }
3123                pi_pop_pe(ctxt);
3124            } else if pi_raw(ctxt) == b'<' && pi_nxt(ctxt, 1) == b'!' && pi_nxt(ctxt, 2) == b'[' {
3125                pi_parse_conditional_sections(ctxt);
3126            } else if pi_raw(ctxt) == b'<' && (pi_nxt(ctxt, 1) == b'!' || pi_nxt(ctxt, 1) == b'?') {
3127                pi_parse_markup_decl(ctxt);
3128            } else if pi_raw(ctxt) == b'%' {
3129                pi_parse_pe_reference(ctxt);
3130            } else {
3131                pi_fatal_err(ctxt, XML_ERR_EXT_SUBSET_NOT_FINISHED);
3132                while (*ctxt).inputNr > old_input_nr {
3133                    pi_pop_pe(ctxt);
3134                }
3135                break;
3136            }
3137            pi_skip_blanks(ctxt);
3138        }
3139    }
3140}
3141
3142/// `xmlParseConditionalSections`.
3143unsafe fn pi_parse_conditional_sections(ctxt: *mut _xmlParserCtxt) {
3144    unsafe {
3145        let old_input_nr = (*ctxt).inputNr;
3146        let mut depth: usize = 0;
3147        loop {
3148            if pi_stopped(ctxt) {
3149                return;
3150            }
3151            let input = pi_input(ctxt);
3152            if input.is_null() {
3153                return;
3154            }
3155            if (*input).cur >= (*input).end {
3156                if (*ctxt).inputNr <= old_input_nr {
3157                    pi_fatal_err(ctxt, XML_ERR_EXT_SUBSET_NOT_FINISHED);
3158                    return;
3159                }
3160                pi_pop_pe(ctxt);
3161            } else if pi_raw(ctxt) == b'<' && pi_nxt(ctxt, 1) == b'!' && pi_nxt(ctxt, 2) == b'[' {
3162                pi_skip(ctxt, 3);
3163                pi_skip_blanks(ctxt);
3164                if pi_cmp7(ctxt, b"INCLUDE") {
3165                    pi_skip(ctxt, 7);
3166                    pi_skip_blanks(ctxt);
3167                    if pi_raw(ctxt) != b'[' {
3168                        pi_fatal_err(ctxt, XML_ERR_CONDSEC_INVALID);
3169                        return;
3170                    }
3171                    pi_next1(ctxt);
3172                    depth += 1;
3173                } else if pi_cmp6(ctxt, b"IGNORE") {
3174                    pi_skip(ctxt, 6);
3175                    pi_skip_blanks(ctxt);
3176                    if pi_raw(ctxt) != b'[' {
3177                        pi_fatal_err(ctxt, XML_ERR_CONDSEC_INVALID);
3178                        return;
3179                    }
3180                    pi_next1(ctxt);
3181                    let mut ignore_depth: usize = 0;
3182                    loop {
3183                        if pi_stopped(ctxt) {
3184                            return;
3185                        }
3186                        let inp = pi_input(ctxt);
3187                        if inp.is_null() || (*inp).cur >= (*inp).end || pi_raw(ctxt) == 0 {
3188                            pi_fatal_err(ctxt, XML_ERR_CONDSEC_NOT_FINISHED);
3189                            return;
3190                        }
3191                        if pi_raw(ctxt) == b'<'
3192                            && pi_nxt(ctxt, 1) == b'!'
3193                            && pi_nxt(ctxt, 2) == b'['
3194                        {
3195                            pi_skip(ctxt, 3);
3196                            ignore_depth += 1;
3197                        } else if pi_raw(ctxt) == b']'
3198                            && pi_nxt(ctxt, 1) == b']'
3199                            && pi_nxt(ctxt, 2) == b'>'
3200                        {
3201                            pi_skip(ctxt, 3);
3202                            if ignore_depth == 0 {
3203                                break;
3204                            }
3205                            ignore_depth -= 1;
3206                        } else {
3207                            pi_next1(ctxt);
3208                        }
3209                    }
3210                } else {
3211                    pi_fatal_err(ctxt, XML_ERR_CONDSEC_INVALID_KEYWORD);
3212                    return;
3213                }
3214            } else if depth > 0
3215                && pi_raw(ctxt) == b']'
3216                && pi_nxt(ctxt, 1) == b']'
3217                && pi_nxt(ctxt, 2) == b'>'
3218            {
3219                depth -= 1;
3220                pi_skip(ctxt, 3);
3221            } else if pi_raw(ctxt) == b'<' && (pi_nxt(ctxt, 1) == b'!' || pi_nxt(ctxt, 1) == b'?') {
3222                pi_parse_markup_decl(ctxt);
3223            } else if pi_raw(ctxt) == b'%' {
3224                pi_parse_pe_reference(ctxt);
3225            } else {
3226                pi_fatal_err(ctxt, XML_ERR_EXT_SUBSET_NOT_FINISHED);
3227                return;
3228            }
3229            if depth == 0 {
3230                break;
3231            }
3232            pi_skip_blanks(ctxt);
3233        }
3234    }
3235}
3236
3237/// `xmlParseVersionNum`.
3238unsafe fn pi_parse_version_num(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
3239    unsafe {
3240        let mut buf: Vec<u8> = Vec::new();
3241        let mut cur = pi_raw(ctxt);
3242        if !cur.is_ascii_digit() {
3243            return ptr::null_mut();
3244        }
3245        buf.push(cur);
3246        pi_next1(ctxt);
3247        cur = pi_raw(ctxt);
3248        if cur != b'.' {
3249            return ptr::null_mut();
3250        }
3251        buf.push(cur);
3252        pi_next1(ctxt);
3253        cur = pi_raw(ctxt);
3254        while cur.is_ascii_digit() {
3255            buf.push(cur);
3256            pi_next1(ctxt);
3257            cur = pi_raw(ctxt);
3258        }
3259        pi_strndup_bytes(buf.as_ptr(), buf.len())
3260    }
3261}
3262
3263/// `xmlParseVersionInfo`.
3264unsafe fn pi_parse_version_info(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
3265    unsafe {
3266        if pi_cmp7(ctxt, b"version") {
3267            pi_skip(ctxt, 7);
3268            pi_skip_blanks(ctxt);
3269            if pi_raw(ctxt) != b'=' {
3270                pi_fatal_err(ctxt, XML_ERR_EQUAL_REQUIRED);
3271                return ptr::null_mut();
3272            }
3273            pi_next1(ctxt);
3274            pi_skip_blanks(ctxt);
3275            if pi_raw(ctxt) == b'"' {
3276                pi_next1(ctxt);
3277                let version = pi_parse_version_num(ctxt);
3278                if pi_raw(ctxt) != b'"' {
3279                    pi_fatal_err(ctxt, XML_ERR_STRING_NOT_CLOSED);
3280                } else {
3281                    pi_next1(ctxt);
3282                }
3283                return version;
3284            } else if pi_raw(ctxt) == b'\'' {
3285                pi_next1(ctxt);
3286                let version = pi_parse_version_num(ctxt);
3287                if pi_raw(ctxt) != b'\'' {
3288                    pi_fatal_err(ctxt, XML_ERR_STRING_NOT_CLOSED);
3289                } else {
3290                    pi_next1(ctxt);
3291                }
3292                return version;
3293            } else {
3294                pi_fatal_err(ctxt, XML_ERR_STRING_NOT_STARTED);
3295            }
3296        }
3297        ptr::null_mut()
3298    }
3299}
3300
3301/// `xmlParseEncName`.
3302unsafe fn pi_parse_enc_name(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
3303    unsafe {
3304        let cur = pi_raw(ctxt);
3305        if !(cur.is_ascii_lowercase() || cur.is_ascii_uppercase()) {
3306            pi_fatal_err(ctxt, XML_ERR_ENCODING_NAME);
3307            return ptr::null_mut();
3308        }
3309        let mut buf: Vec<u8> = Vec::new();
3310        buf.push(cur);
3311        pi_next1(ctxt);
3312        let mut c = pi_raw(ctxt);
3313        while c.is_ascii_alphanumeric() || c == b'.' || c == b'_' || c == b'-' {
3314            buf.push(c);
3315            pi_next1(ctxt);
3316            c = pi_raw(ctxt);
3317        }
3318        pi_strndup_bytes(buf.as_ptr(), buf.len())
3319    }
3320}
3321
3322/// `xmlParseEncodingDecl` — returns `ctxt->encoding` and stores it.
3323unsafe fn pi_parse_encoding_decl(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
3324    unsafe {
3325        pi_skip_blanks(ctxt);
3326        if !pi_cmp8(ctxt, b"encoding") {
3327            return ptr::null();
3328        }
3329        pi_skip(ctxt, 8);
3330        pi_skip_blanks(ctxt);
3331        if pi_raw(ctxt) != b'=' {
3332            pi_fatal_err(ctxt, XML_ERR_EQUAL_REQUIRED);
3333            return ptr::null();
3334        }
3335        pi_next1(ctxt);
3336        pi_skip_blanks(ctxt);
3337        let mut encoding: *mut xmlChar = ptr::null_mut();
3338        if pi_raw(ctxt) == b'"' {
3339            pi_next1(ctxt);
3340            encoding = pi_parse_enc_name(ctxt);
3341            if pi_raw(ctxt) != b'"' {
3342                pi_fatal_err(ctxt, XML_ERR_STRING_NOT_CLOSED);
3343                if !encoding.is_null() {
3344                    xmlFreeImpl(encoding as *mut c_void);
3345                }
3346                return ptr::null();
3347            }
3348            pi_next1(ctxt);
3349        } else if pi_raw(ctxt) == b'\'' {
3350            pi_next1(ctxt);
3351            encoding = pi_parse_enc_name(ctxt);
3352            if pi_raw(ctxt) != b'\'' {
3353                pi_fatal_err(ctxt, XML_ERR_STRING_NOT_CLOSED);
3354                if !encoding.is_null() {
3355                    xmlFreeImpl(encoding as *mut c_void);
3356                }
3357                return ptr::null();
3358            }
3359            pi_next1(ctxt);
3360        } else {
3361            pi_fatal_err(ctxt, XML_ERR_STRING_NOT_STARTED);
3362        }
3363        if encoding.is_null() {
3364            return ptr::null();
3365        }
3366        let c = &mut *ctxt;
3367        if !c.encoding.is_null() {
3368            xmlFreeImpl(c.encoding as *mut c_void);
3369        }
3370        c.encoding = encoding;
3371        c.encoding as *const xmlChar
3372    }
3373}
3374
3375/// `xmlParseSDDecl`.
3376unsafe fn pi_parse_sd_decl(ctxt: *mut _xmlParserCtxt) -> c_int {
3377    unsafe {
3378        let mut standalone = -2;
3379        pi_skip_blanks(ctxt);
3380        if pi_cmp10(ctxt, b"standalone") {
3381            pi_skip(ctxt, 10);
3382            pi_skip_blanks(ctxt);
3383            if pi_raw(ctxt) != b'=' {
3384                pi_fatal_err(ctxt, XML_ERR_EQUAL_REQUIRED);
3385                return standalone;
3386            }
3387            pi_next1(ctxt);
3388            pi_skip_blanks(ctxt);
3389            let quote = pi_raw(ctxt);
3390            if quote == b'\'' || quote == b'"' {
3391                pi_next1(ctxt);
3392                if pi_raw(ctxt) == b'n' && pi_nxt(ctxt, 1) == b'o' {
3393                    standalone = 0;
3394                    pi_skip(ctxt, 2);
3395                } else if pi_raw(ctxt) == b'y' && pi_nxt(ctxt, 1) == b'e' && pi_nxt(ctxt, 2) == b's'
3396                {
3397                    standalone = 1;
3398                    pi_skip(ctxt, 3);
3399                } else {
3400                    pi_fatal_err(ctxt, XML_ERR_STANDALONE_VALUE);
3401                }
3402                if pi_raw(ctxt) != quote {
3403                    pi_fatal_err(ctxt, XML_ERR_STRING_NOT_CLOSED);
3404                } else {
3405                    pi_next1(ctxt);
3406                }
3407            } else {
3408                pi_fatal_err(ctxt, XML_ERR_STRING_NOT_STARTED);
3409            }
3410        }
3411        standalone
3412    }
3413}
3414
3415/// `xmlParseXMLDecl`.
3416unsafe fn pi_parse_xml_decl(ctxt: *mut _xmlParserCtxt) {
3417    unsafe {
3418        (*ctxt).standalone = -2;
3419        pi_skip(ctxt, 5);
3420        if !pi_is_blank_ch(pi_raw(ctxt)) {
3421            pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
3422        }
3423        pi_skip_blanks(ctxt);
3424        let version = pi_parse_version_info(ctxt);
3425        if version.is_null() {
3426            pi_fatal_err(ctxt, XML_ERR_VERSION_MISSING);
3427        } else {
3428            if !pi_cstr_eq(version, b"1.0") {
3429                if pi_nxt(ctxt, 0) == b'1' {
3430                    // warning-level upstream; keep parsing
3431                } else {
3432                    pi_fatal_err(ctxt, XML_ERR_UNKNOWN_VERSION);
3433                }
3434            }
3435            let c = &mut *ctxt;
3436            if !c.version.is_null() {
3437                xmlFreeImpl(c.version as *mut c_void);
3438            }
3439            c.version = version;
3440        }
3441        if !pi_is_blank_ch(pi_raw(ctxt)) {
3442            if pi_raw(ctxt) == b'?' && pi_nxt(ctxt, 1) == b'>' {
3443                pi_skip(ctxt, 2);
3444                return;
3445            }
3446            pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
3447        }
3448        pi_parse_encoding_decl(ctxt);
3449        if !(*ctxt).encoding.is_null() && !pi_is_blank_ch(pi_raw(ctxt)) {
3450            if pi_raw(ctxt) == b'?' && pi_nxt(ctxt, 1) == b'>' {
3451                pi_skip(ctxt, 2);
3452                return;
3453            }
3454            pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
3455        }
3456        pi_skip_blanks(ctxt);
3457        (*ctxt).standalone = pi_parse_sd_decl(ctxt);
3458        pi_skip_blanks(ctxt);
3459        if pi_raw(ctxt) == b'?' && pi_nxt(ctxt, 1) == b'>' {
3460            pi_skip(ctxt, 2);
3461        } else if pi_raw(ctxt) == b'>' {
3462            pi_fatal_err(ctxt, XML_ERR_XMLDECL_NOT_FINISHED);
3463            pi_next1(ctxt);
3464        } else {
3465            pi_fatal_err(ctxt, XML_ERR_XMLDECL_NOT_FINISHED);
3466            while !pi_stopped(ctxt) && pi_raw(ctxt) != 0 {
3467                let c = pi_raw(ctxt);
3468                pi_next1(ctxt);
3469                if c == b'>' {
3470                    break;
3471                }
3472            }
3473        }
3474    }
3475}
3476
3477/// `xmlParseMisc`.
3478unsafe fn pi_parse_misc(ctxt: *mut _xmlParserCtxt) {
3479    unsafe {
3480        while !pi_stopped(ctxt) {
3481            pi_skip_blanks(ctxt);
3482            if pi_raw(ctxt) == b'<' && pi_nxt(ctxt, 1) == b'?' {
3483                pi_parse_pi(ctxt);
3484            } else if pi_raw(ctxt) == b'<'
3485                && pi_nxt(ctxt, 1) == b'!'
3486                && pi_nxt(ctxt, 2) == b'-'
3487                && pi_nxt(ctxt, 3) == b'-'
3488            {
3489                pi_parse_comment(ctxt);
3490            } else {
3491                break;
3492            }
3493        }
3494    }
3495}
3496
3497/// `xmlParseContent` — parse a content sequence.
3498#[allow(clippy::while_immutable_condition)]
3499unsafe fn pi_parse_content(ctxt: *mut _xmlParserCtxt) {
3500    unsafe {
3501        if ctxt.is_null() || pi_input(ctxt).is_null() {
3502            return;
3503        }
3504        pi_ctxt_late_init(ctxt);
3505        let old_name_nr = (*ctxt).nameNr;
3506        let old_space_nr = (*ctxt).spaceNr;
3507        let old_node_nr = (*ctxt).nodeNr;
3508        loop {
3509            let input = pi_input(ctxt);
3510            if input.is_null() {
3511                break;
3512            }
3513            if (*input).cur >= (*input).end || pi_stopped(ctxt) {
3514                break;
3515            }
3516            let cur = (*input).cur;
3517            if *cur == b'<' {
3518                if cur.add(1) < (*input).end && *cur.add(1) == b'?' {
3519                    pi_parse_pi(ctxt);
3520                } else if cur.add(8) < (*input).end
3521                    && *cur.add(1) == b'!'
3522                    && *cur.add(2) == b'['
3523                    && *cur.add(3) == b'C'
3524                    && *cur.add(4) == b'D'
3525                    && *cur.add(5) == b'A'
3526                    && *cur.add(6) == b'T'
3527                    && *cur.add(7) == b'A'
3528                    && *cur.add(8) == b'['
3529                {
3530                    pi_parse_cd_sect(ctxt);
3531                } else if cur.add(3) < (*input).end
3532                    && *cur.add(1) == b'!'
3533                    && *cur.add(2) == b'-'
3534                    && *cur.add(3) == b'-'
3535                {
3536                    pi_parse_comment(ctxt);
3537                } else if cur.add(1) < (*input).end && *cur.add(1) == b'/' {
3538                    if (*ctxt).nameNr <= old_name_nr {
3539                        break;
3540                    }
3541                    pi_parse_end_tag(ctxt);
3542                } else {
3543                    pi_parse_element(ctxt);
3544                }
3545            } else if *cur == b'&' {
3546                pi_parse_reference(ctxt);
3547            } else {
3548                pi_parse_char_data(ctxt, 0);
3549            }
3550        }
3551        // Premature end of data in tag.
3552        if (*ctxt).nameNr > old_name_nr
3553            && !pi_input(ctxt).is_null()
3554            && (*(*ctxt).input).cur >= (*(*ctxt).input).end
3555            && (*ctxt).wellFormed != 0
3556        {
3557            pi_fatal_err(ctxt, XML_ERR_TAG_NOT_FINISHED);
3558        }
3559        // Clean up in error case.
3560        while (*ctxt).nodeNr > old_node_nr {
3561            pi_node_pop(ctxt);
3562        }
3563        while (*ctxt).nameNr > old_name_nr {
3564            pi_name_pop(ctxt);
3565        }
3566        while (*ctxt).spaceNr > old_space_nr {
3567            pi_space_pop(ctxt);
3568        }
3569    }
3570}
3571
3572/// `xmlParseAttribute` — parse `Name Eq AttValue`.
3573unsafe fn pi_parse_attribute(
3574    ctxt: *mut _xmlParserCtxt,
3575    value: *mut *mut xmlChar,
3576) -> *const xmlChar {
3577    unsafe {
3578        *value = ptr::null_mut();
3579        let name = pi_parse_name(ctxt);
3580        if name.is_null() {
3581            pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
3582            return ptr::null();
3583        }
3584        pi_skip_blanks(ctxt);
3585        let mut val: *mut xmlChar = ptr::null_mut();
3586        if pi_raw(ctxt) == b'=' {
3587            pi_next1(ctxt);
3588            pi_skip_blanks(ctxt);
3589            val = pi_parse_att_value(ctxt);
3590        } else {
3591            pi_fatal_err(ctxt, XML_ERR_ATTRIBUTE_WITHOUT_VALUE);
3592            return name;
3593        }
3594        // xml:space / xml:lang checks (only when space stack is set up).
3595        if pi_cstr_eq(name, b"xml:space") && !val.is_null() && !(*ctxt).space.is_null() {
3596            if pi_cstr_eq(val, b"default") {
3597                *(*ctxt).space = 0;
3598            } else if pi_cstr_eq(val, b"preserve") {
3599                *(*ctxt).space = 1;
3600            }
3601        }
3602        *value = val;
3603        name
3604    }
3605}
3606
3607/// `xmlParseStartTag` — parse `<name attrs...>`.
3608unsafe fn pi_parse_start_tag(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
3609    unsafe {
3610        if pi_raw(ctxt) != b'<' {
3611            return ptr::null();
3612        }
3613        pi_next1(ctxt);
3614        let name = pi_parse_name(ctxt);
3615        if name.is_null() {
3616            pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
3617            return ptr::null();
3618        }
3619        let c0 = &mut *ctxt;
3620        let mut atts = c0.atts;
3621        let mut maxatts = c0.maxatts;
3622        let mut nbatts: usize = 0;
3623        let mut failed = false;
3624
3625        pi_skip_blanks(ctxt);
3626        while !(pi_raw(ctxt) == b'>' || (pi_raw(ctxt) == b'/' && pi_nxt(ctxt, 1) == b'>'))
3627            && pi_is_byte_char(pi_raw(ctxt))
3628            && !pi_stopped(ctxt)
3629        {
3630            let mut attvalue: *mut xmlChar = ptr::null_mut();
3631            let attname = pi_parse_attribute(ctxt, &mut attvalue);
3632            if attname.is_null() {
3633                failed = true;
3634                if !attvalue.is_null() {
3635                    xmlFreeImpl(attvalue as *mut c_void);
3636                }
3637                break;
3638            }
3639            if !attvalue.is_null() {
3640                // [WFC: Unique Att Spec]
3641                let mut i = 0;
3642                while i < nbatts {
3643                    if crate::abi::exports_xml2::xmlStrEqual(*atts.add(i), attname) != 0 {
3644                        failed = true;
3645                        break;
3646                    }
3647                    i += 2;
3648                }
3649                if failed {
3650                    if !attvalue.is_null() {
3651                        xmlFreeImpl(attvalue as *mut c_void);
3652                    }
3653                    break;
3654                }
3655                // Add the pair to atts.
3656                if nbatts + 4 > maxatts as usize {
3657                    let new_max = if maxatts == 0 { 20 } else { maxatts * 2 };
3658                    let n = xmlReallocImpl(
3659                        atts as *mut c_void,
3660                        (new_max as usize) * size_of::<*const xmlChar>(),
3661                    ) as *mut *const xmlChar;
3662                    if n.is_null() {
3663                        pi_err_memory(ctxt);
3664                        failed = true;
3665                        xmlFreeImpl(attvalue as *mut c_void);
3666                        break;
3667                    }
3668                    atts = n;
3669                    maxatts = new_max;
3670                    let c = &mut *ctxt;
3671                    c.atts = atts;
3672                    c.maxatts = maxatts;
3673                }
3674                *atts.add(nbatts) = attname;
3675                *atts.add(nbatts + 1) = attvalue;
3676                nbatts += 2;
3677                attvalue = ptr::null_mut();
3678            } else {
3679                failed = true;
3680            }
3681            if !attvalue.is_null() {
3682                xmlFreeImpl(attvalue as *mut c_void);
3683            }
3684            if pi_raw(ctxt) == b'>' || (pi_raw(ctxt) == b'/' && pi_nxt(ctxt, 1) == b'>') {
3685                break;
3686            }
3687            if pi_skip_blanks(ctxt) == 0 {
3688                pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
3689            }
3690        }
3691
3692        // SAX: Start of Element!
3693        pi_dispatch_start_element(ctxt, name, atts, nbatts);
3694
3695        // Free the attribute name/value strings (SAX handlers copy them).
3696        let mut i = 0;
3697        while i < nbatts {
3698            if !atts.is_null() {
3699                let p = *atts.add(i);
3700                if !p.is_null() {
3701                    xmlFreeImpl(p as *mut c_void);
3702                }
3703            }
3704            i += 1;
3705        }
3706        name
3707    }
3708}
3709
3710/// `xmlParseEndTag` — parse `</name>`.
3711unsafe fn pi_parse_end_tag(ctxt: *mut _xmlParserCtxt) {
3712    unsafe {
3713        if pi_raw(ctxt) != b'<' || pi_nxt(ctxt, 1) != b'/' {
3714            pi_fatal_err(ctxt, XML_ERR_LTSLASH_REQUIRED);
3715            return;
3716        }
3717        pi_skip(ctxt, 2);
3718        let c = &*ctxt;
3719        let name = pi_parse_name_and_compare(ctxt, c.name);
3720        pi_skip_blanks(ctxt);
3721        if !pi_is_byte_char(pi_raw(ctxt)) || pi_raw(ctxt) != b'>' {
3722            pi_fatal_err(ctxt, XML_ERR_GT_REQUIRED);
3723        } else {
3724            pi_next1(ctxt);
3725        }
3726        if name != std::ptr::dangling::<xmlChar>() {
3727            if name.is_null() {
3728                // "unparsable" name
3729                pi_fatal_err(ctxt, XML_ERR_TAG_NAME_MISMATCH);
3730            } else {
3731                pi_fatal_err(ctxt, XML_ERR_TAG_NAME_MISMATCH);
3732                xmlFreeImpl(name as *mut c_void);
3733            }
3734        }
3735        // SAX: End of Tag.
3736        let c = &*ctxt;
3737        pi_dispatch_end_element(ctxt, c.name);
3738        pi_name_pop(ctxt);
3739        pi_space_pop(ctxt);
3740    }
3741}
3742
3743/// `xmlParseElement` — parse `<name ...>content</name>`.
3744unsafe fn pi_parse_element(ctxt: *mut _xmlParserCtxt) {
3745    unsafe {
3746        let max_depth = if (*ctxt).options & XML_PARSE_HUGE != 0 {
3747            2048
3748        } else {
3749            256
3750        };
3751        if (*ctxt).nameNr > max_depth {
3752            pi_fatal_err(ctxt, XML_ERR_RESOURCE_LIMIT);
3753            return;
3754        }
3755        // spacePush
3756        let c = &mut *ctxt;
3757        if c.spaceNr == 0 || (!c.space.is_null() && *c.space == -2) {
3758            pi_space_push(ctxt, -1);
3759        } else if !c.space.is_null() {
3760            pi_space_push(ctxt, *c.space);
3761        } else {
3762            pi_space_push(ctxt, -1);
3763        }
3764        let line = (*(*ctxt).input).line;
3765        let name = pi_parse_start_tag(ctxt);
3766        if name.is_null() {
3767            pi_space_pop(ctxt);
3768            return;
3769        }
3770        pi_name_push(ctxt, name);
3771
3772        // Check for an empty element.
3773        if pi_raw(ctxt) == b'/' && pi_nxt(ctxt, 1) == b'>' {
3774            pi_skip(ctxt, 2);
3775            let c = &*ctxt;
3776            pi_dispatch_end_element(ctxt, c.name);
3777            pi_name_pop(ctxt);
3778            pi_space_pop(ctxt);
3779            return;
3780        }
3781        if pi_raw(ctxt) == b'>' {
3782            pi_next1(ctxt);
3783        } else {
3784            pi_fatal_err(ctxt, XML_ERR_GT_REQUIRED);
3785            pi_name_pop(ctxt);
3786            pi_space_pop(ctxt);
3787            return;
3788        }
3789
3790        // Content.
3791        pi_parse_content(ctxt);
3792
3793        // End tag.
3794        let input = pi_input(ctxt);
3795        if input.is_null() || (*input).cur >= (*input).end {
3796            if (*ctxt).wellFormed != 0 {
3797                pi_fatal_err(ctxt, XML_ERR_TAG_NOT_FINISHED);
3798            }
3799            return;
3800        }
3801        pi_parse_end_tag(ctxt);
3802    }
3803}
3804
3805/// `xmlParseDocTypeDecl` — assumes `<!DOCTYPE` was detected.
3806unsafe fn pi_parse_doc_type_decl(ctxt: *mut _xmlParserCtxt) {
3807    unsafe {
3808        pi_skip(ctxt, 9);
3809        if pi_skip_blanks(ctxt) == 0 {
3810            pi_fatal_err(ctxt, XML_ERR_SPACE_REQUIRED);
3811        }
3812        let name = pi_parse_name(ctxt);
3813        if name.is_null() {
3814            pi_fatal_err(ctxt, XML_ERR_NAME_REQUIRED);
3815            return;
3816        }
3817        (*ctxt).intSubName = name;
3818        pi_skip_blanks(ctxt);
3819        let mut public_id: *mut xmlChar = ptr::null_mut();
3820        let uri = pi_parse_external_id(ctxt, &mut public_id, 1);
3821        if !uri.is_null() || !public_id.is_null() {
3822            (*ctxt).hasExternalSubset = 1;
3823        }
3824        (*ctxt).extSubURI = uri;
3825        (*ctxt).extSubSystem = public_id;
3826        pi_skip_blanks(ctxt);
3827        let c = &*ctxt;
3828        if !c.sax.is_null() && c.disableSAX == 0 {
3829            SaxDispatcher::internal_subset(&*c.sax, c.userData, name, public_id, uri);
3830        }
3831        if pi_raw(ctxt) != b'[' && pi_raw(ctxt) != b'>' {
3832            pi_fatal_err(ctxt, XML_ERR_DOCTYPE_NOT_FINISHED);
3833        }
3834    }
3835}
3836
3837/// `xmlParseInternalSubset`.
3838#[allow(clippy::while_immutable_condition)]
3839unsafe fn pi_parse_internal_subset(ctxt: *mut _xmlParserCtxt) {
3840    unsafe {
3841        if pi_raw(ctxt) == b'[' {
3842            let old_input_nr = (*ctxt).inputNr;
3843            pi_next1(ctxt);
3844            pi_skip_blanks(ctxt);
3845            loop {
3846                if pi_stopped(ctxt) {
3847                    return;
3848                }
3849                let input = pi_input(ctxt);
3850                if input.is_null() {
3851                    return;
3852                }
3853                if (*input).cur >= (*input).end {
3854                    if (*ctxt).inputNr <= old_input_nr {
3855                        pi_fatal_err(ctxt, XML_ERR_INT_SUBSET_NOT_FINISHED);
3856                        return;
3857                    }
3858                    pi_pop_pe(ctxt);
3859                } else if pi_raw(ctxt) == b']' && (*ctxt).inputNr <= old_input_nr {
3860                    pi_next1(ctxt);
3861                    pi_skip_blanks(ctxt);
3862                    break;
3863                } else if pi_raw(ctxt) == b'<'
3864                    && (pi_nxt(ctxt, 1) == b'!' || pi_nxt(ctxt, 1) == b'?')
3865                {
3866                    pi_parse_markup_decl(ctxt);
3867                } else if pi_raw(ctxt) == b'%' {
3868                    pi_parse_pe_reference(ctxt);
3869                } else {
3870                    pi_fatal_err(ctxt, XML_ERR_INT_SUBSET_NOT_FINISHED);
3871                    while (*ctxt).inputNr > old_input_nr {
3872                        pi_pop_pe(ctxt);
3873                    }
3874                    return;
3875                }
3876                pi_skip_blanks(ctxt);
3877            }
3878        }
3879        if pi_raw(ctxt) != b'>' {
3880            pi_fatal_err(ctxt, XML_ERR_DOCTYPE_NOT_FINISHED);
3881            return;
3882        }
3883        pi_next1(ctxt);
3884    }
3885}
3886
3887/// `xmlParseDocument`.
3888unsafe fn pi_parse_document(ctxt: *mut _xmlParserCtxt) -> c_int {
3889    unsafe {
3890        if ctxt.is_null() || pi_input(ctxt).is_null() {
3891            return -1;
3892        }
3893        pi_ctxt_late_init(ctxt);
3894
3895        // SAX: detecting the level — setDocumentLocator.
3896        let c0 = &*ctxt;
3897        if !c0.sax.is_null() && (*c0.sax).setDocumentLocator.is_some() {
3898            SaxDispatcher::set_document_locator(
3899                &*c0.sax,
3900                c0.userData,
3901                &crate::abi::data_globals::xmlDefaultSAXLocator as *const _xmlSAXLocator
3902                    as *mut _xmlSAXLocator,
3903            );
3904        }
3905
3906        if pi_raw(ctxt) == 0 {
3907            pi_fatal_err(ctxt, XML_ERR_DOCUMENT_EMPTY);
3908            return -1;
3909        }
3910
3911        if pi_cmp5(ctxt, b"<?xml") && pi_is_blank_ch(pi_nxt(ctxt, 5)) {
3912            pi_parse_xml_decl(ctxt);
3913            pi_skip_blanks(ctxt);
3914        } else {
3915            let c = &mut *ctxt;
3916            if !c.version.is_null() {
3917                xmlFreeImpl(c.version as *mut c_void);
3918            }
3919            c.version = crate::abi::exports_xml2::xmlStrdup(c"1.0".as_ptr() as *const xmlChar);
3920            if c.version.is_null() {
3921                pi_err_memory(ctxt);
3922                return -1;
3923            }
3924        }
3925        let c1 = &*ctxt;
3926        if !c1.sax.is_null() && c1.disableSAX == 0 {
3927            SaxDispatcher::start_document(&*c1.sax, c1.userData);
3928        }
3929
3930        // The Misc part of the prolog.
3931        pi_parse_misc(ctxt);
3932
3933        // Then possibly doc type declaration(s) and more Misc.
3934        if pi_cmp9(ctxt, b"<!DOCTYPE") {
3935            let c = &mut *ctxt;
3936            c.inSubset = 1;
3937            pi_parse_doc_type_decl(ctxt);
3938            if pi_raw(ctxt) == b'[' {
3939                pi_parse_internal_subset(ctxt);
3940            } else if pi_raw(ctxt) == b'>' {
3941                pi_next1(ctxt);
3942            }
3943            c.inSubset = 2;
3944            let c2 = &*ctxt;
3945            if !c2.sax.is_null() && c2.disableSAX == 0 {
3946                SaxDispatcher::external_subset(
3947                    &*c2.sax,
3948                    c2.userData,
3949                    c2.intSubName,
3950                    c2.extSubSystem,
3951                    c2.extSubURI,
3952                );
3953            }
3954            let c3 = &mut *ctxt;
3955            c3.inSubset = 0;
3956            pi_parse_misc(ctxt);
3957        }
3958
3959        // Time to start parsing the tree itself.
3960        if pi_raw(ctxt) != b'<' {
3961            if (*ctxt).wellFormed != 0 {
3962                pi_fatal_err(ctxt, XML_ERR_DOCUMENT_EMPTY);
3963            }
3964        } else {
3965            pi_parse_element(ctxt);
3966            pi_parse_misc(ctxt);
3967            // Check EOF.
3968            if !pi_stopped(ctxt) {
3969                let input = pi_input(ctxt);
3970                if !input.is_null() && (*input).cur < (*input).end {
3971                    pi_fatal_err(ctxt, XML_ERR_DOCUMENT_END);
3972                }
3973            }
3974        }
3975
3976        let c = &mut *ctxt;
3977        c.instate = XML_PARSER_EOF_STATE;
3978        if !c.sax.is_null() && c.disableSAX == 0 {
3979            SaxDispatcher::end_document(&*c.sax, c.userData);
3980        }
3981        if c.wellFormed == 0 {
3982            c.valid = 0;
3983            return -1;
3984        }
3985        0
3986    }
3987}
3988
3989/// `xmlParseExtParsedEnt`.
3990unsafe fn pi_parse_ext_parsed_ent(ctxt: *mut _xmlParserCtxt) -> c_int {
3991    unsafe {
3992        if ctxt.is_null() || pi_input(ctxt).is_null() {
3993            return -1;
3994        }
3995        pi_ctxt_late_init(ctxt);
3996        if pi_raw(ctxt) == 0 {
3997            pi_fatal_err(ctxt, XML_ERR_DOCUMENT_EMPTY);
3998        }
3999        if pi_cmp5(ctxt, b"<?xml") && pi_is_blank_ch(pi_nxt(ctxt, 5)) {
4000            pi_parse_xml_decl(ctxt);
4001            pi_skip_blanks(ctxt);
4002        } else {
4003            let c = &mut *ctxt;
4004            if !c.version.is_null() {
4005                xmlFreeImpl(c.version as *mut c_void);
4006            }
4007            c.version = crate::abi::exports_xml2::xmlStrdup(c"1.0".as_ptr() as *const xmlChar);
4008        }
4009        let c0 = &*ctxt;
4010        if !c0.sax.is_null() && c0.disableSAX == 0 {
4011            SaxDispatcher::start_document(&*c0.sax, c0.userData);
4012        }
4013        let c1 = &mut *ctxt;
4014        c1.options &= !XML_PARSE_DTDVALID;
4015        c1.validate = 0;
4016        c1.depth = 0;
4017
4018        pi_parse_content(ctxt);
4019
4020        let input = pi_input(ctxt);
4021        if !input.is_null() && (*input).cur < (*input).end {
4022            pi_fatal_err(ctxt, XML_ERR_NOT_WELL_BALANCED);
4023        }
4024        let c2 = &*ctxt;
4025        if !c2.sax.is_null() && c2.disableSAX == 0 {
4026            SaxDispatcher::end_document(&*c2.sax, c2.userData);
4027        }
4028        if (*ctxt).wellFormed == 0 {
4029            return -1;
4030        }
4031        0
4032    }
4033}
4034
4035/// Parse a content sequence into a node list, using a synthetic `#root`
4036/// node (upstream `xmlCtxtParseContentInternal`).
4037unsafe fn pi_parse_content_node_list(
4038    ctxt: *mut _xmlParserCtxt,
4039    input: *mut _xmlParserInput,
4040    has_text_decl: c_int,
4041) -> *mut _xmlNode {
4042    unsafe {
4043        let mut root: *mut _xmlNode = ptr::null_mut();
4044        let mut list: *mut _xmlNode = ptr::null_mut();
4045        let root_name = b"#root\0";
4046        root = crate::xml::tree::new_node(ptr::null_mut(), root_name.as_ptr() as *const xmlChar);
4047        if root.is_null() {
4048            pi_err_memory(ctxt);
4049            return ptr::null_mut();
4050        }
4051        if pi_input_push(ctxt, input) < 0 {
4052            crate::xml::tree::free_node(root);
4053            return ptr::null_mut();
4054        }
4055        pi_name_push(ctxt, root_name.as_ptr() as *const xmlChar);
4056        pi_space_push(ctxt, -1);
4057        pi_node_push(ctxt, root);
4058
4059        if has_text_decl != 0 && pi_cmp5(ctxt, b"<?xml") && pi_is_blank_ch(pi_nxt(ctxt, 5)) {
4060            pi_parse_text_decl(ctxt);
4061        }
4062
4063        pi_parse_content(ctxt);
4064
4065        let cur = pi_input(ctxt);
4066        if !cur.is_null() && (*cur).cur < (*cur).end {
4067            pi_fatal_err(ctxt, XML_ERR_NOT_WELL_BALANCED);
4068        }
4069
4070        if (*ctxt).wellFormed != 0 {
4071            // Unlink the newly created node list.
4072            list = (*root).children;
4073            (*root).children = ptr::null_mut();
4074            (*root).last = ptr::null_mut();
4075            let mut n = list;
4076            while !n.is_null() {
4077                (*n).parent = ptr::null_mut();
4078                n = (*n).next;
4079            }
4080        }
4081
4082        if pi_input_pop(ctxt) == input {
4083            // The popped input is the one we pushed: free it (struct and its
4084            // owned filename; the base data lives in the boxed InputBuffer
4085            // stashed in ctxt->_private, freed by free_parser_ctxt).
4086            crate::xml::parser::helpers::free_parser_input(input);
4087        }
4088        pi_node_pop(ctxt);
4089        pi_name_pop(ctxt);
4090        pi_space_pop(ctxt);
4091        crate::xml::tree::free_node(root);
4092        list
4093    }
4094}
4095
4096// ═══════════════════════════════════════════════════════════════════════════════
4097// C ABI exports — parserInternals.h name/primitive family
4098// ═══════════════════════════════════════════════════════════════════════════════
4099
4100/// `xmlNamespaceParseNCName` (legacy libxml2 2.6.x API).
4101///
4102/// ```c
4103/// const xmlChar *xmlNamespaceParseNCName(xmlParserCtxtPtr ctxt);
4104/// ```
4105#[no_mangle]
4106pub unsafe extern "C" fn xmlNamespaceParseNCName(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
4107    if ctxt.is_null() || pi_input(ctxt).is_null() {
4108        return ptr::null();
4109    }
4110    unsafe {
4111        let start = pi_cur_ptr(ctxt);
4112        let (c, l) = pi_current_char(ctxt);
4113        if (c != b'_' as c_int && !pi_is_letter_ch(c as u8)) || c == b':' as c_int {
4114            return ptr::null();
4115        }
4116        pi_nextl(ctxt, l);
4117        loop {
4118            let (c2, l2) = pi_current_char(ctxt);
4119            if !pi_is_name_char(c2) || c2 == b':' as c_int {
4120                break;
4121            }
4122            pi_nextl(ctxt, l2);
4123        }
4124        let len = pi_cur_ptr(ctxt).offset_from(start) as usize;
4125        if len == 0 {
4126            return ptr::null();
4127        }
4128        pi_strndup_bytes(start, len)
4129    }
4130}
4131
4132/// `xmlNamespaceParseQName` (legacy libxml2 2.6.x API).
4133///
4134/// ```c
4135/// const xmlChar *xmlNamespaceParseQName(xmlParserCtxtPtr ctxt, xmlChar **prefix);
4136/// ```
4137#[no_mangle]
4138pub unsafe extern "C" fn xmlNamespaceParseQName(
4139    ctxt: *mut _xmlParserCtxt,
4140    prefix: *mut *mut xmlChar,
4141) -> *const xmlChar {
4142    if ctxt.is_null() || pi_input(ctxt).is_null() {
4143        return ptr::null();
4144    }
4145    unsafe {
4146        if !prefix.is_null() {
4147            *prefix = ptr::null_mut();
4148        }
4149        let start = pi_cur_ptr(ctxt);
4150        let (c, l) = pi_current_char(ctxt);
4151        if (c != b'_' as c_int && !pi_is_letter_ch(c as u8)) || c == b':' as c_int {
4152            return ptr::null();
4153        }
4154        pi_nextl(ctxt, l);
4155        loop {
4156            let (c2, l2) = pi_current_char(ctxt);
4157            if !pi_is_name_char(c2) || c2 == b':' as c_int {
4158                break;
4159            }
4160            pi_nextl(ctxt, l2);
4161        }
4162        if pi_raw(ctxt) == b':' {
4163            pi_next1(ctxt);
4164            if !prefix.is_null() {
4165                let plen = pi_cur_ptr(ctxt).offset_from(start) as usize - 1;
4166                *prefix = pi_strndup_bytes(start, plen);
4167            }
4168            let lstart = pi_cur_ptr(ctxt);
4169            let (c2, l2) = pi_current_char(ctxt);
4170            if (c2 != b'_' as c_int && !pi_is_letter_ch(c2 as u8)) || c2 == b':' as c_int {
4171                return ptr::null();
4172            }
4173            pi_nextl(ctxt, l2);
4174            loop {
4175                let (c3, l3) = pi_current_char(ctxt);
4176                if !pi_is_name_char(c3) || c3 == b':' as c_int {
4177                    break;
4178                }
4179                pi_nextl(ctxt, l3);
4180            }
4181            let llen = pi_cur_ptr(ctxt).offset_from(lstart) as usize;
4182            return pi_strndup_bytes(lstart, llen);
4183        }
4184        let len = pi_cur_ptr(ctxt).offset_from(start) as usize;
4185        if len == 0 {
4186            return ptr::null();
4187        }
4188        pi_strndup_bytes(start, len)
4189    }
4190}
4191
4192/// `xmlNamespaceParseNSDef` (legacy libxml2 2.6.x API).
4193///
4194/// ```c
4195/// const xmlChar *xmlNamespaceParseNSDef(xmlParserCtxtPtr ctxt);
4196/// ```
4197#[no_mangle]
4198pub unsafe extern "C" fn xmlNamespaceParseNSDef(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
4199    if ctxt.is_null() || pi_input(ctxt).is_null() {
4200        return ptr::null();
4201    }
4202    unsafe {
4203        let start = pi_cur_ptr(ctxt);
4204        if !pi_cmp5(ctxt, b"xmlns") {
4205            return ptr::null();
4206        }
4207        pi_skip(ctxt, 5);
4208        if pi_raw(ctxt) == b':' {
4209            pi_next1(ctxt);
4210            let (c, l) = pi_current_char(ctxt);
4211            if (c != b'_' as c_int && !pi_is_letter_ch(c as u8)) || c == b':' as c_int {
4212                return ptr::null();
4213            }
4214            pi_nextl(ctxt, l);
4215            loop {
4216                let (c2, l2) = pi_current_char(ctxt);
4217                if !pi_is_name_char(c2) || c2 == b':' as c_int {
4218                    break;
4219                }
4220                pi_nextl(ctxt, l2);
4221            }
4222        }
4223        let len = pi_cur_ptr(ctxt).offset_from(start) as usize;
4224        pi_strndup_bytes(start, len)
4225    }
4226}
4227
4228/// `xmlParseNamespace` (legacy libxml2 2.6.x API).
4229///
4230/// Parses a namespace declaration `xmlns[:prefix] = "uri"`.
4231///
4232/// ```c
4233/// const xmlChar *xmlParseNamespace(xmlParserCtxtPtr ctxt);
4234/// ```
4235#[no_mangle]
4236pub unsafe extern "C" fn xmlParseNamespace(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
4237    if ctxt.is_null() || pi_input(ctxt).is_null() {
4238        return ptr::null();
4239    }
4240    unsafe {
4241        let name = xmlNamespaceParseNSDef(ctxt);
4242        if name.is_null() {
4243            return ptr::null();
4244        }
4245        pi_skip_blanks(ctxt);
4246        if pi_raw(ctxt) == b'=' {
4247            pi_next1(ctxt);
4248            pi_skip_blanks(ctxt);
4249            let value = pi_parse_att_value(ctxt);
4250            if value.is_null() {
4251                pi_fatal_err(ctxt, XML_ERR_ATTRIBUTE_WITHOUT_VALUE);
4252                return name;
4253            }
4254            xmlFreeImpl(value as *mut c_void);
4255        }
4256        name
4257    }
4258}
4259
4260/// `xmlParseQuotedString` (legacy libxml2 2.6.x API).
4261///
4262/// ```c
4263/// xmlChar *xmlParseQuotedString(xmlParserCtxtPtr ctxt);
4264/// ```
4265#[no_mangle]
4266pub unsafe extern "C" fn xmlParseQuotedString(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
4267    pi_parse_quoted_string(ctxt)
4268}
4269
4270/// `xmlParseName`.
4271///
4272/// ```c
4273/// const xmlChar *xmlParseName(xmlParserCtxtPtr ctxt);
4274/// ```
4275#[no_mangle]
4276pub unsafe extern "C" fn xmlParseName(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
4277    pi_parse_name(ctxt)
4278}
4279
4280/// `xmlParseNmtoken`.
4281///
4282/// ```c
4283/// xmlChar *xmlParseNmtoken(xmlParserCtxtPtr ctxt);
4284/// ```
4285#[no_mangle]
4286pub unsafe extern "C" fn xmlParseNmtoken(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
4287    pi_parse_nmtoken(ctxt)
4288}
4289
4290/// `xmlParseCharRef`.
4291///
4292/// ```c
4293/// int xmlParseCharRef(xmlParserCtxtPtr ctxt);
4294/// ```
4295#[no_mangle]
4296pub unsafe extern "C" fn xmlParseCharRef(ctxt: *mut _xmlParserCtxt) -> c_int {
4297    pi_parse_char_ref(ctxt)
4298}
4299
4300/// `xmlParseEntityRef`.
4301///
4302/// ```c
4303/// xmlEntity *xmlParseEntityRef(xmlParserCtxtPtr ctxt);
4304/// ```
4305#[no_mangle]
4306pub unsafe extern "C" fn xmlParseEntityRef(ctxt: *mut _xmlParserCtxt) -> *mut _xmlEntity {
4307    pi_parse_entity_ref(ctxt)
4308}
4309
4310/// `xmlParseEntityValue`.
4311///
4312/// ```c
4313/// xmlChar *xmlParseEntityValue(xmlParserCtxtPtr ctxt, xmlChar **orig);
4314/// ```
4315#[no_mangle]
4316pub unsafe extern "C" fn xmlParseEntityValue(
4317    ctxt: *mut _xmlParserCtxt,
4318    orig: *mut *mut xmlChar,
4319) -> *mut xmlChar {
4320    pi_parse_entity_value(ctxt, orig)
4321}
4322
4323/// `xmlParseAttValue`.
4324///
4325/// ```c
4326/// xmlChar *xmlParseAttValue(xmlParserCtxtPtr ctxt);
4327/// ```
4328#[no_mangle]
4329pub unsafe extern "C" fn xmlParseAttValue(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
4330    pi_parse_att_value(ctxt)
4331}
4332
4333/// `xmlParseSystemLiteral`.
4334///
4335/// ```c
4336/// xmlChar *xmlParseSystemLiteral(xmlParserCtxtPtr ctxt);
4337/// ```
4338#[no_mangle]
4339pub unsafe extern "C" fn xmlParseSystemLiteral(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
4340    pi_parse_system_literal(ctxt)
4341}
4342
4343/// `xmlParsePubidLiteral`.
4344///
4345/// ```c
4346/// xmlChar *xmlParsePubidLiteral(xmlParserCtxtPtr ctxt);
4347/// ```
4348#[no_mangle]
4349pub unsafe extern "C" fn xmlParsePubidLiteral(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
4350    pi_parse_pubid_literal(ctxt)
4351}
4352
4353/// `xmlParseExternalID`.
4354///
4355/// ```c
4356/// xmlChar *xmlParseExternalID(xmlParserCtxtPtr ctxt, xmlChar **publicId, int strict);
4357/// ```
4358#[no_mangle]
4359pub unsafe extern "C" fn xmlParseExternalID(
4360    ctxt: *mut _xmlParserCtxt,
4361    public_id: *mut *mut xmlChar,
4362    strict: c_int,
4363) -> *mut xmlChar {
4364    if ctxt.is_null() || public_id.is_null() {
4365        return ptr::null_mut();
4366    }
4367    pi_parse_external_id(ctxt, public_id, strict)
4368}
4369
4370/// `xmlParsePITarget`.
4371///
4372/// ```c
4373/// const xmlChar *xmlParsePITarget(xmlParserCtxtPtr ctxt);
4374/// ```
4375#[no_mangle]
4376pub unsafe extern "C" fn xmlParsePITarget(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
4377    pi_parse_pi_target(ctxt)
4378}
4379
4380/// `xmlParsePI`.
4381///
4382/// ```c
4383/// void xmlParsePI(xmlParserCtxtPtr ctxt);
4384/// ```
4385#[no_mangle]
4386pub unsafe extern "C" fn xmlParsePI(ctxt: *mut _xmlParserCtxt) {
4387    pi_parse_pi(ctxt)
4388}
4389
4390/// `xmlParseComment`.
4391///
4392/// ```c
4393/// void xmlParseComment(xmlParserCtxtPtr ctxt);
4394/// ```
4395#[no_mangle]
4396pub unsafe extern "C" fn xmlParseComment(ctxt: *mut _xmlParserCtxt) {
4397    pi_parse_comment(ctxt)
4398}
4399
4400/// `xmlParseCharData`.
4401///
4402/// ```c
4403/// void xmlParseCharData(xmlParserCtxtPtr ctxt, int cdata);
4404/// ```
4405#[no_mangle]
4406pub unsafe extern "C" fn xmlParseCharData(ctxt: *mut _xmlParserCtxt, cdata: c_int) {
4407    pi_parse_char_data(ctxt, cdata)
4408}
4409
4410/// `xmlParseCDSect`.
4411///
4412/// ```c
4413/// void xmlParseCDSect(xmlParserCtxtPtr ctxt);
4414/// ```
4415#[no_mangle]
4416pub unsafe extern "C" fn xmlParseCDSect(ctxt: *mut _xmlParserCtxt) {
4417    pi_parse_cd_sect(ctxt)
4418}
4419
4420/// `xmlParseReference`.
4421///
4422/// ```c
4423/// void xmlParseReference(xmlParserCtxtPtr ctxt);
4424/// ```
4425#[no_mangle]
4426pub unsafe extern "C" fn xmlParseReference(ctxt: *mut _xmlParserCtxt) {
4427    pi_parse_reference(ctxt)
4428}
4429
4430/// `xmlParserHandleReference` (legacy wrapper, upstream parserInternals.h).
4431///
4432/// ```c
4433/// void xmlParserHandleReference(xmlParserCtxtPtr ctxt);
4434/// ```
4435#[no_mangle]
4436pub unsafe extern "C" fn xmlParserHandleReference(ctxt: *mut _xmlParserCtxt) {
4437    pi_parse_reference(ctxt)
4438}
4439
4440/// `xmlParsePEReference`.
4441///
4442/// ```c
4443/// void xmlParsePEReference(xmlParserCtxtPtr ctxt);
4444/// ```
4445#[no_mangle]
4446pub unsafe extern "C" fn xmlParsePEReference(ctxt: *mut _xmlParserCtxt) {
4447    pi_parse_pe_reference(ctxt)
4448}
4449
4450/// `xmlParserHandlePEReference`.
4451///
4452/// ```c
4453/// void xmlParserHandlePEReference(xmlParserCtxtPtr ctxt);
4454/// ```
4455#[no_mangle]
4456pub unsafe extern "C" fn xmlParserHandlePEReference(ctxt: *mut _xmlParserCtxt) {
4457    pi_parse_pe_reference(ctxt)
4458}
4459
4460/// `xmlParseNotationDecl`.
4461///
4462/// ```c
4463/// void xmlParseNotationDecl(xmlParserCtxtPtr ctxt);
4464/// ```
4465#[no_mangle]
4466pub unsafe extern "C" fn xmlParseNotationDecl(ctxt: *mut _xmlParserCtxt) {
4467    pi_parse_notation_decl(ctxt)
4468}
4469
4470/// `xmlParseEntityDecl`.
4471///
4472/// ```c
4473/// void xmlParseEntityDecl(xmlParserCtxtPtr ctxt);
4474/// ```
4475#[no_mangle]
4476pub unsafe extern "C" fn xmlParseEntityDecl(ctxt: *mut _xmlParserCtxt) {
4477    pi_parse_entity_decl(ctxt)
4478}
4479
4480/// `xmlParseDefaultDecl`.
4481///
4482/// ```c
4483/// int xmlParseDefaultDecl(xmlParserCtxtPtr ctxt, xmlChar **value);
4484/// ```
4485#[no_mangle]
4486pub unsafe extern "C" fn xmlParseDefaultDecl(
4487    ctxt: *mut _xmlParserCtxt,
4488    value: *mut *mut xmlChar,
4489) -> c_int {
4490    if ctxt.is_null() || value.is_null() {
4491        return 0;
4492    }
4493    pi_parse_default_decl(ctxt, value)
4494}
4495
4496/// `xmlParseAttributeType`.
4497///
4498/// ```c
4499/// int xmlParseAttributeType(xmlParserCtxtPtr ctxt, xmlEnumeration **tree);
4500/// ```
4501#[no_mangle]
4502pub unsafe extern "C" fn xmlParseAttributeType(
4503    ctxt: *mut _xmlParserCtxt,
4504    tree: *mut *mut _xmlEnumeration,
4505) -> c_int {
4506    if ctxt.is_null() || tree.is_null() {
4507        return 0;
4508    }
4509    pi_parse_attribute_type(ctxt, tree)
4510}
4511
4512/// `xmlParseNotationType`.
4513///
4514/// ```c
4515/// xmlEnumeration *xmlParseNotationType(xmlParserCtxtPtr ctxt);
4516/// ```
4517#[no_mangle]
4518pub unsafe extern "C" fn xmlParseNotationType(ctxt: *mut _xmlParserCtxt) -> *mut _xmlEnumeration {
4519    pi_parse_notation_type(ctxt)
4520}
4521
4522/// `xmlParseEnumerationType`.
4523///
4524/// ```c
4525/// xmlEnumeration *xmlParseEnumerationType(xmlParserCtxtPtr ctxt);
4526/// ```
4527#[no_mangle]
4528pub unsafe extern "C" fn xmlParseEnumerationType(
4529    ctxt: *mut _xmlParserCtxt,
4530) -> *mut _xmlEnumeration {
4531    pi_parse_enumeration_type(ctxt)
4532}
4533
4534/// `xmlParseEnumeratedType`.
4535///
4536/// ```c
4537/// int xmlParseEnumeratedType(xmlParserCtxtPtr ctxt, xmlEnumeration **tree);
4538/// ```
4539#[no_mangle]
4540pub unsafe extern "C" fn xmlParseEnumeratedType(
4541    ctxt: *mut _xmlParserCtxt,
4542    tree: *mut *mut _xmlEnumeration,
4543) -> c_int {
4544    if ctxt.is_null() || tree.is_null() {
4545        return 0;
4546    }
4547    pi_parse_enumerated_type(ctxt, tree)
4548}
4549
4550/// `xmlParseAttributeListDecl`.
4551///
4552/// ```c
4553/// void xmlParseAttributeListDecl(xmlParserCtxtPtr ctxt);
4554/// ```
4555#[no_mangle]
4556pub unsafe extern "C" fn xmlParseAttributeListDecl(ctxt: *mut _xmlParserCtxt) {
4557    pi_parse_attribute_list_decl(ctxt)
4558}
4559
4560/// `xmlParseElementMixedContentDecl`.
4561///
4562/// ```c
4563/// xmlElementContent *xmlParseElementMixedContentDecl(xmlParserCtxtPtr ctxt, int inputchk);
4564/// ```
4565#[no_mangle]
4566pub unsafe extern "C" fn xmlParseElementMixedContentDecl(
4567    ctxt: *mut _xmlParserCtxt,
4568    inputchk: c_int,
4569) -> *mut _xmlElementContent {
4570    pi_parse_element_mixed_content_decl(ctxt, inputchk)
4571}
4572
4573/// `xmlParseElementChildrenContentDecl`.
4574///
4575/// ```c
4576/// xmlElementContent *xmlParseElementChildrenContentDecl(xmlParserCtxtPtr ctxt, int inputchk);
4577/// ```
4578#[no_mangle]
4579pub unsafe extern "C" fn xmlParseElementChildrenContentDecl(
4580    ctxt: *mut _xmlParserCtxt,
4581    inputchk: c_int,
4582) -> *mut _xmlElementContent {
4583    pi_parse_element_children_content_decl_priv(ctxt, inputchk, 1)
4584}
4585
4586/// `xmlParseElementContentDecl`.
4587///
4588/// ```c
4589/// int xmlParseElementContentDecl(xmlParserCtxtPtr ctxt, const xmlChar *name,
4590///                                xmlElementContent **result);
4591/// ```
4592#[no_mangle]
4593pub unsafe extern "C" fn xmlParseElementContentDecl(
4594    ctxt: *mut _xmlParserCtxt,
4595    name: *const xmlChar,
4596    result: *mut *mut _xmlElementContent,
4597) -> c_int {
4598    if ctxt.is_null() || result.is_null() {
4599        return -1;
4600    }
4601    pi_parse_element_content_decl(ctxt, name, result)
4602}
4603
4604/// `xmlParseElementDecl`.
4605///
4606/// ```c
4607/// int xmlParseElementDecl(xmlParserCtxtPtr ctxt);
4608/// ```
4609#[no_mangle]
4610pub unsafe extern "C" fn xmlParseElementDecl(ctxt: *mut _xmlParserCtxt) -> c_int {
4611    pi_parse_element_decl(ctxt)
4612}
4613
4614/// `xmlParseMarkupDecl`.
4615///
4616/// ```c
4617/// void xmlParseMarkupDecl(xmlParserCtxtPtr ctxt);
4618/// ```
4619#[no_mangle]
4620pub unsafe extern "C" fn xmlParseMarkupDecl(ctxt: *mut _xmlParserCtxt) {
4621    pi_parse_markup_decl(ctxt)
4622}
4623
4624/// `xmlParseVersionNum`.
4625///
4626/// ```c
4627/// xmlChar *xmlParseVersionNum(xmlParserCtxtPtr ctxt);
4628/// ```
4629#[no_mangle]
4630pub unsafe extern "C" fn xmlParseVersionNum(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
4631    pi_parse_version_num(ctxt)
4632}
4633
4634/// `xmlParseVersionInfo`.
4635///
4636/// ```c
4637/// xmlChar *xmlParseVersionInfo(xmlParserCtxtPtr ctxt);
4638/// ```
4639#[no_mangle]
4640pub unsafe extern "C" fn xmlParseVersionInfo(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
4641    pi_parse_version_info(ctxt)
4642}
4643
4644/// `xmlParseEncName`.
4645///
4646/// ```c
4647/// xmlChar *xmlParseEncName(xmlParserCtxtPtr ctxt);
4648/// ```
4649#[no_mangle]
4650pub unsafe extern "C" fn xmlParseEncName(ctxt: *mut _xmlParserCtxt) -> *mut xmlChar {
4651    pi_parse_enc_name(ctxt)
4652}
4653
4654/// `xmlParseEncodingDecl`.
4655///
4656/// ```c
4657/// const xmlChar *xmlParseEncodingDecl(xmlParserCtxtPtr ctxt);
4658/// ```
4659#[no_mangle]
4660pub unsafe extern "C" fn xmlParseEncodingDecl(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
4661    pi_parse_encoding_decl(ctxt)
4662}
4663
4664/// `xmlParseSDDecl`.
4665///
4666/// ```c
4667/// int xmlParseSDDecl(xmlParserCtxtPtr ctxt);
4668/// ```
4669#[no_mangle]
4670pub unsafe extern "C" fn xmlParseSDDecl(ctxt: *mut _xmlParserCtxt) -> c_int {
4671    pi_parse_sd_decl(ctxt)
4672}
4673
4674/// `xmlParseXMLDecl`.
4675///
4676/// ```c
4677/// void xmlParseXMLDecl(xmlParserCtxtPtr ctxt);
4678/// ```
4679#[no_mangle]
4680pub unsafe extern "C" fn xmlParseXMLDecl(ctxt: *mut _xmlParserCtxt) {
4681    pi_parse_xml_decl(ctxt)
4682}
4683
4684/// `xmlParseTextDecl`.
4685///
4686/// ```c
4687/// void xmlParseTextDecl(xmlParserCtxtPtr ctxt);
4688/// ```
4689#[no_mangle]
4690pub unsafe extern "C" fn xmlParseTextDecl(ctxt: *mut _xmlParserCtxt) {
4691    pi_parse_text_decl(ctxt)
4692}
4693
4694/// `xmlParseExternalSubset`.
4695///
4696/// ```c
4697/// void xmlParseExternalSubset(xmlParserCtxtPtr ctxt, const xmlChar *publicId,
4698///                             const xmlChar *systemId);
4699/// ```
4700#[no_mangle]
4701pub unsafe extern "C" fn xmlParseExternalSubset(
4702    ctxt: *mut _xmlParserCtxt,
4703    public_id: *const xmlChar,
4704    system_id: *const xmlChar,
4705) {
4706    pi_parse_external_subset(ctxt, public_id, system_id)
4707}
4708
4709/// `xmlParseDocTypeDecl`.
4710///
4711/// ```c
4712/// void xmlParseDocTypeDecl(xmlParserCtxtPtr ctxt);
4713/// ```
4714#[no_mangle]
4715pub unsafe extern "C" fn xmlParseDocTypeDecl(ctxt: *mut _xmlParserCtxt) {
4716    pi_parse_doc_type_decl(ctxt)
4717}
4718
4719/// `xmlParseAttribute`.
4720///
4721/// ```c
4722/// const xmlChar *xmlParseAttribute(xmlParserCtxtPtr ctxt, xmlChar **value);
4723/// ```
4724#[no_mangle]
4725pub unsafe extern "C" fn xmlParseAttribute(
4726    ctxt: *mut _xmlParserCtxt,
4727    value: *mut *mut xmlChar,
4728) -> *const xmlChar {
4729    if ctxt.is_null() || value.is_null() {
4730        return ptr::null();
4731    }
4732    pi_parse_attribute(ctxt, value)
4733}
4734
4735/// `xmlParseStartTag`.
4736///
4737/// ```c
4738/// const xmlChar *xmlParseStartTag(xmlParserCtxtPtr ctxt);
4739/// ```
4740#[no_mangle]
4741pub unsafe extern "C" fn xmlParseStartTag(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
4742    pi_parse_start_tag(ctxt)
4743}
4744
4745/// `xmlParseEndTag`.
4746///
4747/// ```c
4748/// void xmlParseEndTag(xmlParserCtxtPtr ctxt);
4749/// ```
4750#[no_mangle]
4751pub unsafe extern "C" fn xmlParseEndTag(ctxt: *mut _xmlParserCtxt) {
4752    pi_parse_end_tag(ctxt)
4753}
4754
4755/// `xmlParseElement`.
4756///
4757/// ```c
4758/// void xmlParseElement(xmlParserCtxtPtr ctxt);
4759/// ```
4760#[no_mangle]
4761pub unsafe extern "C" fn xmlParseElement(ctxt: *mut _xmlParserCtxt) {
4762    pi_parse_element(ctxt)
4763}
4764
4765/// `xmlParseContent`.
4766///
4767/// ```c
4768/// void xmlParseContent(xmlParserCtxtPtr ctxt);
4769/// ```
4770#[no_mangle]
4771pub unsafe extern "C" fn xmlParseContent(ctxt: *mut _xmlParserCtxt) {
4772    pi_parse_content(ctxt)
4773}
4774
4775/// `xmlParseMisc`.
4776///
4777/// ```c
4778/// void xmlParseMisc(xmlParserCtxtPtr ctxt);
4779/// ```
4780#[no_mangle]
4781pub unsafe extern "C" fn xmlParseMisc(ctxt: *mut _xmlParserCtxt) {
4782    pi_parse_misc(ctxt)
4783}
4784
4785// ═══════════════════════════════════════════════════════════════════════════════
4786// Document-level entry points
4787// ═══════════════════════════════════════════════════════════════════════════════
4788
4789/// `xmlParseCtxtExternalEntity`.
4790///
4791/// ```c
4792/// int xmlParseCtxtExternalEntity(xmlParserCtxtPtr ctx, const xmlChar *URL,
4793///                                const xmlChar *ID, xmlNode **lst);
4794/// ```
4795#[no_mangle]
4796pub unsafe extern "C" fn xmlParseCtxtExternalEntity(
4797    ctxt: *mut _xmlParserCtxt,
4798    url: *const xmlChar,
4799    _id: *const xmlChar,
4800    list_out: *mut *mut _xmlNode,
4801) -> c_int {
4802    unsafe {
4803        if !list_out.is_null() {
4804            *list_out = ptr::null_mut();
4805        }
4806        if ctxt.is_null() {
4807            return XML_ERR_ARGUMENT;
4808        }
4809        if url.is_null() {
4810            pi_fatal_err(ctxt, XML_ERR_INTERNAL_ERROR);
4811            return (*ctxt).errNo;
4812        }
4813        // Load the entity content from the URL (treated as a file path).
4814        let url_cstr = url as *const c_char;
4815        let input_buf = match input_from_file(url_cstr) {
4816            Ok(b) => b,
4817            Err(_) => {
4818                pi_fatal_err(ctxt, XML_ERR_INTERNAL_ERROR);
4819                return (*ctxt).errNo;
4820            }
4821        };
4822        let input = {
4823            // Wrap the buffer in a _xmlParserInput; keep the InputBuffer
4824            // alive by boxing it and stashing it in _private.
4825            let boxed = Box::into_raw(Box::new(input_buf));
4826            let pi = xmlMallocZero(size_of::<_xmlParserInput>()) as *mut _xmlParserInput;
4827            if pi.is_null() {
4828                let _ = Box::from_raw(boxed);
4829                pi_err_memory(ctxt);
4830                return (*ctxt).errNo;
4831            }
4832            (*boxed).populate_parser_input_without_filename(&mut *pi);
4833            // Own a C copy of the buffer's filename: the boxed InputBuffer is
4834            // freed with the context (free_parser_ctxt), so borrowing its
4835            // Rust String here would leave a dangling `filename` (the
4836            // observed heap-reuse garbage in TREE-001).
4837            if let Some(fname) = (*boxed).filename() {
4838                (*pi).filename = crate::xml::string::xml_strndup(
4839                    fname.as_ptr() as *const crate::abi::types::xmlChar,
4840                    fname.len(),
4841                ) as *const c_char;
4842            }
4843            (*pi).buf = ptr::null_mut();
4844            (*pi).directory = ptr::null();
4845            (*pi).free = None;
4846            (*pi).encoding = ptr::null();
4847            (*pi).version = ptr::null();
4848            (*pi).flags = 0;
4849            (*pi).id = 0;
4850            (*pi).parentConsumed = 0;
4851            (*pi).entity = ptr::null_mut();
4852            // stash the box so it outlives the parse (side table; ctxt._private
4853            // stays application data — 11.1-X)
4854            crate::xml::parser::helpers::stash_input_buffer(ctxt, boxed);
4855            pi
4856        };
4857
4858        pi_ctxt_late_init(ctxt);
4859        let list = pi_parse_content_node_list(ctxt, input, 1);
4860        if !list_out.is_null() {
4861            *list_out = list;
4862        } else if !list.is_null() {
4863            crate::xml::tree::free_node_list(list);
4864        }
4865        (*ctxt).errNo
4866    }
4867}
4868
4869/// `xmlParseExternalEntity`.
4870///
4871/// ```c
4872/// int xmlParseExternalEntity(xmlDoc *doc, xmlSAXHandler *sax, void *user_data,
4873///                            int depth, const xmlChar *URL, const xmlChar *ID,
4874///                            xmlNode **lst);
4875/// ```
4876#[no_mangle]
4877pub unsafe extern "C" fn xmlParseExternalEntity(
4878    doc: *mut _xmlDoc,
4879    sax: *mut _xmlSAXHandler,
4880    user_data: *mut c_void,
4881    depth: c_int,
4882    url: *const xmlChar,
4883    id: *const xmlChar,
4884    list: *mut *mut _xmlNode,
4885) -> c_int {
4886    unsafe {
4887        if !list.is_null() {
4888            *list = ptr::null_mut();
4889        }
4890        if doc.is_null() {
4891            return XML_ERR_ARGUMENT;
4892        }
4893        let ctxt = create_parser_ctxt();
4894        if ctxt.is_null() {
4895            return XML_ERR_NO_MEMORY;
4896        }
4897        // Install the given SAX handler (or the default one).
4898        if !sax.is_null() {
4899            let dst = (*ctxt).sax;
4900            if !dst.is_null() {
4901                // Copy the handler struct; only copy the first-class fields.
4902                ptr::copy_nonoverlapping(sax, dst, 1);
4903            }
4904            (*ctxt).userData = user_data;
4905        }
4906        (*ctxt).depth = depth;
4907        (*ctxt).myDoc = doc;
4908        let ret = xmlParseCtxtExternalEntity(ctxt, url, id, list);
4909        free_parser_ctxt(ctxt);
4910        ret
4911    }
4912}
4913
4914/// `xmlParseBalancedChunkMemory`.
4915///
4916/// ```c
4917/// int xmlParseBalancedChunkMemory(xmlDoc *doc, xmlSAXHandler *sax, void *user_data,
4918///                                 int depth, const xmlChar *string, xmlNode **lst);
4919/// ```
4920#[no_mangle]
4921pub unsafe extern "C" fn xmlParseBalancedChunkMemory(
4922    doc: *mut _xmlDoc,
4923    sax: *mut _xmlSAXHandler,
4924    user_data: *mut c_void,
4925    depth: c_int,
4926    string: *const xmlChar,
4927    lst: *mut *mut _xmlNode,
4928) -> c_int {
4929    xmlParseBalancedChunkMemoryRecover(doc, sax, user_data, depth, string, lst, 0)
4930}
4931
4932/// `xmlParseBalancedChunkMemoryRecover`.
4933///
4934/// ```c
4935/// int xmlParseBalancedChunkMemoryRecover(xmlDoc *doc, xmlSAXHandler *sax,
4936///                                        void *user_data, int depth,
4937///                                        const xmlChar *string, xmlNode **lst,
4938///                                        int recover);
4939/// ```
4940#[no_mangle]
4941pub unsafe extern "C" fn xmlParseBalancedChunkMemoryRecover(
4942    doc: *mut _xmlDoc,
4943    sax: *mut _xmlSAXHandler,
4944    user_data: *mut c_void,
4945    depth: c_int,
4946    string: *const xmlChar,
4947    list_out: *mut *mut _xmlNode,
4948    recover: c_int,
4949) -> c_int {
4950    unsafe {
4951        if !list_out.is_null() {
4952            *list_out = ptr::null_mut();
4953        }
4954        if string.is_null() {
4955            return XML_ERR_ARGUMENT;
4956        }
4957        if doc.is_null() {
4958            return XML_ERR_ARGUMENT;
4959        }
4960        let ctxt = create_parser_ctxt();
4961        if ctxt.is_null() {
4962            return XML_ERR_NO_MEMORY;
4963        }
4964        if !sax.is_null() {
4965            let dst = (*ctxt).sax;
4966            if !dst.is_null() {
4967                ptr::copy_nonoverlapping(sax, dst, 1);
4968            }
4969            (*ctxt).userData = user_data;
4970        }
4971        pi_ctxt_late_init(ctxt);
4972        (*ctxt).depth = depth;
4973        (*ctxt).myDoc = doc;
4974        if recover != 0 {
4975            (*ctxt).options |= XML_PARSE_RECOVER;
4976            (*ctxt).recovery = 1;
4977        }
4978
4979        let slen = crate::abi::exports_xml2::xmlStrlen(string) as c_int;
4980        let input_buf = input_from_memory(string as *const c_char, slen);
4981        // Keep the buffer alive via the side table (ctxt._private stays
4982        // application data — 11.1-X).
4983        let boxed = Box::into_raw(Box::new(input_buf));
4984        crate::xml::parser::helpers::stash_input_buffer(ctxt, boxed);
4985        let input = xmlMallocZero(size_of::<_xmlParserInput>()) as *mut _xmlParserInput;
4986        if input.is_null() {
4987            let _ = Box::from_raw(boxed);
4988            crate::xml::parser::helpers::free_stashed_input_buffer(ctxt);
4989            let ret = (*ctxt).errNo;
4990            free_parser_ctxt(ctxt);
4991            return if ret != 0 { ret } else { XML_ERR_NO_MEMORY };
4992        }
4993        (*boxed).populate_parser_input_without_filename(&mut *input);
4994        if let Some(fname) = (*boxed).filename() {
4995            (*input).filename = crate::xml::string::xml_strndup(
4996                fname.as_ptr() as *const crate::abi::types::xmlChar,
4997                fname.len(),
4998            ) as *const c_char;
4999        }
5000        (*input).buf = ptr::null_mut();
5001        (*input).directory = ptr::null();
5002        (*input).free = None;
5003        (*input).encoding = ptr::null();
5004        (*input).version = ptr::null();
5005        (*input).flags = 0;
5006        (*input).id = 0;
5007        (*input).parentConsumed = 0;
5008        (*input).entity = ptr::null_mut();
5009
5010        let list = pi_parse_content_node_list(ctxt, input, 0);
5011        if !list_out.is_null() {
5012            *list_out = list;
5013        } else if !list.is_null() {
5014            crate::xml::tree::free_node_list(list);
5015        }
5016        let ret = if (*ctxt).wellFormed == 0 {
5017            (*ctxt).errNo
5018        } else {
5019            XML_ERR_OK
5020        };
5021        free_parser_ctxt(ctxt);
5022        ret
5023    }
5024}
5025
5026/// `xmlParseInNodeContext`.
5027///
5028/// ```c
5029/// xmlParserErrors xmlParseInNodeContext(xmlNode *node, const char *data,
5030///                                       int datalen, int options, xmlNode **lst);
5031/// ```
5032#[no_mangle]
5033pub unsafe extern "C" fn xmlParseInNodeContext(
5034    node: *mut _xmlNode,
5035    data: *const c_char,
5036    datalen: c_int,
5037    options: c_int,
5038    list_out: *mut *mut _xmlNode,
5039) -> c_int {
5040    unsafe {
5041        if list_out.is_null() {
5042            return XML_ERR_INTERNAL_ERROR;
5043        }
5044        *list_out = ptr::null_mut();
5045        if node.is_null() || data.is_null() || datalen < 0 {
5046            return XML_ERR_INTERNAL_ERROR;
5047        }
5048        let doc = (*node).doc;
5049        if doc.is_null() {
5050            return XML_ERR_INTERNAL_ERROR;
5051        }
5052        let ctxt = create_parser_ctxt();
5053        if ctxt.is_null() {
5054            return XML_ERR_NO_MEMORY;
5055        }
5056        (*ctxt).options = options;
5057
5058        let input_buf = input_from_memory(data, datalen);
5059        let boxed = Box::into_raw(Box::new(input_buf));
5060        crate::xml::parser::helpers::stash_input_buffer(ctxt, boxed);
5061        let input = xmlMallocZero(size_of::<_xmlParserInput>()) as *mut _xmlParserInput;
5062        if input.is_null() {
5063            let _ = Box::from_raw(boxed);
5064            crate::xml::parser::helpers::free_stashed_input_buffer(ctxt);
5065            free_parser_ctxt(ctxt);
5066            return XML_ERR_NO_MEMORY;
5067        }
5068        (*boxed).populate_parser_input_without_filename(&mut *input);
5069        if let Some(fname) = (*boxed).filename() {
5070            (*input).filename = crate::xml::string::xml_strndup(
5071                fname.as_ptr() as *const crate::abi::types::xmlChar,
5072                fname.len(),
5073            ) as *const c_char;
5074        }
5075        (*input).buf = ptr::null_mut();
5076        (*input).directory = ptr::null();
5077        (*input).free = None;
5078        (*input).encoding = ptr::null();
5079        (*input).version = ptr::null();
5080        (*input).flags = 0;
5081        (*input).id = 0;
5082        (*input).parentConsumed = 0;
5083        (*input).entity = ptr::null_mut();
5084
5085        pi_ctxt_late_init(ctxt);
5086        (*ctxt).myDoc = doc;
5087
5088        // Push namespaces in scope of the node onto the SAX2 ns stack.
5089        // (Simplified: only the direct nsDef chain of the node.)
5090        let mut ns_cur = (*node).nsDef;
5091        while !ns_cur.is_null() {
5092            let nsp = ns_cur;
5093            ns_cur = (*ns_cur).next;
5094            // Create a namespace declaration on the synthetic root later;
5095            // record them by pushing onto ctxt->nsTab (SAX2 ns stack).
5096            if !(*nsp).prefix.is_null() {
5097                // push (prefix, href) onto nsTab
5098            }
5099        }
5100
5101        let list = pi_parse_content_node_list(ctxt, input, 0);
5102        if list.is_null() {
5103            let ret = (*ctxt).errNo;
5104            if ret == XML_ERR_ARGUMENT {
5105                free_parser_ctxt(ctxt);
5106                return XML_ERR_INTERNAL_ERROR;
5107            }
5108            free_parser_ctxt(ctxt);
5109            return if ret != 0 {
5110                ret
5111            } else {
5112                XML_ERR_INTERNAL_ERROR
5113            };
5114        }
5115        *list_out = list;
5116        free_parser_ctxt(ctxt);
5117        XML_ERR_OK
5118    }
5119}
5120
5121/// `xmlParseDTD`.
5122///
5123/// ```c
5124/// xmlDtdPtr xmlParseDTD(const xmlChar *publicId, const xmlChar *systemId);
5125/// ```
5126#[no_mangle]
5127pub unsafe extern "C" fn xmlParseDTD(
5128    public_id: *const xmlChar,
5129    system_id: *const xmlChar,
5130) -> *mut _xmlDtd {
5131    unsafe {
5132        if public_id.is_null() && system_id.is_null() {
5133            return ptr::null_mut();
5134        }
5135        if system_id.is_null() {
5136            return ptr::null_mut();
5137        }
5138        let ctxt = create_parser_ctxt();
5139        if ctxt.is_null() {
5140            return ptr::null_mut();
5141        }
5142        pi_ctxt_late_init(ctxt);
5143        (*ctxt).inSubset = 2;
5144        (*ctxt).hasExternalSubset = 1;
5145
5146        let mut ret: *mut _xmlDtd = ptr::null_mut();
5147        let input_buf = input_from_file(system_id as *const c_char);
5148        if let Err(()) = input_buf {
5149            // UPSTREAM-PARITY (parserInternals.c xmlNewInputFromFile via the
5150            // default entity loader): a failed load raises
5151            // xmlCtxtErrIO(ctxt, XML_IO_ENOENT, url) — "I/O warning :
5152            // failed to load \"%s\": %s\n" (HOSTILE-FAILURE F7).
5153            let errno = *libc::__errno_location();
5154            let errstr = if errno == 0 {
5155                String::new()
5156            } else {
5157                std::ffi::CStr::from_ptr(libc::strerror(errno))
5158                    .to_string_lossy()
5159                    .into_owned()
5160            };
5161            let url_str = std::ffi::CStr::from_ptr(system_id as *const c_char).to_string_lossy();
5162            crate::abi::exports_parser::emit_io_warning(
5163                ctxt,
5164                format!("failed to load \"{url_str}\": {errstr}\n"),
5165            );
5166        }
5167        if let Ok(buf) = input_buf {
5168            let boxed = Box::into_raw(Box::new(buf));
5169            crate::xml::parser::helpers::stash_input_buffer(ctxt, boxed);
5170            let input = xmlMallocZero(size_of::<_xmlParserInput>()) as *mut _xmlParserInput;
5171            if !input.is_null() {
5172                (*boxed).populate_parser_input_without_filename(&mut *input);
5173                if let Some(fname) = (*boxed).filename() {
5174                    (*input).filename = crate::xml::string::xml_strndup(
5175                        fname.as_ptr() as *const crate::abi::types::xmlChar,
5176                        fname.len(),
5177                    ) as *const c_char;
5178                }
5179                (*input).buf = ptr::null_mut();
5180                (*input).directory = ptr::null();
5181                (*input).free = None;
5182                (*input).encoding = ptr::null();
5183                (*input).version = ptr::null();
5184                (*input).flags = 0;
5185                (*input).id = 0;
5186                (*input).parentConsumed = 0;
5187                (*input).entity = ptr::null_mut();
5188                // Make it the main input.
5189                (*ctxt).input = input;
5190                (*ctxt).inputNr = 1;
5191                let tab = xmlMallocZero(4 * size_of::<*mut _xmlParserInput>())
5192                    as *mut *mut _xmlParserInput;
5193                if !tab.is_null() {
5194                    *tab = input;
5195                    (*ctxt).inputTab = tab;
5196                    (*ctxt).inputMax = 4;
5197                }
5198                pi_parse_external_subset(ctxt, public_id, system_id);
5199            }
5200        }
5201
5202        if (*ctxt).wellFormed != 0 && !(*ctxt).myDoc.is_null() {
5203            let doc = (*ctxt).myDoc;
5204            if !(*doc).intSubset.is_null() {
5205                ret = (*doc).intSubset;
5206            } else if !(*doc).extSubset.is_null() {
5207                ret = (*doc).extSubset;
5208            }
5209        }
5210        free_parser_ctxt(ctxt);
5211        ret
5212    }
5213}
5214
5215/// `xmlParseEntity`.
5216///
5217/// ```c
5218/// xmlDocPtr xmlParseEntity(const char *filename);
5219/// ```
5220#[no_mangle]
5221pub unsafe extern "C" fn xmlParseEntity(filename: *const c_char) -> *mut _xmlDoc {
5222    unsafe {
5223        if filename.is_null() {
5224            return ptr::null_mut();
5225        }
5226        let ctxt = create_parser_ctxt();
5227        if ctxt.is_null() {
5228            return ptr::null_mut();
5229        }
5230        let input_buf = match input_from_file(filename) {
5231            Ok(b) => b,
5232            Err(_) => {
5233                free_parser_ctxt(ctxt);
5234                return ptr::null_mut();
5235            }
5236        };
5237        setup_parser_input(ctxt, input_buf);
5238        xmlParseExtParsedEnt(ctxt);
5239        let ret = if (*ctxt).wellFormed != 0 {
5240            (*ctxt).myDoc
5241        } else {
5242            let doc = (*ctxt).myDoc;
5243            if !doc.is_null() {
5244                crate::xml::tree::free_doc(doc);
5245                (*ctxt).myDoc = ptr::null_mut();
5246            }
5247            ptr::null_mut()
5248        };
5249        // Detach the doc so the context free doesn't touch it.
5250        (*ctxt).myDoc = ptr::null_mut();
5251        free_parser_ctxt(ctxt);
5252        ret
5253    }
5254}
5255
5256/// `xmlParseExtParsedEnt`.
5257///
5258/// ```c
5259/// int xmlParseExtParsedEnt(xmlParserCtxtPtr ctxt);
5260/// ```
5261#[no_mangle]
5262pub unsafe extern "C" fn xmlParseExtParsedEnt(ctxt: *mut _xmlParserCtxt) -> c_int {
5263    pi_parse_ext_parsed_ent(ctxt)
5264}
5265
5266// ═══════════════════════════════════════════════════════════════════════════════
5267// xmlParserInput* input handling (parserInternals.c / parser.h)
5268// ═══════════════════════════════════════════════════════════════════════════════
5269
5270// `xmlParserInputRead` — deprecated, always an error.
5271//
5272// ```c
5273// int xmlParserInputRead(xmlParserInput *in, int len);
5274// `xmlParserInputGrow`.
5275//
5276// ```c
5277// int xmlParserInputGrow(xmlParserInput *in, int len);
5278// `xmlParserInputShrink`.
5279//
5280// ```c
5281// void xmlParserInputShrink(xmlParserInput *in);
5282// ═══════════════════════════════════════════════════════════════════════════════
5283// xmlParserInputBuffer* (xmlIO.h)
5284// ═══════════════════════════════════════════════════════════════════════════════
5285
5286/// Opaque memory backing for a `_xmlParserInputBuffer`.
5287#[repr(C)]
5288struct PiInputMem {
5289    data: *mut u8,
5290    len: usize,
5291    cap: usize,
5292    owned: bool,
5293}
5294
5295unsafe fn pi_mem_create() -> *mut PiInputMem {
5296    unsafe { xmlMallocZero(size_of::<PiInputMem>()) as *mut PiInputMem }
5297}
5298
5299unsafe fn pi_mem_free(m: *mut PiInputMem) {
5300    unsafe {
5301        if m.is_null() {
5302            return;
5303        }
5304        if !(*m).data.is_null() && (*m).owned {
5305            xmlFreeImpl((*m).data as *mut c_void);
5306        }
5307        xmlFreeImpl(m as *mut c_void);
5308    }
5309}
5310
5311unsafe fn pi_mem_grow(m: *mut PiInputMem, extra: usize) -> bool {
5312    unsafe {
5313        if m.is_null() {
5314            return false;
5315        }
5316        if (*m).len + extra <= (*m).cap {
5317            return true;
5318        }
5319        let mut new_cap = if (*m).cap == 0 { 256 } else { (*m).cap };
5320        while new_cap < (*m).len + extra {
5321            new_cap *= 2;
5322        }
5323        if !(*m).owned {
5324            // Convert a static buffer into an owned copy.
5325            let data = xmlMallocImpl(new_cap) as *mut u8;
5326            if data.is_null() {
5327                return false;
5328            }
5329            if (*m).len > 0 && !(*m).data.is_null() {
5330                ptr::copy_nonoverlapping((*m).data, data, (*m).len);
5331            }
5332            (*m).data = data;
5333            (*m).owned = true;
5334            (*m).cap = new_cap;
5335            return true;
5336        }
5337        let data = xmlReallocImpl((*m).data as *mut c_void, new_cap) as *mut u8;
5338        if data.is_null() {
5339            return false;
5340        }
5341        (*m).data = data;
5342        (*m).cap = new_cap;
5343        true
5344    }
5345}
5346
5347/// Read callback for fd-backed buffers.
5348unsafe extern "C" fn pi_fd_read(context: *mut c_void, buffer: *mut c_char, len: c_int) -> c_int {
5349    unsafe {
5350        if context.is_null() || buffer.is_null() || len <= 0 {
5351            return -1;
5352        }
5353        let fd = *(context as *const c_int);
5354        libc::read(fd, buffer as *mut c_void, len as usize) as c_int
5355    }
5356}
5357
5358/// Close callback for fd-backed buffers: releases the boxed fd.
5359unsafe extern "C" fn pi_fd_close(context: *mut c_void) -> c_int {
5360    unsafe {
5361        if context.is_null() {
5362            return -1;
5363        }
5364        let _ = Box::from_raw(context as *mut c_int);
5365        0
5366    }
5367}
5368
5369/// Read callback for FILE*-backed buffers.
5370unsafe extern "C" fn pi_file_read(context: *mut c_void, buffer: *mut c_char, len: c_int) -> c_int {
5371    unsafe {
5372        if context.is_null() || buffer.is_null() || len <= 0 {
5373            return -1;
5374        }
5375        libc::fread(
5376            buffer as *mut c_void,
5377            1,
5378            len as usize,
5379            context as *mut libc::FILE,
5380        ) as c_int
5381    }
5382}
5383
5384/// `xmlParserInputBufferCreateFd`.
5385///
5386/// ```c
5387/// xmlParserInputBufferPtr xmlParserInputBufferCreateFd(int fd, xmlCharEncoding enc);
5388/// ```
5389#[no_mangle]
5390pub unsafe extern "C" fn xmlParserInputBufferCreateFd(
5391    fd: c_int,
5392    _enc: c_int,
5393) -> *mut _xmlParserInputBuffer {
5394    unsafe {
5395        if fd < 0 {
5396            return ptr::null_mut();
5397        }
5398        let buf = crate::xml::parser::helpers::alloc_parser_input_buffer();
5399        if buf.is_null() {
5400            return ptr::null_mut();
5401        }
5402        let fd_box = Box::into_raw(Box::new(fd));
5403        (*buf).context = fd_box as *mut c_void;
5404        (*buf).readcallback = Some(pi_fd_read);
5405        (*buf).closecallback = Some(pi_fd_close);
5406        (*buf).compressed = -1;
5407        (*buf).buffer = pi_mem_create() as *mut c_void;
5408        buf
5409    }
5410}
5411
5412/// `xmlParserInputBufferCreateFile`.
5413///
5414/// ```c
5415/// xmlParserInputBufferPtr xmlParserInputBufferCreateFile(FILE *file, xmlCharEncoding enc);
5416/// ```
5417#[no_mangle]
5418pub unsafe extern "C" fn xmlParserInputBufferCreateFile(
5419    file: *mut c_void,
5420    _enc: c_int,
5421) -> *mut _xmlParserInputBuffer {
5422    unsafe {
5423        if file.is_null() {
5424            return ptr::null_mut();
5425        }
5426        let buf = crate::xml::parser::helpers::alloc_parser_input_buffer();
5427        if buf.is_null() {
5428            return ptr::null_mut();
5429        }
5430        (*buf).context = file;
5431        (*buf).readcallback = Some(pi_file_read);
5432        (*buf).closecallback = None;
5433        (*buf).compressed = -1;
5434        (*buf).buffer = pi_mem_create() as *mut c_void;
5435        buf
5436    }
5437}
5438
5439/// `xmlParserInputBufferCreateStatic`.
5440///
5441/// ```c
5442/// xmlParserInputBufferPtr xmlParserInputBufferCreateStatic(const char *mem,
5443///                                                          int size, xmlCharEncoding enc);
5444/// ```
5445#[no_mangle]
5446pub unsafe extern "C" fn xmlParserInputBufferCreateStatic(
5447    mem: *const c_char,
5448    size: c_int,
5449    _enc: c_int,
5450) -> *mut _xmlParserInputBuffer {
5451    unsafe {
5452        if mem.is_null() || size < 0 {
5453            return ptr::null_mut();
5454        }
5455        let buf = crate::xml::parser::helpers::alloc_parser_input_buffer();
5456        if buf.is_null() {
5457            return ptr::null_mut();
5458        }
5459        let m = pi_mem_create();
5460        if m.is_null() {
5461            crate::xml::parser::helpers::free_parser_input_buffer(buf);
5462            return ptr::null_mut();
5463        }
5464        (*m).data = mem as *mut u8;
5465        (*m).len = size as usize;
5466        (*m).cap = size as usize;
5467        (*m).owned = false;
5468        (*buf).buffer = m as *mut c_void;
5469        (*buf).compressed = -1;
5470        buf
5471    }
5472}
5473
5474/// Local stand-in for upstream `__xmlParserInputBufferCreateFilename` (the
5475/// default filename→buffer factory used when no custom loader is installed).
5476unsafe extern "C" fn pi_default_input_buffer_create_filename(
5477    _uri: *const c_char,
5478    _enc: c_int,
5479) -> *mut _xmlParserInputBuffer {
5480    unsafe { crate::xml::parser::helpers::alloc_parser_input_buffer() }
5481}
5482
5483/// `xmlParserInputBufferCreateFilenameDefault`.
5484///
5485/// ```c
5486/// xmlParserInputBufferCreateFilenameFunc
5487/// xmlParserInputBufferCreateFilenameDefault(xmlParserInputBufferCreateFilenameFunc func);
5488/// ```
5489#[no_mangle]
5490/// UPSTREAM-PARITY: comparing the registered callback against the default
5491/// function pointer to decide reset-vs-replace mirrors upstream globals.c;
5492/// on ELF platforms the address of a symbol is stable and unique within the
5493/// DSO.
5494#[allow(
5495    renamed_and_removed_lints,
5496    clippy::fn_address_comparisons,
5497    unpredictable_function_pointer_comparisons
5498)]
5499pub unsafe extern "C" fn xmlParserInputBufferCreateFilenameDefault(
5500    func: Option<unsafe extern "C" fn(*const c_char, c_int) -> *mut _xmlParserInputBuffer>,
5501) -> Option<unsafe extern "C" fn(*const c_char, c_int) -> *mut _xmlParserInputBuffer> {
5502    let default_fn: unsafe extern "C" fn(*const c_char, c_int) -> *mut _xmlParserInputBuffer =
5503        pi_default_input_buffer_create_filename;
5504    let old = crate::xml::globals::get_parser_input_buffer_create_filename_value();
5505    crate::xml::globals::set_parser_input_buffer_create_filename_value(
5506        if func == Some(default_fn) { None } else { func },
5507    );
5508    old.or(Some(default_fn))
5509}
5510
5511// ═══════════════════════════════════════════════════════════════════════════════
5512// xmlCtxt accessor / 2.14+ parser-API family (parser.h / parserInternals.h)
5513// ═══════════════════════════════════════════════════════════════════════════════
5514//
5515// R-000165 closure (11.1-X): the parser-context accessors and the 2.14+
5516// input constructors ported from archaeology/libxml2-git (parser.c /
5517// parserInternals.c 2.15.3). NULL contexts return NULL/0/-1 exactly like
5518// upstream; every function is exported with the upstream name so the
5519// header-compile court's declared-functions-exported check closes.
5520
5521/// Upstream `xmlCtxtIsCatastrophicError` — errNo in the catastrophic set.
5522unsafe fn pi_ctxt_is_catastrophic(ctxt: *mut _xmlParserCtxt) -> c_int {
5523    if ctxt.is_null() {
5524        return 1;
5525    }
5526    unsafe {
5527        let e = (*ctxt).errNo;
5528        if e == crate::abi::types::XML_ERR_NO_MEMORY
5529            || e == crate::abi::types::XML_ERR_INTERNAL_ERROR
5530            || e == crate::abi::types::XML_ERR_RESOURCE_LIMIT
5531        {
5532            1
5533        } else {
5534            0
5535        }
5536    }
5537}
5538
5539/// `xmlCtxtGetVersion` — the XML version declared in the document.
5540#[no_mangle]
5541pub unsafe extern "C" fn xmlCtxtGetVersion(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
5542    if ctxt.is_null() {
5543        return ptr::null();
5544    }
5545    unsafe { (*ctxt).version as *const xmlChar }
5546}
5547
5548/// `xmlCtxtGetStandalone` — standalone status (-1 unset, 0 no, 1 yes).
5549#[no_mangle]
5550pub unsafe extern "C" fn xmlCtxtGetStandalone(ctxt: *mut _xmlParserCtxt) -> c_int {
5551    if ctxt.is_null() {
5552        return -1;
5553    }
5554    unsafe { (*ctxt).standalone }
5555}
5556
5557/// `xmlCtxtGetOptions` — the parser options bitmask.
5558#[no_mangle]
5559pub unsafe extern "C" fn xmlCtxtGetOptions(ctxt: *mut _xmlParserCtxt) -> c_int {
5560    if ctxt.is_null() {
5561        return 0;
5562    }
5563    unsafe { (*ctxt).options }
5564}
5565
5566/// `xmlCtxtGetPrivate` — the private application data.
5567#[no_mangle]
5568pub unsafe extern "C" fn xmlCtxtGetPrivate(ctxt: *mut _xmlParserCtxt) -> *mut c_void {
5569    if ctxt.is_null() {
5570        return ptr::null_mut();
5571    }
5572    unsafe { (*ctxt)._private }
5573}
5574
5575/// `xmlCtxtSetPrivate` — set the private application data.
5576#[no_mangle]
5577pub unsafe extern "C" fn xmlCtxtSetPrivate(ctxt: *mut _xmlParserCtxt, priv_: *mut c_void) {
5578    if ctxt.is_null() {
5579        return;
5580    }
5581    unsafe { (*ctxt)._private = priv_ };
5582}
5583
5584/// `xmlCtxtGetCatalogs` — the local catalogs.
5585#[no_mangle]
5586pub unsafe extern "C" fn xmlCtxtGetCatalogs(ctxt: *mut _xmlParserCtxt) -> *mut c_void {
5587    if ctxt.is_null() {
5588        return ptr::null_mut();
5589    }
5590    unsafe { (*ctxt).catalogs }
5591}
5592
5593/// `xmlCtxtSetCatalogs` — set the local catalogs.
5594#[no_mangle]
5595pub unsafe extern "C" fn xmlCtxtSetCatalogs(ctxt: *mut _xmlParserCtxt, catalogs: *mut c_void) {
5596    if ctxt.is_null() {
5597        return;
5598    }
5599    unsafe { (*ctxt).catalogs = catalogs };
5600}
5601
5602/// `xmlCtxtGetDict` — the dictionary.
5603#[no_mangle]
5604pub unsafe extern "C" fn xmlCtxtGetDict(ctxt: *mut _xmlParserCtxt) -> *mut c_void {
5605    if ctxt.is_null() {
5606        return ptr::null_mut();
5607    }
5608    unsafe { (*ctxt).dict }
5609}
5610
5611/// `xmlCtxtSetDict` — replace the dictionary (old one freed, new referenced).
5612#[no_mangle]
5613pub unsafe extern "C" fn xmlCtxtSetDict(ctxt: *mut _xmlParserCtxt, dict: *mut c_void) {
5614    if ctxt.is_null() {
5615        return;
5616    }
5617    unsafe {
5618        if !(*ctxt).dict.is_null() {
5619            crate::abi::exports_xml2::xmlDictFree((*ctxt).dict);
5620        }
5621        if !dict.is_null() {
5622            crate::abi::exports_hash::xmlDictReference(dict);
5623        }
5624        (*ctxt).dict = dict;
5625    }
5626}
5627
5628/// `xmlCtxtGetSaxHandler` — the SAX handler struct (not a copy).
5629#[no_mangle]
5630pub unsafe extern "C" fn xmlCtxtGetSaxHandler(ctxt: *mut _xmlParserCtxt) -> *mut _xmlSAXHandler {
5631    if ctxt.is_null() {
5632        return ptr::null_mut();
5633    }
5634    unsafe { (*ctxt).sax }
5635}
5636
5637/// `xmlCtxtSetSaxHandler` — copy `sax` into the context's handler struct.
5638#[no_mangle]
5639pub unsafe extern "C" fn xmlCtxtSetSaxHandler(
5640    ctxt: *mut _xmlParserCtxt,
5641    sax: *const _xmlSAXHandler,
5642) -> c_int {
5643    if ctxt.is_null() || (*ctxt).sax.is_null() || sax.is_null() {
5644        return -1;
5645    }
5646    unsafe {
5647        ptr::copy_nonoverlapping(sax, (*ctxt).sax, 1);
5648    }
5649    0
5650}
5651
5652/// `xmlCtxtIsHtml` — 1 if this is an HTML parser context.
5653#[no_mangle]
5654pub unsafe extern "C" fn xmlCtxtIsHtml(ctxt: *mut _xmlParserCtxt) -> c_int {
5655    if ctxt.is_null() {
5656        return 0;
5657    }
5658    unsafe { (*ctxt).html }
5659}
5660
5661/// `xmlCtxtIsStopped` — 1 if the parser is stopped (disableSAX != 0).
5662#[no_mangle]
5663pub unsafe extern "C" fn xmlCtxtIsStopped(ctxt: *mut _xmlParserCtxt) -> c_int {
5664    if ctxt.is_null() {
5665        return 0;
5666    }
5667    unsafe {
5668        if (*ctxt).disableSAX != 0 {
5669            1
5670        } else {
5671            0
5672        }
5673    }
5674}
5675
5676/// `xmlCtxtIsInSubset` — DTD subset status (0 none, 1 internal, 2 external).
5677#[no_mangle]
5678pub unsafe extern "C" fn xmlCtxtIsInSubset(ctxt: *mut _xmlParserCtxt) -> c_int {
5679    if ctxt.is_null() {
5680        return 0;
5681    }
5682    unsafe { (*ctxt).inSubset }
5683}
5684
5685/// `xmlCtxtGetValidCtxt` — pointer to the validation context.
5686#[no_mangle]
5687pub unsafe extern "C" fn xmlCtxtGetValidCtxt(ctxt: *mut _xmlParserCtxt) -> *mut _xmlValidCtxt {
5688    if ctxt.is_null() {
5689        return ptr::null_mut();
5690    }
5691    unsafe { core::ptr::addr_of_mut!((*ctxt).vctxt) }
5692}
5693
5694/// `xmlCtxtGetUserData` — the user data.
5695#[no_mangle]
5696pub unsafe extern "C" fn xmlCtxtGetUserData(ctxt: *mut _xmlParserCtxt) -> *mut c_void {
5697    if ctxt.is_null() {
5698        return ptr::null_mut();
5699    }
5700    unsafe { (*ctxt).userData }
5701}
5702
5703/// `xmlCtxtGetNode` — the current node or the document node.
5704#[no_mangle]
5705pub unsafe extern "C" fn xmlCtxtGetNode(ctxt: *mut _xmlParserCtxt) -> *mut _xmlNode {
5706    if ctxt.is_null() {
5707        return ptr::null_mut();
5708    }
5709    unsafe {
5710        if !(*ctxt).node.is_null() {
5711            (*ctxt).node
5712        } else {
5713            (*ctxt).myDoc as *mut _xmlNode
5714        }
5715    }
5716}
5717
5718/// `xmlCtxtGetDocTypeDecl` — doctype declaration data (SAX callbacks only).
5719#[no_mangle]
5720pub unsafe extern "C" fn xmlCtxtGetDocTypeDecl(
5721    ctxt: *mut _xmlParserCtxt,
5722    name: *mut *const xmlChar,
5723    system_id: *mut *const xmlChar,
5724    public_id: *mut *const xmlChar,
5725) -> c_int {
5726    if ctxt.is_null() {
5727        return -1;
5728    }
5729    unsafe {
5730        if !name.is_null() {
5731            *name = (*ctxt).intSubName;
5732        }
5733        if !system_id.is_null() {
5734            *system_id = (*ctxt).extSubURI as *const xmlChar;
5735        }
5736        if !public_id.is_null() {
5737            *public_id = (*ctxt).extSubSystem as *const xmlChar;
5738        }
5739    }
5740    0
5741}
5742
5743/// `xmlCtxtGetInputPosition` — position of an input (outermost 0, innermost -1).
5744#[no_mangle]
5745pub unsafe extern "C" fn xmlCtxtGetInputPosition(
5746    ctxt: *mut _xmlParserCtxt,
5747    input_index: c_int,
5748    filename: *mut *const c_char,
5749    line: *mut c_int,
5750    col: *mut c_int,
5751    utf8_byte_pos: *mut c_ulong,
5752) -> c_int {
5753    unsafe {
5754        if ctxt.is_null() {
5755            return -1;
5756        }
5757        let mut idx = input_index;
5758        if idx < 0 {
5759            idx += (*ctxt).inputNr;
5760            if idx < 0 {
5761                return -1;
5762            }
5763        }
5764        if idx >= (*ctxt).inputNr || (*ctxt).inputTab.is_null() {
5765            return -1;
5766        }
5767        let input = *(*ctxt).inputTab.add(idx as usize);
5768        if input.is_null() {
5769            return -1;
5770        }
5771        if !filename.is_null() {
5772            *filename = (*input).filename;
5773        }
5774        if !line.is_null() {
5775            *line = (*input).line;
5776        }
5777        if !col.is_null() {
5778            *col = (*input).col;
5779        }
5780        if !utf8_byte_pos.is_null() {
5781            let consumed = (*input).consumed;
5782            let mut pos: c_ulong = consumed;
5783            if !(*input).cur.is_null() && !(*input).base.is_null() {
5784                pos = pos.wrapping_add(((*input).cur as usize - (*input).base as usize) as c_ulong);
5785            }
5786            *utf8_byte_pos = pos;
5787        }
5788        0
5789    }
5790}
5791
5792/// `xmlCtxtGetInputWindow` — window into the input data (upstream
5793/// xmlParserInputGetWindow; 80-char cap, UTF-8 aware).
5794#[no_mangle]
5795pub unsafe extern "C" fn xmlCtxtGetInputWindow(
5796    ctxt: *mut _xmlParserCtxt,
5797    input_index: c_int,
5798    start_out: *mut *const xmlChar,
5799    size_in_out: *mut c_int,
5800    offset_out: *mut c_int,
5801) -> c_int {
5802    unsafe {
5803        if ctxt.is_null() || start_out.is_null() || size_in_out.is_null() || offset_out.is_null() {
5804            return -1;
5805        }
5806        let mut idx = input_index;
5807        if idx < 0 {
5808            idx += (*ctxt).inputNr;
5809            if idx < 0 {
5810                return -1;
5811            }
5812        }
5813        if idx >= (*ctxt).inputNr || (*ctxt).inputTab.is_null() {
5814            return -1;
5815        }
5816        let input = *(*ctxt).inputTab.add(idx as usize);
5817        if input.is_null() {
5818            return -1;
5819        }
5820        crate::abi::exports_misc::parser_input_get_window_pub(
5821            input,
5822            start_out,
5823            size_in_out,
5824            offset_out,
5825        );
5826        0
5827    }
5828}
5829
5830/// `xmlCtxtGetStatus` — XML_STATUS_* bitmask (well-formedness/validation).
5831#[no_mangle]
5832pub unsafe extern "C" fn xmlCtxtGetStatus(ctxt: *mut _xmlParserCtxt) -> c_int {
5833    unsafe {
5834        let mut bits: c_int = 0;
5835        if pi_ctxt_is_catastrophic(ctxt) != 0 {
5836            bits |= 1 << 3; // XML_STATUS_CATASTROPHIC_ERROR
5837            bits |= 1 << 0; // XML_STATUS_NOT_WELL_FORMED
5838            bits |= 1 << 1; // XML_STATUS_NOT_NS_WELL_FORMED
5839            if !ctxt.is_null() && (*ctxt).validate != 0 {
5840                bits |= 1 << 2; // XML_STATUS_DTD_VALIDATION_FAILED
5841            }
5842            return bits;
5843        }
5844        if (*ctxt).wellFormed == 0 {
5845            bits |= 1 << 0;
5846        }
5847        if (*ctxt).nsWellFormed == 0 {
5848            bits |= 1 << 1;
5849        }
5850        if (*ctxt).validate != 0 && (*ctxt).valid == 0 {
5851            bits |= 1 << 2;
5852        }
5853        bits
5854    }
5855}
5856
5857/// `xmlCtxtGetDeclaredEncoding` — the encoding from the encoding declaration.
5858#[no_mangle]
5859pub unsafe extern "C" fn xmlCtxtGetDeclaredEncoding(ctxt: *mut _xmlParserCtxt) -> *const xmlChar {
5860    if ctxt.is_null() {
5861        return ptr::null();
5862    }
5863    unsafe { (*ctxt).encoding as *const xmlChar }
5864}
5865
5866/// `xmlCtxtGetDocument` — take the parsed document (resets the context's).
5867#[no_mangle]
5868pub unsafe extern "C" fn xmlCtxtGetDocument(ctxt: *mut _xmlParserCtxt) -> *mut _xmlDoc {
5869    unsafe {
5870        if ctxt.is_null() {
5871            return ptr::null_mut();
5872        }
5873        let doc: *mut _xmlDoc;
5874        if (*ctxt).wellFormed != 0
5875            || (((*ctxt).recovery != 0 || (*ctxt).html != 0) && pi_ctxt_is_catastrophic(ctxt) == 0)
5876        {
5877            doc = (*ctxt).myDoc;
5878        } else {
5879            if (*ctxt).errNo == crate::abi::types::XML_ERR_OK {
5880                // xmlFatalErr(ctxt, XML_ERR_INTERNAL_ERROR, "unknown error")
5881                (*ctxt).errNo = crate::abi::types::XML_ERR_INTERNAL_ERROR;
5882            }
5883            doc = ptr::null_mut();
5884            if !(*ctxt).myDoc.is_null() {
5885                crate::xml::tree::free_doc((*ctxt).myDoc);
5886            }
5887        }
5888        (*ctxt).myDoc = ptr::null_mut();
5889        doc
5890    }
5891}
5892
5893/// `xmlCtxtSetCharEncConvImpl` — install a custom encoding-conversion impl.
5894#[no_mangle]
5895pub unsafe extern "C" fn xmlCtxtSetCharEncConvImpl(
5896    ctxt: *mut _xmlParserCtxt,
5897    impl_: Option<crate::abi::callbacks::xmlCharEncConvImpl>,
5898    vctxt: *mut c_void,
5899) {
5900    if ctxt.is_null() {
5901        return;
5902    }
5903    unsafe {
5904        (*ctxt).convImpl = impl_;
5905        (*ctxt).convCtxt = vctxt;
5906    }
5907}
5908
5909/// `xmlCtxtSetResourceLoader` — install a custom resource loader.
5910#[no_mangle]
5911pub unsafe extern "C" fn xmlCtxtSetResourceLoader(
5912    ctxt: *mut _xmlParserCtxt,
5913    loader: Option<crate::abi::callbacks::xmlResourceLoader>,
5914    vctxt: *mut c_void,
5915) {
5916    if ctxt.is_null() {
5917        return;
5918    }
5919    unsafe {
5920        (*ctxt).resourceLoader = loader;
5921        (*ctxt).resourceCtxt = vctxt;
5922    }
5923}
5924
5925/// `xmlCtxtPushInput` — push an input onto the stack (upstream parser.c).
5926#[no_mangle]
5927pub unsafe extern "C" fn xmlCtxtPushInput(
5928    ctxt: *mut _xmlParserCtxt,
5929    value: *mut _xmlParserInput,
5930) -> c_int {
5931    unsafe {
5932        if ctxt.is_null() || value.is_null() {
5933            return -1;
5934        }
5935        let max_depth = if (*ctxt).options & crate::abi::types::XML_PARSE_HUGE != 0 {
5936            40
5937        } else {
5938            20
5939        };
5940        if (*ctxt).inputNr >= (*ctxt).inputMax {
5941            let old_max = (*ctxt).inputMax;
5942            let mut new_size = old_max * 2 + 5;
5943            if new_size > max_depth {
5944                new_size = max_depth;
5945            }
5946            if new_size <= old_max {
5947                return -1;
5948            }
5949            let tmp = xmlReallocImpl(
5950                (*ctxt).inputTab as *mut c_void,
5951                (new_size as usize) * size_of::<*mut _xmlParserInput>(),
5952            ) as *mut *mut _xmlParserInput;
5953            if tmp.is_null() {
5954                return -1;
5955            }
5956            (*ctxt).inputTab = tmp;
5957            (*ctxt).inputMax = new_size;
5958        }
5959        *(*ctxt).inputTab.add((*ctxt).inputNr as usize) = value;
5960        (*ctxt).input = value;
5961        (*value).id = (*ctxt).input_id;
5962        (*ctxt).input_id += 1;
5963        let idx = (*ctxt).inputNr;
5964        (*ctxt).inputNr += 1;
5965        idx
5966    }
5967}
5968
5969/// `xmlCtxtPopInput` — pop the top input (returns it; caller owns it).
5970#[no_mangle]
5971pub unsafe extern "C" fn xmlCtxtPopInput(ctxt: *mut _xmlParserCtxt) -> *mut _xmlParserInput {
5972    unsafe {
5973        if ctxt.is_null() || (*ctxt).inputNr <= 0 {
5974            return ptr::null_mut();
5975        }
5976        (*ctxt).inputNr -= 1;
5977        if (*ctxt).inputNr > 0 {
5978            (*ctxt).input = *(*ctxt).inputTab.add(((*ctxt).inputNr - 1) as usize);
5979        } else {
5980            (*ctxt).input = ptr::null_mut();
5981        }
5982        let ret = *(*ctxt).inputTab.add((*ctxt).inputNr as usize);
5983        *(*ctxt).inputTab.add((*ctxt).inputNr as usize) = ptr::null_mut();
5984        ret
5985    }
5986}
5987
5988/// `xmlCtxtValidateDtd` — validate a document against a DTD using the
5989/// context's error handler (upstream valid.c).
5990#[no_mangle]
5991pub unsafe extern "C" fn xmlCtxtValidateDtd(
5992    ctxt: *mut _xmlParserCtxt,
5993    doc: *mut _xmlDoc,
5994    dtd: *mut _xmlDtd,
5995) -> c_int {
5996    if ctxt.is_null() || (*ctxt).html != 0 {
5997        return 0;
5998    }
5999    unsafe {
6000        crate::abi::exports_parser::xmlCtxtReset(ctxt);
6001        crate::xml::validation::validate_dtd(&mut (*ctxt).vctxt, doc, dtd)
6002    }
6003}
6004
6005/// `xmlCtxtValidateDocument` — validate a document using the context's
6006/// error handler (upstream valid.c).
6007#[no_mangle]
6008pub unsafe extern "C" fn xmlCtxtValidateDocument(
6009    ctxt: *mut _xmlParserCtxt,
6010    doc: *mut _xmlDoc,
6011) -> c_int {
6012    if ctxt.is_null() || (*ctxt).html != 0 {
6013        return 0;
6014    }
6015    unsafe {
6016        crate::abi::exports_parser::xmlCtxtReset(ctxt);
6017        crate::xml::validation::validate_document(&mut (*ctxt).vctxt, doc)
6018    }
6019}
6020
6021/// `xmlCtxtParseDtd` — parse a DTD from an input (input is consumed/freed).
6022#[no_mangle]
6023pub unsafe extern "C" fn xmlCtxtParseDtd(
6024    ctxt: *mut _xmlParserCtxt,
6025    input: *mut _xmlParserInput,
6026    public_id: *const xmlChar,
6027    system_id: *const xmlChar,
6028) -> *mut _xmlDtd {
6029    unsafe {
6030        if ctxt.is_null() || input.is_null() {
6031            crate::xml::parser::helpers::free_parser_input(input);
6032            return ptr::null_mut();
6033        }
6034        if xmlCtxtPushInput(ctxt, input) < 0 {
6035            crate::xml::parser::helpers::free_parser_input(input);
6036            return ptr::null_mut();
6037        }
6038        let pub_id = if public_id.is_null() {
6039            c"none".as_ptr() as *const xmlChar
6040        } else {
6041            public_id
6042        };
6043        let sys_id = if system_id.is_null() {
6044            c"none".as_ptr() as *const xmlChar
6045        } else {
6046            system_id
6047        };
6048        (*ctxt).myDoc = crate::xml::tree::new_doc(c"1.0".as_ptr() as *const xmlChar);
6049        if (*ctxt).myDoc.is_null() {
6050            return ptr::null_mut();
6051        }
6052        (*(*ctxt).myDoc).properties = XML_DOC_INTERNAL as c_int;
6053        (*(*ctxt).myDoc).extSubset = crate::xml::tree::new_dtd(
6054            (*ctxt).myDoc,
6055            c"none".as_ptr() as *const xmlChar,
6056            pub_id,
6057            sys_id,
6058        );
6059        if (*(*ctxt).myDoc).extSubset.is_null() {
6060            crate::xml::tree::free_doc((*ctxt).myDoc);
6061            (*ctxt).myDoc = ptr::null_mut();
6062            return ptr::null_mut();
6063        }
6064        pi_parse_external_subset(ctxt, pub_id, sys_id);
6065        let mut ret: *mut _xmlDtd = ptr::null_mut();
6066        if (*ctxt).wellFormed != 0 {
6067            ret = (*(*ctxt).myDoc).extSubset;
6068            (*(*ctxt).myDoc).extSubset = ptr::null_mut();
6069            if !ret.is_null() {
6070                (*ret).doc = ptr::null_mut();
6071                let mut tmp = (*ret).children;
6072                while !tmp.is_null() {
6073                    (*tmp).doc = ptr::null_mut();
6074                    tmp = (*tmp).next;
6075                }
6076            }
6077        }
6078        if !(*ctxt).myDoc.is_null() {
6079            crate::xml::tree::free_doc((*ctxt).myDoc);
6080        }
6081        (*ctxt).myDoc = ptr::null_mut();
6082        ret
6083    }
6084}
6085
6086/// `xmlCtxtParseContent` — parse a well-balanced content sequence into a
6087/// node list in the context of `node` (upstream parser.c; the input is
6088/// consumed and freed).
6089#[no_mangle]
6090pub unsafe extern "C" fn xmlCtxtParseContent(
6091    ctxt: *mut _xmlParserCtxt,
6092    input: *mut _xmlParserInput,
6093    node: *mut _xmlNode,
6094    has_text_decl: c_int,
6095) -> *mut _xmlNode {
6096    unsafe {
6097        if ctxt.is_null() || input.is_null() || node.is_null() {
6098            crate::xml::parser::helpers::free_parser_input(input);
6099            return ptr::null_mut();
6100        }
6101        let doc = (*node).doc;
6102        if doc.is_null() {
6103            crate::xml::parser::helpers::free_parser_input(input);
6104            return ptr::null_mut();
6105        }
6106        let mut target = node;
6107        match (*node).type_ {
6108            t if t == xmlElementType::XML_ELEMENT_NODE as c_int
6109                || t == xmlElementType::XML_DOCUMENT_NODE as c_int
6110                || t == xmlElementType::XML_HTML_DOCUMENT_NODE as c_int => {}
6111            t if t == xmlElementType::XML_ATTRIBUTE_NODE as c_int
6112                || t == xmlElementType::XML_TEXT_NODE as c_int
6113                || t == xmlElementType::XML_CDATA_SECTION_NODE as c_int
6114                || t == xmlElementType::XML_ENTITY_REF_NODE as c_int
6115                || t == xmlElementType::XML_PI_NODE as c_int
6116                || t == xmlElementType::XML_COMMENT_NODE as c_int =>
6117            {
6118                let mut cur = (*node).parent;
6119                while !cur.is_null() {
6120                    let ct = (*cur).type_;
6121                    if ct == xmlElementType::XML_ELEMENT_NODE as c_int
6122                        || ct == xmlElementType::XML_DOCUMENT_NODE as c_int
6123                        || ct == xmlElementType::XML_HTML_DOCUMENT_NODE as c_int
6124                    {
6125                        target = cur;
6126                        break;
6127                    }
6128                    cur = (*cur).parent;
6129                }
6130            }
6131            _ => {
6132                crate::xml::parser::helpers::free_parser_input(input);
6133                return ptr::null_mut();
6134            }
6135        }
6136
6137        crate::abi::exports_parser::xmlCtxtReset(ctxt);
6138        let old_dict = (*ctxt).dict;
6139        let old_options = (*ctxt).options;
6140        let old_dict_names = (*ctxt).dictNames;
6141        let old_load_subset = (*ctxt).loadsubset;
6142        if !(*doc).dict.is_null() {
6143            (*ctxt).dict = (*doc).dict;
6144        } else {
6145            (*ctxt).options |= crate::abi::types::XML_PARSE_NODICT;
6146            (*ctxt).dictNames = 0;
6147        }
6148        (*ctxt).loadsubset |= crate::abi::constants::XML_SKIP_IDS;
6149        (*ctxt).options |= crate::abi::types::XML_PARSE_SKIP_IDS;
6150        (*ctxt).myDoc = doc;
6151
6152        let list = pi_parse_content_node_list(ctxt, input, has_text_decl);
6153
6154        (*ctxt).dict = old_dict;
6155        (*ctxt).options = old_options;
6156        (*ctxt).dictNames = old_dict_names;
6157        (*ctxt).loadsubset = old_load_subset;
6158        (*ctxt).myDoc = ptr::null_mut();
6159        (*ctxt).node = ptr::null_mut();
6160        crate::xml::parser::helpers::free_parser_input(input);
6161        let _ = target;
6162        list
6163    }
6164}
6165
6166// ═══════════════════════════════════════════════════════════════════════════════
6167// xmlNewInputFrom* / xmlInputSetEncodingHandler (parser.h 2.14+ family)
6168// ═══════════════════════════════════════════════════════════════════════════════
6169//
6170// R-000165 closure: input-stream constructors ported from
6171// parserInternals.c. The returned input OWNS its buffer and filename and
6172// must be freed with xmlFreeInputStream (free_parser_input frees the
6173// buffer via the xmlIO layer and the owned filename).
6174
6175/// Set the owned filename on a freshly built input (url may be NULL).
6176unsafe fn pi_set_input_filename(input: *mut _xmlParserInput, url: *const c_char) {
6177    if !url.is_null() {
6178        (*input).filename = crate::xml::string::xml_strdup(url as *const crate::abi::types::xmlChar)
6179            as *const c_char;
6180    }
6181}
6182
6183/// `xmlNewInputFromMemory` — new input reading from a memory area.
6184#[no_mangle]
6185pub unsafe extern "C" fn xmlNewInputFromMemory(
6186    url: *const c_char,
6187    mem: *const c_void,
6188    size: usize,
6189    _flags: c_int,
6190) -> *mut _xmlParserInput {
6191    unsafe {
6192        if mem.is_null() {
6193            return ptr::null_mut();
6194        }
6195        let buf = crate::xml::io::input_buffer_create_mem(
6196            mem as *const c_char,
6197            size as c_int,
6198            crate::abi::types::xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int,
6199        );
6200        if buf.is_null() {
6201            return ptr::null_mut();
6202        }
6203        let input = crate::abi::exports_parser::parser_input_from_buf_pub(buf);
6204        if input.is_null() {
6205            return ptr::null_mut();
6206        }
6207        pi_set_input_filename(input, url);
6208        input
6209    }
6210}
6211
6212/// `xmlNewInputFromString` — new input reading from a zero-terminated string.
6213#[no_mangle]
6214pub unsafe extern "C" fn xmlNewInputFromString(
6215    url: *const c_char,
6216    str: *const c_char,
6217    _flags: c_int,
6218) -> *mut _xmlParserInput {
6219    unsafe {
6220        if str.is_null() {
6221            return ptr::null_mut();
6222        }
6223        let len = libc::strlen(str);
6224        let buf = crate::xml::io::input_buffer_create_mem(
6225            str,
6226            len as c_int,
6227            crate::abi::types::xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int,
6228        );
6229        if buf.is_null() {
6230            return ptr::null_mut();
6231        }
6232        let input = crate::abi::exports_parser::parser_input_from_buf_pub(buf);
6233        if input.is_null() {
6234            return ptr::null_mut();
6235        }
6236        pi_set_input_filename(input, url);
6237        input
6238    }
6239}
6240
6241/// `xmlNewInputFromFd` — new input reading from a file descriptor
6242/// (the fd is drained at creation; upstream closes it with the input — the
6243/// candidate's read-at-creation pattern closes it after reading).
6244#[no_mangle]
6245pub unsafe extern "C" fn xmlNewInputFromFd(
6246    url: *const c_char,
6247    fd: c_int,
6248    _flags: c_int,
6249) -> *mut _xmlParserInput {
6250    unsafe {
6251        if fd < 0 {
6252            return ptr::null_mut();
6253        }
6254        let mut data: Vec<u8> = Vec::new();
6255        let mut tmp = [0u8; 4096];
6256        loop {
6257            let n = libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len());
6258            if n <= 0 {
6259                break;
6260            }
6261            data.extend_from_slice(&tmp[..n as usize]);
6262        }
6263        let buf = crate::xml::io::input_buffer_create_mem(
6264            data.as_ptr() as *const c_char,
6265            data.len() as c_int,
6266            crate::abi::types::xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int,
6267        );
6268        if buf.is_null() {
6269            return ptr::null_mut();
6270        }
6271        let input = crate::abi::exports_parser::parser_input_from_buf_pub(buf);
6272        if input.is_null() {
6273            return ptr::null_mut();
6274        }
6275        pi_set_input_filename(input, url);
6276        input
6277    }
6278}
6279
6280/// `xmlNewInputFromIO` — new input reading from I/O callbacks.
6281#[no_mangle]
6282pub unsafe extern "C" fn xmlNewInputFromIO(
6283    url: *const c_char,
6284    io_read: Option<crate::abi::callbacks::xmlInputReadCallback>,
6285    io_close: Option<crate::abi::callbacks::xmlInputCloseCallback>,
6286    io_ctxt: *mut c_void,
6287    _flags: c_int,
6288) -> *mut _xmlParserInput {
6289    unsafe {
6290        let Some(read) = io_read else {
6291            return ptr::null_mut();
6292        };
6293        // Drain the callback (candidate's read-at-creation pattern).
6294        let mut data: Vec<u8> = Vec::new();
6295        let mut tmp = [0u8; 4096];
6296        loop {
6297            let n = read(io_ctxt, tmp.as_mut_ptr() as *mut c_char, tmp.len() as c_int);
6298            if n <= 0 {
6299                break;
6300            }
6301            data.extend_from_slice(&tmp[..n as usize]);
6302        }
6303        let buf = crate::xml::io::input_buffer_create_mem(
6304            data.as_ptr() as *const c_char,
6305            data.len() as c_int,
6306            crate::abi::types::xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int,
6307        );
6308        if buf.is_null() {
6309            if let Some(close) = io_close {
6310                close(io_ctxt);
6311            }
6312            return ptr::null_mut();
6313        }
6314        // Honor the close callback on buffer free (upstream contract).
6315        (*buf).closecallback = io_close;
6316        let input = crate::abi::exports_parser::parser_input_from_buf_pub(buf);
6317        if input.is_null() {
6318            return ptr::null_mut();
6319        }
6320        pi_set_input_filename(input, url);
6321        input
6322    }
6323}
6324
6325/// `xmlNewInputFromUrl` — new input from a file/URL (2.14+; error code +
6326/// out-param).
6327#[no_mangle]
6328pub unsafe extern "C" fn xmlNewInputFromUrl(
6329    url: *const c_char,
6330    _flags: c_int,
6331    out: *mut *mut _xmlParserInput,
6332) -> c_int {
6333    unsafe {
6334        if out.is_null() {
6335            return crate::abi::types::XML_ERR_ARGUMENT;
6336        }
6337        *out = ptr::null_mut();
6338        if url.is_null() {
6339            return crate::abi::types::XML_ERR_ARGUMENT;
6340        }
6341        let buf = crate::xml::io::input_buffer_create_file(
6342            url,
6343            crate::abi::types::xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int,
6344        );
6345        if buf.is_null() {
6346            return crate::abi::types::XML_IO_ENOENT;
6347        }
6348        let input = crate::abi::exports_parser::parser_input_from_buf_pub(buf);
6349        if input.is_null() {
6350            return crate::abi::types::XML_ERR_NO_MEMORY;
6351        }
6352        pi_set_input_filename(input, url);
6353        *out = input;
6354        crate::abi::types::XML_ERR_OK
6355    }
6356}
6357
6358/// `xmlInputSetEncodingHandler` — attach an encoding handler to an input
6359/// (upstream parserInternals.c; handler closed on error / UTF-8 pass).
6360#[no_mangle]
6361pub unsafe extern "C" fn xmlInputSetEncodingHandler(
6362    input: *mut _xmlParserInput,
6363    handler: *mut c_void,
6364) -> c_int {
6365    unsafe {
6366        if input.is_null() || (*input).buf.is_null() {
6367            if !handler.is_null() {
6368                crate::abi::exports_xml2::xmlCharEncCloseFunc(handler);
6369            }
6370            return crate::abi::types::XML_ERR_ARGUMENT;
6371        }
6372        let in_ = (*input).buf;
6373        let mut h = handler;
6374        // UTF-8 requires no encoding handler.
6375        if !h.is_null() {
6376            let name = (*(h as *mut crate::abi::structs::_xmlCharEncodingHandler)).name;
6377            if !name.is_null()
6378                && crate::abi::exports_xml2::xmlStrcasecmp(
6379                    name as *const crate::abi::types::xmlChar,
6380                    c"UTF-8".as_ptr() as *const crate::abi::types::xmlChar,
6381                ) == 0
6382            {
6383                crate::abi::exports_xml2::xmlCharEncCloseFunc(h);
6384                h = ptr::null_mut();
6385            }
6386        }
6387        if std::ptr::eq((*in_).encoder, h) {
6388            return crate::abi::types::XML_ERR_OK;
6389        }
6390        if !(*in_).encoder.is_null() {
6391            crate::abi::exports_xml2::xmlCharEncCloseFunc((*in_).encoder);
6392            (*in_).encoder = h;
6393            return crate::abi::types::XML_ERR_OK;
6394        }
6395        (*in_).encoder = h;
6396        crate::abi::types::XML_ERR_OK
6397    }
6398}