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