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