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