Skip to main content

libxml_rs/abi/
exports_parserint.rs

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