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