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