Skip to main content

libxml_rs/abi/
exports_parser.rs

1//! exports_parser — C ABI exports for the XML parser family (§11.1-I).
2//!
3//! Implements the parser/parserInternals/xmlIO/encoding/tree export surface:
4//! parser-context creation and lifecycle, the `xmlCtxtRead*` family, parser
5//! input buffers and streams, encoding switches, the deprecated node-info
6//! sequence, global I/O callback registration, external-entity loaders, the
7//! `xmlFile*` I/O callbacks, low-level character scanning helpers and the
8//! SAX/DTD parse front-ends.
9//!
10//! Where an internal engine entry point exists (e.g. `crate::xml::parser::helpers`),
11//! the export wraps it; otherwise the function is ported from upstream
12//! `parser.c` / `parserInternals.c` / `xmlIO.c` / `error.c` / `encoding.c`
13//! (see `archaeology/libxml2-git`).
14
15#![allow(missing_docs)]
16#![allow(non_snake_case)]
17#![allow(non_camel_case_types)]
18#![allow(non_upper_case_globals)]
19
20use core::ffi::CStr;
21use core::ptr;
22use std::os::raw::{c_char, c_int, c_long, c_uchar, c_uint, c_ulong, c_void};
23
24use parking_lot::Mutex;
25
26use crate::abi::allocator::{xmlFree, xmlMalloc, xmlMallocZero, xmlMemStrdup, xmlRealloc};
27use crate::abi::callbacks::{
28    xmlGenericErrorFunc, xmlInputCloseCallback, xmlInputReadCallback, xmlOutputCloseCallback,
29    xmlOutputWriteCallback, xmlStructuredErrorFunc,
30};
31use crate::abi::structs::*;
32use crate::abi::types::*;
33use crate::xml::parser::helpers;
34use crate::xml::parser::input::InputBuffer;
35use crate::xml::{dtd, encoding, entities, errors, globals, io, string, tree};
36
37// ═══════════════════════════════════════════════════════════════════════════════
38// Local ABI types (upstream xmlIO.h / parser.h, not present in callbacks.rs)
39// ═══════════════════════════════════════════════════════════════════════════════
40
41/// `xmlInputMatchCallback` — decide whether a filename is handled by the
42/// registered input callback pair.
43type xmlInputMatchCallback = unsafe extern "C" fn(filename: *const c_char) -> c_int;
44
45/// `xmlInputOpenCallback` — open a resource and return an I/O context.
46type xmlInputOpenCallback = unsafe extern "C" fn(filename: *const c_char) -> *mut c_void;
47
48/// `xmlOutputMatchCallback` — decide whether a filename is handled by the
49/// registered output callback pair.
50type xmlOutputMatchCallback = unsafe extern "C" fn(filename: *const c_char) -> c_int;
51
52/// `xmlOutputOpenCallback` — open a resource for writing and return a context.
53type xmlOutputOpenCallback = unsafe extern "C" fn(filename: *const c_char) -> *mut c_void;
54
55/// `xmlExternalEntityLoader` — resolve an external entity to a parser input.
56type xmlExternalEntityLoader = unsafe extern "C" fn(
57    URL: *const c_char,
58    ID: *const c_char,
59    ctxt: *mut _xmlParserCtxt,
60) -> *mut _xmlParserInput;
61
62#[derive(Clone, Copy)]
63struct InputCallbackEntry {
64    matchcb: Option<xmlInputMatchCallback>,
65    opencb: Option<xmlInputOpenCallback>,
66    readcb: Option<xmlInputReadCallback>,
67    closecb: Option<xmlInputCloseCallback>,
68}
69
70#[derive(Clone, Copy)]
71struct OutputCallbackEntry {
72    matchcb: Option<xmlOutputMatchCallback>,
73    opencb: Option<xmlOutputOpenCallback>,
74    writecb: Option<xmlOutputWriteCallback>,
75    closecb: Option<xmlOutputCloseCallback>,
76}
77
78static INPUT_CALLBACKS: Mutex<Vec<InputCallbackEntry>> = Mutex::new(Vec::new());
79static OUTPUT_CALLBACKS: Mutex<Vec<OutputCallbackEntry>> = Mutex::new(Vec::new());
80
81static EXTERNAL_ENTITY_LOADER: Mutex<Option<xmlExternalEntityLoader>> =
82    Mutex::new(Some(default_external_entity_loader));
83
84// Deprecated legacy function codes not present in types.rs (upstream xmlerror.h).
85const XML_ERR_USER_STOP: c_int = 111;
86const XML_ERR_RESOURCE_LIMIT: c_int = 114;
87
88// XML_SCAN_* flags (upstream include/private/parser.h).
89const XML_SCAN_NC: c_int = 1;
90const XML_SCAN_NMTOKEN: c_int = 2;
91const XML_SCAN_OLD10: c_int = 4;
92
93// xmlParserLoadSubset bits (upstream parser.h).
94const XML_DETECT_IDS: c_int = 1 << 0;
95const XML_COMPLETE_ATTRS: c_int = 1 << 1;
96
97/// Keep enough input around to show errors in context (parserInternals.c).
98const LINE_LEN: usize = 80;
99
100/// Minimal amount of data the parser expects in the buffer (parserInternals.c).
101const INPUT_CHUNK: usize = 100;
102
103const XML_INVALID_CHAR: c_int = -1;
104
105// ═══════════════════════════════════════════════════════════════════════════════
106// Internal helpers
107// ═══════════════════════════════════════════════════════════════════════════════
108
109/// Shared context initialisation: zeroes `ctxt`, installs the SAX handler and
110/// sets the initial parser state (upstream `xmlInitSAXParserCtxt`).
111///
112/// # Safety
113///
114/// `ctxt` must be a valid, writable, freshly allocated parser context.
115unsafe fn init_sax_parser_ctxt(
116    ctxt: *mut _xmlParserCtxt,
117    sax: *const _xmlSAXHandler,
118    userData: *mut c_void,
119) -> c_int {
120    unsafe {
121        ptr::write_bytes(ctxt as *mut u8, 0, core::mem::size_of::<_xmlParserCtxt>());
122
123        let c = &mut *ctxt;
124
125        // SAX handler.
126        if c.sax.is_null() {
127            let new_sax =
128                xmlMallocZero(core::mem::size_of::<_xmlSAXHandler>()) as *mut _xmlSAXHandler;
129            if new_sax.is_null() {
130                return -1;
131            }
132            c.sax = new_sax;
133        }
134        if sax.is_null() {
135            crate::xml::sax::xmlSAX2InitDefaultSAXHandler(c.sax);
136            c.userData = ctxt as *mut c_void;
137        } else if (*sax).initialized == XML_SAX2_MAGIC as c_uint {
138            // Full SAX2 handler copy.
139            ptr::copy_nonoverlapping(sax, c.sax, 1);
140            c.userData = if userData.is_null() {
141                ctxt as *mut c_void
142            } else {
143                userData
144            };
145        } else {
146            // SAX1 handler: only the V1 prefix is meaningful.
147            ptr::write_bytes(c.sax as *mut u8, 0, core::mem::size_of::<_xmlSAXHandler>());
148            ptr::copy_nonoverlapping(
149                sax as *const u8,
150                c.sax as *mut u8,
151                core::mem::size_of::<_xmlSAXHandlerV1>(),
152            );
153            c.userData = if userData.is_null() {
154                ctxt as *mut c_void
155            } else {
156                userData
157            };
158        }
159
160        c.wellFormed = 1;
161        c.standalone = -1;
162        c.errNo = XML_ERR_OK;
163        c.valid = 1;
164        c.nsWellFormed = 1;
165        c.instate = xmlParserInputState::XML_PARSER_START as c_int;
166        c.keepBlanks = globals::get_keep_blanks_default();
167        c.replaceEntities = globals::get_substitute_entities_default();
168        c.linenumbers = 1;
169        c.charset = xmlCharEncoding::XML_CHAR_ENCODING_UTF8 as c_int;
170        c.pedantic = globals::get_pedantic_parser_default();
171        c.loadsubset = globals::get_load_ext_dtd_default();
172        c.docdict = 1;
173        c.options = 0;
174
175        c.vctxt.userData = ctxt as *mut c_void;
176        c.vctxt.valid = 1;
177    }
178    0
179}
180
181/// Mirror `options` into the parser context's historical struct members
182/// (upstream `xmlCtxtSetOptionsInternal`).
183///
184/// # Safety
185///
186/// `ctxt` must be a valid, writable parser context.
187unsafe fn apply_options(ctxt: *mut _xmlParserCtxt, options: c_int) {
188    unsafe {
189        let c = &mut *ctxt;
190        c.options = options;
191        c.recovery = (options & XML_PARSE_RECOVER != 0) as c_int;
192        c.replaceEntities = (options & XML_PARSE_NOENT != 0) as c_int;
193        c.loadsubset = ((options & XML_PARSE_DTDLOAD != 0) as c_int)
194            | if options & XML_PARSE_DTDATTR != 0 {
195                XML_COMPLETE_ATTRS
196            } else {
197                0
198            };
199        c.validate = (options & XML_PARSE_DTDVALID != 0) as c_int;
200        c.pedantic = (options & XML_PARSE_PEDANTIC != 0) as c_int;
201        c.keepBlanks = if options & XML_PARSE_NOBLANKS != 0 { 0 } else { 1 };
202        c.dictNames = if options & XML_PARSE_NODICT != 0 { 0 } else { 1 };
203    }
204}
205
206/// Find the registered encoding handler for an `xmlCharEncoding` value, or NULL.
207unsafe fn encoding_handler_for(enc: c_int) -> *mut _xmlCharEncodingHandler {
208    let e: xmlCharEncoding = unsafe { core::mem::transmute(enc) };
209    match encoding::encoding_name(e) {
210        Some(name) => {
211            let mut nul = name.to_vec();
212            nul.push(0);
213            encoding::find_encoding_handler(nul.as_ptr() as *const xmlChar)
214        }
215        None => ptr::null_mut(),
216    }
217}
218
219/// Build a `_xmlParserInput` that references the data owned by `buf` (an input
220/// buffer previously created by the xmlIO layer). The buffer keeps the data
221/// alive; the returned input must be freed with `helpers::free_parser_input`.
222///
223/// # Safety
224///
225/// `buf` must be a valid input buffer or NULL, and must outlive the returned
226/// input.
227unsafe fn parser_input_from_buf(buf: *mut _xmlParserInputBuffer) -> *mut _xmlParserInput {
228    let input = unsafe { xmlMallocZero(core::mem::size_of::<_xmlParserInput>()) }
229        as *mut _xmlParserInput;
230    if input.is_null() {
231        return ptr::null_mut();
232    }
233    unsafe {
234        (*input).buf = buf;
235        (*input).line = 1;
236        (*input).col = 1;
237        if !buf.is_null() {
238            let b = &*buf;
239            if !b.buffer.is_null() {
240                let xbuf = &*(b.buffer as *mut _xmlBuffer);
241                if !xbuf.content.is_null() {
242                    (*input).base = xbuf.content;
243                    (*input).cur = xbuf.content;
244                    (*input).end = xbuf.content.add(xbuf.use_ as usize);
245                    (*input).length = xbuf.use_ as c_int;
246                }
247            }
248        }
249    }
250    input
251}
252
253/// Materialise an `InputBuffer` (owned copy) from a raw `_xmlParserInput`,
254/// so the data survives the caller's input lifetime.
255///
256/// # Safety
257///
258/// `input` must be a valid pointer to a `_xmlParserInput`.
259unsafe fn input_buffer_from_parser_input(input: *mut _xmlParserInput) -> InputBuffer {
260    unsafe {
261        let pi = &*input;
262        if !pi.buf.is_null() {
263            let b = &*pi.buf;
264            if let Some(read) = b.readcallback {
265                return helpers::input_from_io(Some(read), b.closecallback, b.context);
266            }
267        }
268        if !pi.base.is_null() && !pi.end.is_null() && pi.end >= pi.base {
269            let len = (pi.end as usize).saturating_sub(pi.base as usize);
270            let slice = core::slice::from_raw_parts(pi.base, len);
271            return InputBuffer::from_memory(slice, None);
272        }
273        InputBuffer::from_memory(&[], None)
274    }
275}
276
277/// Core of `xmlCtxtRead*`: reset the context, wire an input buffer, parse,
278/// and return the resulting document (freed on hard error unless recovery).
279///
280/// # Safety
281///
282/// `ctxt` must be a valid parser context; `input` is consumed.
283unsafe fn ctxt_read_doc(
284    ctxt: *mut _xmlParserCtxt,
285    input: InputBuffer,
286    url: *const c_char,
287    options: c_int,
288) -> *mut _xmlDoc {
289    unsafe {
290        xmlCtxtReset(ctxt);
291        apply_options(ctxt, options);
292        helpers::setup_parser_input(ctxt, input);
293        if helpers::parse_document(ctxt) != 0 {
294            let doc = (*ctxt).myDoc;
295            (*ctxt).myDoc = ptr::null_mut();
296            if options & XML_PARSE_RECOVER != 0 {
297                return doc;
298            }
299            if !doc.is_null() {
300                tree::free_doc(doc);
301            }
302            return ptr::null_mut();
303        }
304        let doc = (*ctxt).myDoc;
305        if !doc.is_null() && !url.is_null() {
306            (*doc).URL = string::xml_strdup(url as *const xmlChar);
307        }
308        doc
309    }
310}
311
312/// Parse DTD declaration text using the internal engine by wrapping it in a
313/// synthetic document (`<!DOCTYPE none [ ... ]><none/>`) when the text is a
314/// bare DTD subset, or parsing it directly when it is already a document.
315///
316/// Returns a detached DTD (never owned by a document), or NULL.
317///
318/// # Safety
319///
320/// `ctxt` must be a valid parser context; `data` must be readable for `len`
321/// bytes.
322unsafe fn parse_dtd_text(
323    ctxt: *mut _xmlParserCtxt,
324    data: &[u8],
325    public_id: *const xmlChar,
326    system_id: *const xmlChar,
327) -> *mut _xmlDtd {
328    unsafe {
329        // If the content is already a full document (contains a DOCTYPE),
330        // parse it directly; otherwise wrap the declarations.
331        let has_doctype = data
332            .windows(9)
333            .any(|w| w.eq_ignore_ascii_case(b"<!DOCTYPE"));
334        let mut wrapped: Vec<u8>;
335        let parse_data: &[u8] = if has_doctype {
336            data
337        } else {
338            wrapped = Vec::with_capacity(data.len() + 32);
339            wrapped.extend_from_slice(b"<!DOCTYPE none [");
340            wrapped.extend_from_slice(data);
341            wrapped.extend_from_slice(b"]><none/>");
342            &wrapped
343        };
344
345        let input = InputBuffer::from_memory(parse_data, None);
346        helpers::setup_parser_input(ctxt, input);
347        let rc = helpers::parse_document(ctxt);
348        let doc = (*ctxt).myDoc;
349        (*ctxt).myDoc = ptr::null_mut();
350
351        if rc == 0 && !doc.is_null() && !(*doc).intSubset.is_null() {
352            let dtd = (*doc).intSubset;
353            (*doc).intSubset = ptr::null_mut();
354            (*dtd).parent = ptr::null_mut();
355            (*dtd).doc = ptr::null_mut();
356            if !public_id.is_null() {
357                (*dtd).ExternalID = string::xml_strdup(public_id);
358            }
359            if !system_id.is_null() {
360                (*dtd).SystemID = string::xml_strdup(system_id);
361            }
362            tree::free_doc(doc);
363            return dtd;
364        }
365
366        if !doc.is_null() {
367            tree::free_doc(doc);
368        }
369        // Fallback: an empty DTD carrying the identifiers.
370        let dtd = dtd::new_dtd(ptr::null_mut(), b"none\0".as_ptr(), public_id, system_id);
371        dtd
372    }
373}
374
375// ═══════════════════════════════════════════════════════════════════════════════
376// Context creation / lifecycle
377// ═══════════════════════════════════════════════════════════════════════════════
378
379/// Create a new parser context with a default SAX2 handler.
380///
381/// # UPSTREAM-PARITY
382///
383/// ```c
384/// xmlParserCtxtPtr xmlNewParserCtxt(void);
385/// ```
386#[no_mangle]
387pub unsafe extern "C" fn xmlNewParserCtxt() -> *mut _xmlParserCtxt {
388    unsafe {
389        globals::init_parser();
390        let ctxt = xmlMallocZero(core::mem::size_of::<_xmlParserCtxt>()) as *mut _xmlParserCtxt;
391        if ctxt.is_null() {
392            return ptr::null_mut();
393        }
394        if init_sax_parser_ctxt(ctxt, ptr::null(), ptr::null_mut()) < 0 {
395            helpers::free_parser_ctxt(ctxt);
396            return ptr::null_mut();
397        }
398        ctxt
399    }
400}
401
402/// Create a new parser context using the given SAX handler (or the default
403/// SAX2 handler when `sax` is NULL).
404///
405/// # UPSTREAM-PARITY
406///
407/// ```c
408/// xmlParserCtxtPtr xmlNewSAXParserCtxt(const xmlSAXHandler *sax, void *userData);
409/// ```
410#[no_mangle]
411pub unsafe extern "C" fn xmlNewSAXParserCtxt(
412    sax: *const _xmlSAXHandler,
413    userData: *mut c_void,
414) -> *mut _xmlParserCtxt {
415    unsafe {
416        globals::init_parser();
417        let ctxt = xmlMallocZero(core::mem::size_of::<_xmlParserCtxt>()) as *mut _xmlParserCtxt;
418        if ctxt.is_null() {
419            return ptr::null_mut();
420        }
421        if init_sax_parser_ctxt(ctxt, sax, userData) < 0 {
422            helpers::free_parser_ctxt(ctxt);
423            return ptr::null_mut();
424        }
425        ctxt
426    }
427}
428
429/// Initialise a parser context (legacy API): zeroes the context, installs a
430/// default SAX2 handler and sets the initial parser state.
431///
432/// # UPSTREAM-PARITY
433///
434/// ```c
435/// int xmlInitParserCtxt(xmlParserCtxtPtr ctxt);
436/// ```
437#[no_mangle]
438pub unsafe extern "C" fn xmlInitParserCtxt(ctxt: *mut _xmlParserCtxt) -> c_int {
439    unsafe { init_sax_parser_ctxt(ctxt, ptr::null(), ptr::null_mut()) }
440}
441
442/// Clear (reset) a parser context.
443///
444/// # UPSTREAM-PARITY
445///
446/// ```c
447/// void xmlClearParserCtxt(xmlParserCtxtPtr ctxt);
448/// ```
449#[no_mangle]
450pub unsafe extern "C" fn xmlClearParserCtxt(ctxt: *mut _xmlParserCtxt) {
451    unsafe { xmlCtxtReset(ctxt) }
452}
453
454/// Reset a parser context: drop the input stack, node/name stacks, strings,
455/// document and error state so the context can be reused.
456///
457/// # UPSTREAM-PARITY
458///
459/// ```c
460/// void xmlCtxtReset(xmlParserCtxtPtr ctxt);
461/// ```
462#[no_mangle]
463pub unsafe extern "C" fn xmlCtxtReset(ctxt: *mut _xmlParserCtxt) {
464    if ctxt.is_null() {
465        return;
466    }
467    unsafe {
468        let c = &mut *ctxt;
469
470        // Free all inputs on the stack.
471        let input_nr = c.inputNr;
472        let input_tab = c.inputTab;
473        if !input_tab.is_null() {
474            for i in 0..input_nr {
475                let input = *input_tab.add(i as usize);
476                if !input.is_null() {
477                    helpers::free_parser_input(input);
478                }
479            }
480            xmlFree(input_tab as *mut c_void);
481        }
482        c.inputTab = ptr::null_mut();
483        c.inputMax = 0;
484        c.inputNr = 0;
485        c.input = ptr::null_mut();
486
487        // Free the stored InputBuffer (leaked in setup_parser_input).
488        if !c._private.is_null() {
489            let _ = Box::from_raw(c._private as *mut InputBuffer);
490            c._private = ptr::null_mut();
491        }
492
493        // Node stack (array only; nodes are owned by the doc).
494        if !c.nodeTab.is_null() {
495            xmlFree(c.nodeTab as *mut c_void);
496        }
497        c.nodeTab = ptr::null_mut();
498        c.nodeMax = 0;
499        c.nodeNr = 0;
500        c.node = ptr::null_mut();
501
502        // Name stack.
503        if !c.nameTab.is_null() {
504            xmlFree(c.nameTab as *mut c_void);
505        }
506        c.nameTab = ptr::null_mut();
507        c.nameMax = 0;
508        c.nameNr = 0;
509        c.name = ptr::null();
510
511        // Space stack: keep the allocation, reset the counter.
512        c.spaceNr = 0;
513        c.space = ptr::null_mut();
514
515        // Namespaces.
516        c.nsNr = 0;
517
518        // Strings owned by the context.
519        if !c.version.is_null() {
520            xmlFree(c.version as *mut c_void);
521            c.version = ptr::null_mut();
522        }
523        if !c.encoding.is_null() {
524            xmlFree(c.encoding as *mut c_void);
525            c.encoding = ptr::null_mut();
526        }
527        if !c.extSubURI.is_null() {
528            xmlFree(c.extSubURI as *mut c_void);
529            c.extSubURI = ptr::null_mut();
530        }
531        if !c.extSubSystem.is_null() {
532            xmlFree(c.extSubSystem as *mut c_void);
533            c.extSubSystem = ptr::null_mut();
534        }
535        if !c.directory.is_null() {
536            xmlFree(c.directory as *mut c_void);
537            c.directory = ptr::null_mut();
538        }
539
540        // Document: the context owns it until reset/free.
541        if !c.myDoc.is_null() {
542            tree::free_doc(c.myDoc);
543        }
544        c.myDoc = ptr::null_mut();
545
546        // Parser state.
547        c.standalone = -1;
548        c.hasExternalSubset = 0;
549        c.hasPErefs = 0;
550        c.instate = xmlParserInputState::XML_PARSER_START as c_int;
551        c.wellFormed = 1;
552        c.nsWellFormed = 1;
553        c.disableSAX = 0;
554        c.valid = 1;
555        c.record_info = 0;
556        c.checkIndex = 0;
557        c.inSubset = 0;
558        c.errNo = XML_ERR_OK;
559        c.depth = 0;
560        c.nbentities = 0;
561        c.sizeentities = 0;
562        c.nbErrors = 0;
563        c.nbWarnings = 0;
564
565        xmlInitNodeInfoSeq(&mut c.node_seq);
566
567        if c.lastError.code != XML_ERR_OK {
568            errors::reset_error(&mut c.lastError);
569        }
570    }
571}
572
573/// Reset a push-parser context and set up a fresh input chunk.
574///
575/// # UPSTREAM-PARITY
576///
577/// ```c
578/// int xmlCtxtResetPush(xmlParserCtxtPtr ctxt, const char *chunk, int size,
579///                      const char *filename, const char *encoding);
580/// ```
581#[no_mangle]
582pub unsafe extern "C" fn xmlCtxtResetPush(
583    ctxt: *mut _xmlParserCtxt,
584    chunk: *const c_char,
585    size: c_int,
586    filename: *const c_char,
587    encoding: *const c_char,
588) -> c_int {
589    if ctxt.is_null() {
590        return 1;
591    }
592    unsafe {
593        xmlCtxtReset(ctxt);
594
595        let slice = if size > 0 && !chunk.is_null() {
596            core::slice::from_raw_parts(chunk as *const u8, size as usize)
597        } else {
598            &[]
599        };
600        let uri = if filename.is_null() {
601            None
602        } else {
603            CStr::from_ptr(filename).to_str().ok()
604        };
605        let input = InputBuffer::from_memory(slice, uri);
606        helpers::setup_parser_input(ctxt, input);
607
608        if !encoding.is_null() {
609            let handler = encoding::find_encoding_handler(encoding as *const xmlChar);
610            if !handler.is_null() {
611                xmlSwitchToEncoding(ctxt, handler);
612            }
613        }
614    }
615    0
616}
617
618/// Apply a full set of parser options, clearing options not present.
619///
620/// # UPSTREAM-PARITY
621///
622/// ```c
623/// int xmlCtxtSetOptions(xmlParserCtxtPtr ctxt, int options);
624/// ```
625#[no_mangle]
626pub unsafe extern "C" fn xmlCtxtSetOptions(ctxt: *mut _xmlParserCtxt, options: c_int) -> c_int {
627    if ctxt.is_null() {
628        return -1;
629    }
630    const ALL_MASK: c_int = XML_PARSE_RECOVER
631        | XML_PARSE_NOENT
632        | XML_PARSE_DTDLOAD
633        | XML_PARSE_DTDATTR
634        | XML_PARSE_DTDVALID
635        | XML_PARSE_NOERROR
636        | XML_PARSE_NOWARNING
637        | XML_PARSE_PEDANTIC
638        | XML_PARSE_NOBLANKS
639        | XML_PARSE_SAX1
640        | XML_PARSE_NONET
641        | XML_PARSE_NODICT
642        | XML_PARSE_NSCLEAN
643        | XML_PARSE_NOCDATA
644        | XML_PARSE_COMPACT
645        | XML_PARSE_OLD10
646        | XML_PARSE_HUGE
647        | XML_PARSE_OLDSAX
648        | XML_PARSE_IGNORE_ENC
649        | XML_PARSE_BIG_LINES;
650
651    unsafe {
652        apply_options(ctxt, options & ALL_MASK);
653    }
654    options & !ALL_MASK
655}
656
657/// Install a per-context structured error handler.
658///
659/// # UPSTREAM-PARITY
660///
661/// ```c
662/// void xmlCtxtSetErrorHandler(xmlParserCtxtPtr ctxt,
663///                             xmlStructuredErrorFunc handler, void *data);
664/// ```
665#[no_mangle]
666pub unsafe extern "C" fn xmlCtxtSetErrorHandler(
667    ctxt: *mut _xmlParserCtxt,
668    handler: Option<xmlStructuredErrorFunc>,
669    data: *mut c_void,
670) {
671    if ctxt.is_null() {
672        return;
673    }
674    unsafe {
675        (*ctxt).errorHandler = handler;
676        (*ctxt).errorCtxt = data;
677    }
678}
679
680/// Set the maximum entity expansion amplification factor.
681///
682/// # UPSTREAM-PARITY
683///
684/// ```c
685/// void xmlCtxtSetMaxAmplification(xmlParserCtxtPtr ctxt, unsigned maxAmpl);
686/// ```
687#[no_mangle]
688pub unsafe extern "C" fn xmlCtxtSetMaxAmplification(ctxt: *mut _xmlParserCtxt, maxAmpl: c_uint) {
689    if ctxt.is_null() || maxAmpl == 0 {
690        return;
691    }
692    unsafe {
693        (*ctxt).maxAmpl = maxAmpl;
694    }
695}
696
697/// Get the last error raised on the context, or NULL.
698///
699/// # UPSTREAM-PARITY
700///
701/// ```c
702/// const xmlError *xmlCtxtGetLastError(void *ctx);
703/// ```
704#[no_mangle]
705pub unsafe extern "C" fn xmlCtxtGetLastError(ctx: *mut c_void) -> *const _xmlError {
706    if ctx.is_null() {
707        return ptr::null();
708    }
709    let ctxt = ctx as *mut _xmlParserCtxt;
710    unsafe {
711        if (*ctxt).lastError.code == XML_ERR_OK {
712            return ptr::null();
713        }
714        &(*ctxt).lastError
715    }
716}
717
718/// Reset the context's last-error state.
719///
720/// # UPSTREAM-PARITY
721///
722/// ```c
723/// void xmlCtxtResetLastError(void *ctx);
724/// ```
725#[no_mangle]
726pub unsafe extern "C" fn xmlCtxtResetLastError(ctx: *mut c_void) {
727    if ctx.is_null() {
728        return;
729    }
730    let ctxt = ctx as *mut _xmlParserCtxt;
731    unsafe {
732        (*ctxt).errNo = XML_ERR_OK;
733        if (*ctxt).lastError.code != XML_ERR_OK {
734            errors::reset_error(&mut (*ctxt).lastError);
735        }
736    }
737}
738
739/// Handle an out-of-memory error on a parser context.
740///
741/// # UPSTREAM-PARITY
742///
743/// ```c
744/// void xmlCtxtErrMemory(xmlParserCtxtPtr ctxt);
745/// ```
746#[no_mangle]
747pub unsafe extern "C" fn xmlCtxtErrMemory(ctxt: *mut _xmlParserCtxt) {
748    if ctxt.is_null() {
749        return;
750    }
751    unsafe {
752        let c = &mut *ctxt;
753        c.errNo = XML_ERR_NO_MEMORY;
754        c.instate = xmlParserInputState::XML_PARSER_EOF as c_int;
755        c.wellFormed = 0;
756        c.disableSAX = 2;
757
758        c.lastError.domain = XML_FROM_PARSER;
759        c.lastError.code = XML_ERR_NO_MEMORY;
760        c.lastError.level = xmlErrorLevel::XML_ERR_FATAL as c_int;
761        c.lastError.message = b"out of memory\n\0".as_ptr() as *mut c_char;
762
763        if let Some(handler) = c.errorHandler {
764            handler(c.errorCtxt, &c.lastError);
765        } else if !c.sax.is_null() {
766            if let Some(serror) = (*c.sax).serror {
767                serror(c.userData, &c.lastError);
768            }
769        }
770    }
771}
772
773/// Stop the parser: no further processing will happen.
774///
775/// # UPSTREAM-PARITY
776///
777/// ```c
778/// void xmlStopParser(xmlParserCtxtPtr ctxt);
779/// ```
780#[no_mangle]
781pub unsafe extern "C" fn xmlStopParser(ctxt: *mut _xmlParserCtxt) {
782    if ctxt.is_null() {
783        return;
784    }
785    unsafe {
786        (*ctxt).disableSAX = 2;
787        if (*ctxt).errNo == XML_ERR_OK {
788            (*ctxt).errNo = XML_ERR_USER_STOP;
789            (*ctxt).lastError.code = XML_ERR_USER_STOP;
790            (*ctxt).wellFormed = 0;
791        }
792    }
793}
794
795/// Return the byte offset of the current parse position within the current
796/// entity, or -1 when it cannot be computed.
797///
798/// # UPSTREAM-PARITY
799///
800/// ```c
801/// long xmlByteConsumed(xmlParserCtxtPtr ctxt);
802/// ```
803#[no_mangle]
804pub unsafe extern "C" fn xmlByteConsumed(ctxt: *mut _xmlParserCtxt) -> c_long {
805    if ctxt.is_null() {
806        return -1;
807    }
808    unsafe {
809        let input = (*ctxt).input;
810        if input.is_null() {
811            return -1;
812        }
813        if !(*input).buf.is_null() && !(*(*input).buf).encoder.is_null() {
814            // With an encoder we cannot cheaply compute the original byte
815            // position; report the raw consumed count.
816            return (*(*input).buf).rawconsumed as c_long;
817        }
818        let consumed = (*input).consumed;
819        if (*input).base.is_null() {
820            return consumed as c_long;
821        }
822        (consumed + ((*input).cur as usize).saturating_sub((*input).base as usize) as c_ulong)
823            as c_long
824    }
825}
826
827/// Extract the directory part of a filename (newly allocated).
828///
829/// # UPSTREAM-PARITY
830///
831/// ```c
832/// char *xmlParserGetDirectory(const char *filename);
833/// ```
834#[no_mangle]
835pub unsafe extern "C" fn xmlParserGetDirectory(filename: *const c_char) -> *mut c_char {
836    if filename.is_null() {
837        return ptr::null_mut();
838    }
839    unsafe {
840        let len = libc::strlen(filename);
841        let mut last_sep: Option<usize> = None;
842        for i in 0..len {
843            if *filename.add(i) == b'/' as c_char {
844                last_sep = Some(i);
845            }
846        }
847        match last_sep {
848            Some(0) => xmlMemStrdup(b"/\0".as_ptr() as *const c_char) as *mut c_char,
849            Some(pos) => {
850                let slice = core::slice::from_raw_parts(filename as *const u8, pos);
851                let mut v = slice.to_vec();
852                v.push(0);
853                xmlMemStrdup(v.as_ptr() as *const c_char) as *mut c_char
854            }
855            None => xmlMemStrdup(b".\0".as_ptr() as *const c_char) as *mut c_char,
856        }
857    }
858}
859
860/// Check whether a file exists: 0 if stat fails, 2 if it is a directory,
861/// 1 otherwise.
862///
863/// # UPSTREAM-PARITY
864///
865/// ```c
866/// int xmlCheckFilename(const char *path);
867/// ```
868#[no_mangle]
869pub unsafe extern "C" fn xmlCheckFilename(path: *const c_char) -> c_int {
870    if path.is_null() {
871        return 0;
872    }
873    unsafe {
874        let mut st: libc::stat = core::mem::zeroed();
875        if libc::stat(path, &mut st) != 0 {
876            return 0;
877        }
878        if st.st_mode & libc::S_IFMT == libc::S_IFDIR {
879            2
880        } else {
881            1
882        }
883    }
884}
885
886/// Test whether a public/system ID pair is one of the XHTML DTDs.
887///
888/// # UPSTREAM-PARITY
889///
890/// ```c
891/// int xmlIsXHTML(const xmlChar *systemID, const xmlChar *publicID);
892/// ```
893#[no_mangle]
894pub unsafe extern "C" fn xmlIsXHTML(
895    systemID: *const xmlChar,
896    publicID: *const xmlChar,
897) -> c_int {
898    const XHTML_STRICT_PUBLIC_ID: &[u8] = b"-//W3C//DTD XHTML 1.0 Strict//EN\0";
899    const XHTML_STRICT_SYSTEM_ID: &[u8] = b"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\0";
900    const XHTML_FRAME_PUBLIC_ID: &[u8] = b"-//W3C//DTD XHTML 1.0 Frameset//EN\0";
901    const XHTML_FRAME_SYSTEM_ID: &[u8] = b"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\0";
902    const XHTML_TRANS_PUBLIC_ID: &[u8] = b"-//W3C//DTD XHTML 1.0 Transitional//EN\0";
903    const XHTML_TRANS_SYSTEM_ID: &[u8] =
904        b"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\0";
905
906    if systemID.is_null() && publicID.is_null() {
907        return -1;
908    }
909    unsafe {
910        if !publicID.is_null() {
911            if string::xml_strcmp(publicID, XHTML_STRICT_PUBLIC_ID.as_ptr() as *const xmlChar) == 0
912                || string::xml_strcmp(
913                    publicID,
914                    XHTML_FRAME_PUBLIC_ID.as_ptr() as *const xmlChar,
915                ) == 0
916                || string::xml_strcmp(
917                    publicID,
918                    XHTML_TRANS_PUBLIC_ID.as_ptr() as *const xmlChar,
919                ) == 0
920            {
921                return 1;
922            }
923        }
924        if !systemID.is_null() {
925            if string::xml_strcmp(systemID, XHTML_STRICT_SYSTEM_ID.as_ptr() as *const xmlChar) == 0
926                || string::xml_strcmp(systemID, XHTML_FRAME_SYSTEM_ID.as_ptr() as *const xmlChar)
927                    == 0
928                || string::xml_strcmp(
929                    systemID,
930                    XHTML_TRANS_SYSTEM_ID.as_ptr() as *const xmlChar,
931                ) == 0
932            {
933                return 1;
934            }
935        }
936    }
937    0
938}
939
940// ═══════════════════════════════════════════════════════════════════════════════
941// Context creation from sources
942// ═══════════════════════════════════════════════════════════════════════════════
943
944/// Create a parser context for an in-memory document.
945///
946/// # UPSTREAM-PARITY
947///
948/// ```c
949/// xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char *buffer, int size);
950/// ```
951#[no_mangle]
952pub unsafe extern "C" fn xmlCreateMemoryParserCtxt(
953    buffer: *const c_char,
954    size: c_int,
955) -> *mut _xmlParserCtxt {
956    if buffer.is_null() || size < 0 {
957        return ptr::null_mut();
958    }
959    unsafe {
960        let ctxt = xmlNewParserCtxt();
961        if ctxt.is_null() {
962            return ptr::null_mut();
963        }
964        let input = helpers::input_from_memory(buffer, size);
965        helpers::setup_parser_input(ctxt, input);
966        ctxt
967    }
968}
969
970/// Create a parser context for push parsing.
971///
972/// # UPSTREAM-PARITY
973///
974/// ```c
975/// xmlParserCtxtPtr xmlCreatePushParserCtxt(xmlSAXHandler *sax, void *user_data,
976///                                          const char *chunk, int size,
977///                                          const char *filename);
978/// ```
979#[no_mangle]
980pub unsafe extern "C" fn xmlCreatePushParserCtxt(
981    sax: *mut _xmlSAXHandler,
982    user_data: *mut c_void,
983    chunk: *const c_char,
984    size: c_int,
985    filename: *const c_char,
986) -> *mut _xmlParserCtxt {
987    unsafe {
988        let ctxt = xmlNewSAXParserCtxt(sax, user_data);
989        if ctxt.is_null() {
990            return ptr::null_mut();
991        }
992        let slice = if size > 0 && !chunk.is_null() {
993            core::slice::from_raw_parts(chunk as *const u8, size as usize)
994        } else {
995            &[]
996        };
997        let uri = if filename.is_null() {
998            None
999        } else {
1000            CStr::from_ptr(filename).to_str().ok()
1001        };
1002        let input = InputBuffer::from_memory(slice, uri);
1003        helpers::setup_parser_input(ctxt, input);
1004        ctxt
1005    }
1006}
1007
1008/// Create a parser context for an I/O stream.
1009///
1010/// # UPSTREAM-PARITY
1011///
1012/// ```c
1013/// xmlParserCtxtPtr xmlCreateIOParserCtxt(xmlSAXHandler *sax, void *user_data,
1014///                                        xmlInputReadCallback ioread,
1015///                                        xmlInputCloseCallback ioclose,
1016///                                        void *ioctx, xmlCharEncoding enc);
1017/// ```
1018#[no_mangle]
1019pub unsafe extern "C" fn xmlCreateIOParserCtxt(
1020    sax: *mut _xmlSAXHandler,
1021    user_data: *mut c_void,
1022    ioread: Option<xmlInputReadCallback>,
1023    ioclose: Option<xmlInputCloseCallback>,
1024    ioctx: *mut c_void,
1025    enc: c_int,
1026) -> *mut _xmlParserCtxt {
1027    unsafe {
1028        let ctxt = xmlNewSAXParserCtxt(sax, user_data);
1029        if ctxt.is_null() {
1030            return ptr::null_mut();
1031        }
1032        let input = helpers::input_from_io(ioread, ioclose, ioctx);
1033        helpers::setup_parser_input(ctxt, input);
1034        if enc != xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int {
1035            xmlSwitchEncoding(ctxt, enc);
1036        }
1037        ctxt
1038    }
1039}
1040
1041/// Create a parser context for a file or URL.
1042///
1043/// # UPSTREAM-PARITY
1044///
1045/// ```c
1046/// xmlParserCtxtPtr xmlCreateURLParserCtxt(const char *filename, int options);
1047/// ```
1048#[no_mangle]
1049pub unsafe extern "C" fn xmlCreateURLParserCtxt(
1050    filename: *const c_char,
1051    options: c_int,
1052) -> *mut _xmlParserCtxt {
1053    if filename.is_null() {
1054        return ptr::null_mut();
1055    }
1056    unsafe {
1057        let ctxt = xmlNewParserCtxt();
1058        if ctxt.is_null() {
1059            return ptr::null_mut();
1060        }
1061        apply_options(ctxt, options);
1062        let input = match helpers::input_from_file(filename) {
1063            Ok(i) => i,
1064            Err(_) => {
1065                helpers::free_parser_ctxt(ctxt);
1066                return ptr::null_mut();
1067            }
1068        };
1069        helpers::setup_parser_input(ctxt, input);
1070        ctxt
1071    }
1072}
1073
1074/// Create a parser context for an external entity.
1075///
1076/// # UPSTREAM-PARITY
1077///
1078/// ```c
1079/// xmlParserCtxtPtr xmlCreateEntityParserCtxt(const xmlChar *URL,
1080///                                            const xmlChar *ID,
1081///                                            const xmlChar *base);
1082/// ```
1083#[no_mangle]
1084pub unsafe extern "C" fn xmlCreateEntityParserCtxt(
1085    URL: *const xmlChar,
1086    ID: *const xmlChar,
1087    base: *const xmlChar,
1088) -> *mut _xmlParserCtxt {
1089    let _ = base; // base URI resolution is a no-op here
1090    unsafe {
1091        let ctxt = xmlNewParserCtxt();
1092        if ctxt.is_null() {
1093            return ptr::null_mut();
1094        }
1095        let input = xmlLoadExternalEntity(URL as *const c_char, ID as *const c_char, ctxt);
1096        if input.is_null() {
1097            helpers::free_parser_ctxt(ctxt);
1098            return ptr::null_mut();
1099        }
1100        if xmlPushInput(ctxt, input) < 0 {
1101            helpers::free_parser_input(input);
1102            helpers::free_parser_ctxt(ctxt);
1103            return ptr::null_mut();
1104        }
1105        ctxt
1106    }
1107}
1108
1109// ═══════════════════════════════════════════════════════════════════════════════
1110// CtxtRead family
1111// ═══════════════════════════════════════════════════════════════════════════════
1112
1113/// Parse an XML in-memory document with a given context.
1114///
1115/// # UPSTREAM-PARITY
1116///
1117/// ```c
1118/// xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar *cur,
1119///                          const char *URL, const char *encoding, int options);
1120/// ```
1121#[no_mangle]
1122pub unsafe extern "C" fn xmlCtxtReadDoc(
1123    ctxt: *mut _xmlParserCtxt,
1124    cur: *const xmlChar,
1125    URL: *const c_char,
1126    _encoding: *const c_char,
1127    options: c_int,
1128) -> *mut _xmlDoc {
1129    if ctxt.is_null() || cur.is_null() {
1130        return ptr::null_mut();
1131    }
1132    unsafe {
1133        let len = string::xml_strlen(cur);
1134        let input = helpers::input_from_memory(cur as *const c_char, len as c_int);
1135        ctxt_read_doc(ctxt, input, URL, options)
1136    }
1137}
1138
1139/// Parse an XML file with a given context.
1140///
1141/// # UPSTREAM-PARITY
1142///
1143/// ```c
1144/// xmlDocPtr xmlCtxtReadFile(xmlParserCtxtPtr ctxt, const char *filename,
1145///                           const char *encoding, int options);
1146/// ```
1147#[no_mangle]
1148pub unsafe extern "C" fn xmlCtxtReadFile(
1149    ctxt: *mut _xmlParserCtxt,
1150    filename: *const c_char,
1151    _encoding: *const c_char,
1152    options: c_int,
1153) -> *mut _xmlDoc {
1154    if ctxt.is_null() || filename.is_null() {
1155        return ptr::null_mut();
1156    }
1157    unsafe {
1158        match helpers::input_from_file(filename) {
1159            Ok(input) => ctxt_read_doc(ctxt, input, filename, options),
1160            Err(_) => ptr::null_mut(),
1161        }
1162    }
1163}
1164
1165/// Parse an XML in-memory block with a given context.
1166///
1167/// # UPSTREAM-PARITY
1168///
1169/// ```c
1170/// xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt, const char *buffer,
1171///                             int size, const char *URL, const char *encoding,
1172///                             int options);
1173/// ```
1174#[no_mangle]
1175pub unsafe extern "C" fn xmlCtxtReadMemory(
1176    ctxt: *mut _xmlParserCtxt,
1177    buffer: *const c_char,
1178    size: c_int,
1179    URL: *const c_char,
1180    _encoding: *const c_char,
1181    options: c_int,
1182) -> *mut _xmlDoc {
1183    if ctxt.is_null() || buffer.is_null() || size < 0 {
1184        return ptr::null_mut();
1185    }
1186    unsafe {
1187        let input = helpers::input_from_memory(buffer, size);
1188        ctxt_read_doc(ctxt, input, URL, options)
1189    }
1190}
1191
1192/// Parse an XML document from a file descriptor with a given context.
1193///
1194/// # UPSTREAM-PARITY
1195///
1196/// ```c
1197/// xmlDocPtr xmlCtxtReadFd(xmlParserCtxtPtr ctxt, int fd, const char *URL,
1198///                         const char *encoding, int options);
1199/// ```
1200#[no_mangle]
1201pub unsafe extern "C" fn xmlCtxtReadFd(
1202    ctxt: *mut _xmlParserCtxt,
1203    fd: c_int,
1204    URL: *const c_char,
1205    _encoding: *const c_char,
1206    options: c_int,
1207) -> *mut _xmlDoc {
1208    if ctxt.is_null() || fd < 0 {
1209        return ptr::null_mut();
1210    }
1211    unsafe {
1212        let mut buf = Vec::new();
1213        let mut tmp = [0u8; 4096];
1214        loop {
1215            let n = libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len());
1216            if n <= 0 {
1217                break;
1218            }
1219            buf.extend_from_slice(&tmp[..n as usize]);
1220        }
1221        let input =
1222            helpers::input_from_memory(buf.as_ptr() as *const c_char, buf.len() as c_int);
1223        ctxt_read_doc(ctxt, input, URL, options)
1224    }
1225}
1226
1227/// Parse an XML document from I/O callbacks with a given context.
1228///
1229/// # UPSTREAM-PARITY
1230///
1231/// ```c
1232/// xmlDocPtr xmlCtxtReadIO(xmlParserCtxtPtr ctxt, xmlInputReadCallback ioread,
1233///                         xmlInputCloseCallback ioclose, void *ioctx,
1234///                         const char *URL, const char *encoding, int options);
1235/// ```
1236#[no_mangle]
1237pub unsafe extern "C" fn xmlCtxtReadIO(
1238    ctxt: *mut _xmlParserCtxt,
1239    ioread: Option<xmlInputReadCallback>,
1240    ioclose: Option<xmlInputCloseCallback>,
1241    ioctx: *mut c_void,
1242    URL: *const c_char,
1243    _encoding: *const c_char,
1244    options: c_int,
1245) -> *mut _xmlDoc {
1246    if ctxt.is_null() {
1247        return ptr::null_mut();
1248    }
1249    unsafe {
1250        let input = helpers::input_from_io(ioread, ioclose, ioctx);
1251        ctxt_read_doc(ctxt, input, URL, options)
1252    }
1253}
1254
1255/// Parse a document from a raw parser input, taking ownership of `input`.
1256///
1257/// # UPSTREAM-PARITY
1258///
1259/// ```c
1260/// xmlDocPtr xmlCtxtParseDocument(xmlParserCtxtPtr ctxt, xmlParserInputPtr input);
1261/// ```
1262#[no_mangle]
1263pub unsafe extern "C" fn xmlCtxtParseDocument(
1264    ctxt: *mut _xmlParserCtxt,
1265    input: *mut _xmlParserInput,
1266) -> *mut _xmlDoc {
1267    if ctxt.is_null() || input.is_null() {
1268        return ptr::null_mut();
1269    }
1270    unsafe {
1271        // Determine whether the caller's input is already owned by the
1272        // context's input stack (pushed via xmlPushInput).
1273        let mut owned = false;
1274        let nr = (*ctxt).inputNr;
1275        let tab = (*ctxt).inputTab;
1276        if !tab.is_null() {
1277            for i in 0..nr {
1278                if *tab.add(i as usize) == input {
1279                    owned = true;
1280                    break;
1281                }
1282            }
1283        }
1284        if (*ctxt).input == input {
1285            owned = true;
1286        }
1287
1288        // Copy the data first so the context reset cannot invalidate it.
1289        let ib = input_buffer_from_parser_input(input);
1290
1291        xmlCtxtReset(ctxt);
1292        helpers::setup_parser_input(ctxt, ib);
1293        helpers::parse_document(ctxt);
1294
1295        if !owned {
1296            helpers::free_parser_input(input);
1297        }
1298
1299        (*ctxt).myDoc
1300    }
1301}
1302
1303// ═══════════════════════════════════════════════════════════════════════════════
1304// Parser input buffers / streams
1305// ═══════════════════════════════════════════════════════════════════════════════
1306
1307/// Allocate a parser input buffer for the given encoding.
1308///
1309/// # UPSTREAM-PARITY
1310///
1311/// ```c
1312/// xmlParserInputBufferPtr xmlAllocParserInputBuffer(xmlCharEncoding enc);
1313/// ```
1314#[no_mangle]
1315pub unsafe extern "C" fn xmlAllocParserInputBuffer(enc: c_int) -> *mut _xmlParserInputBuffer {
1316    unsafe {
1317        let buf = xmlMallocZero(core::mem::size_of::<_xmlParserInputBuffer>())
1318            as *mut _xmlParserInputBuffer;
1319        if buf.is_null() {
1320            return ptr::null_mut();
1321        }
1322        let b = &mut *buf;
1323        b.buffer = io::buf_create(crate::abi::data_globals::xmlDefaultBufferSize) as *mut c_void;
1324        b.raw = io::buf_create(crate::abi::data_globals::xmlDefaultBufferSize) as *mut c_void;
1325        if b.buffer.is_null() || b.raw.is_null() {
1326            io::buf_free(b.buffer as *mut _xmlBuffer);
1327            io::buf_free(b.raw as *mut _xmlBuffer);
1328            xmlFree(buf as *mut c_void);
1329            return ptr::null_mut();
1330        }
1331        b.compressed = -1;
1332
1333        if enc != xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int && enc != xmlCharEncoding::XML_CHAR_ENCODING_ERROR as c_int {
1334            let handler = encoding_handler_for(enc);
1335            if !handler.is_null() {
1336                b.encoder = handler as *mut c_void;
1337            }
1338        }
1339        buf
1340    }
1341}
1342
1343/// Grow an input buffer by reading up to `len` bytes from its source.
1344///
1345/// # UPSTREAM-PARITY
1346///
1347/// ```c
1348/// int xmlParserInputBufferGrow(xmlParserInputBufferPtr in, int len);
1349/// ```
1350#[no_mangle]
1351pub unsafe extern "C" fn xmlParserInputBufferGrow(
1352    in_: *mut _xmlParserInputBuffer,
1353    len: c_int,
1354) -> c_int {
1355    if in_.is_null() || len <= 0 {
1356        return 0;
1357    }
1358    unsafe {
1359        let b = &mut *in_;
1360        if b.error != 0 {
1361            return -1;
1362        }
1363        let Some(read_cb) = b.readcallback else {
1364            // Memory-based buffer: nothing to grow.
1365            return 0;
1366        };
1367        let mut tmp = vec![0u8; len as usize];
1368        let n = read_cb(b.context, tmp.as_mut_ptr() as *mut c_char, len);
1369        if n < 0 {
1370            b.error = 1;
1371            return -1;
1372        }
1373        if n == 0 {
1374            return 0;
1375        }
1376        io::input_buffer_push(in_, tmp.as_ptr() as *const c_char, n);
1377        n
1378    }
1379}
1380
1381/// Push `len` bytes into an input buffer (push parser).
1382///
1383/// # UPSTREAM-PARITY
1384///
1385/// ```c
1386/// int xmlParserInputBufferPush(xmlParserInputBufferPtr in, int len, const char *buf);
1387/// ```
1388#[no_mangle]
1389pub unsafe extern "C" fn xmlParserInputBufferPush(
1390    in_: *mut _xmlParserInputBuffer,
1391    len: c_int,
1392    buf: *const c_char,
1393) -> c_int {
1394    if in_.is_null() {
1395        return -1;
1396    }
1397    if len < 0 || (len > 0 && buf.is_null()) {
1398        return -1;
1399    }
1400    if len == 0 {
1401        return 0;
1402    }
1403    io::input_buffer_push(in_, buf, len)
1404}
1405
1406/// Read up to `len` bytes from an input buffer's source.
1407///
1408/// # UPSTREAM-PARITY
1409///
1410/// ```c
1411/// int xmlParserInputBufferRead(xmlParserInputBufferPtr in, int len);
1412/// ```
1413#[no_mangle]
1414pub unsafe extern "C" fn xmlParserInputBufferRead(
1415    in_: *mut _xmlParserInputBuffer,
1416    len: c_int,
1417) -> c_int {
1418    xmlParserInputBufferGrow(in_, len)
1419}
1420
1421/// Deprecated: reading directly from an input stream is an error.
1422///
1423/// # UPSTREAM-PARITY
1424///
1425/// ```c
1426/// int xmlParserInputRead(xmlParserInputPtr in, int len);
1427/// ```
1428#[no_mangle]
1429pub unsafe extern "C" fn xmlParserInputRead(_in_: *mut _xmlParserInput, _len: c_int) -> c_int {
1430    -1
1431}
1432
1433/// Grow a parser input's buffer by reading more data from its source.
1434///
1435/// # UPSTREAM-PARITY
1436///
1437/// ```c
1438/// int xmlParserInputGrow(xmlParserInputPtr in, int len);
1439/// ```
1440#[no_mangle]
1441pub unsafe extern "C" fn xmlParserInputGrow(in_: *mut _xmlParserInput, len: c_int) -> c_int {
1442    if in_.is_null() || len < 0 {
1443        return -1;
1444    }
1445    unsafe {
1446        let pi = &*in_;
1447        if pi.base.is_null() || pi.cur.is_null() {
1448            return -1;
1449        }
1450        if pi.buf.is_null() {
1451            // Pure memory input: nothing to grow.
1452            return 0;
1453        }
1454        let b = &*pi.buf;
1455        // Memory buffers are not growable.
1456        if b.readcallback.is_none() && b.encoder.is_null() {
1457            return 0;
1458        }
1459        xmlParserInputBufferGrow(pi.buf, len)
1460    }
1461}
1462
1463/// Shrink a parser input, releasing already-consumed data from the buffer.
1464///
1465/// # UPSTREAM-PARITY
1466///
1467/// ```c
1468/// void xmlParserInputShrink(xmlParserInputPtr in);
1469/// ```
1470#[no_mangle]
1471pub unsafe extern "C" fn xmlParserInputShrink(in_: *mut _xmlParserInput) {
1472    if in_.is_null() {
1473        return;
1474    }
1475    unsafe {
1476        let pi = &mut *in_;
1477        if pi.buf.is_null() || pi.base.is_null() || pi.cur.is_null() {
1478            return;
1479        }
1480        let used = (pi.cur as usize).saturating_sub(pi.base as usize);
1481        if used > LINE_LEN {
1482            // The candidate's inputs are backed by stable memory buffers, so
1483            // the base pointer cannot move; account for the consumed bytes.
1484            pi.consumed = pi.consumed.saturating_add((used - LINE_LEN) as c_ulong);
1485        }
1486    }
1487}
1488
1489/// Create a new (empty) parser input stream.
1490///
1491/// # UPSTREAM-PARITY
1492///
1493/// ```c
1494/// xmlParserInputPtr xmlNewInputStream(xmlParserCtxtPtr ctxt);
1495/// ```
1496#[no_mangle]
1497pub unsafe extern "C" fn xmlNewInputStream(ctxt: *mut _xmlParserCtxt) -> *mut _xmlParserInput {
1498    unsafe {
1499        let input = xmlMallocZero(core::mem::size_of::<_xmlParserInput>())
1500            as *mut _xmlParserInput;
1501        if input.is_null() {
1502            if !ctxt.is_null() {
1503                xmlCtxtErrMemory(ctxt);
1504            }
1505            return ptr::null_mut();
1506        }
1507        (*input).line = 1;
1508        (*input).col = 1;
1509        input
1510    }
1511}
1512
1513/// Wrap an input buffer in a parser input stream.
1514///
1515/// # UPSTREAM-PARITY
1516///
1517/// ```c
1518/// xmlParserInputPtr xmlNewIOInputStream(xmlParserCtxtPtr ctxt,
1519///                                       xmlParserInputBufferPtr input,
1520///                                       xmlCharEncoding enc);
1521/// ```
1522#[no_mangle]
1523pub unsafe extern "C" fn xmlNewIOInputStream(
1524    ctxt: *mut _xmlParserCtxt,
1525    input: *mut _xmlParserInputBuffer,
1526    enc: c_int,
1527) -> *mut _xmlParserInput {
1528    if ctxt.is_null() || input.is_null() {
1529        return ptr::null_mut();
1530    }
1531    unsafe {
1532        let pi = xmlNewInputStream(ctxt);
1533        if pi.is_null() {
1534            return ptr::null_mut();
1535        }
1536        (*pi).buf = input;
1537        if enc != xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int && enc != xmlCharEncoding::XML_CHAR_ENCODING_ERROR as c_int {
1538            let handler = encoding_handler_for(enc);
1539            if !handler.is_null() {
1540                io::input_buffer_set_encoder(input, handler);
1541            }
1542        }
1543        pi
1544    }
1545}
1546
1547/// Create a parser input stream from a zero-terminated string. The string
1548/// must remain valid for the lifetime of the input (static mode).
1549///
1550/// # UPSTREAM-PARITY
1551///
1552/// ```c
1553/// xmlParserInputPtr xmlNewStringInputStream(xmlParserCtxtPtr ctxt,
1554///                                           const xmlChar *buffer);
1555/// ```
1556#[no_mangle]
1557pub unsafe extern "C" fn xmlNewStringInputStream(
1558    ctxt: *mut _xmlParserCtxt,
1559    buffer: *const xmlChar,
1560) -> *mut _xmlParserInput {
1561    if ctxt.is_null() || buffer.is_null() {
1562        return ptr::null_mut();
1563    }
1564    unsafe {
1565        let input = xmlNewInputStream(ctxt);
1566        if input.is_null() {
1567            return ptr::null_mut();
1568        }
1569        let len = string::xml_strlen(buffer);
1570        (*input).base = buffer;
1571        (*input).cur = buffer;
1572        (*input).end = buffer.add(len);
1573        (*input).length = len as c_int;
1574        input
1575    }
1576}
1577
1578/// Setup the parser context to parse a new buffer (legacy API).
1579///
1580/// # UPSTREAM-PARITY
1581///
1582/// ```c
1583/// void xmlSetupParserForBuffer(xmlParserCtxtPtr ctxt, const xmlChar* buffer,
1584///                              const char *filename);
1585/// ```
1586#[no_mangle]
1587pub unsafe extern "C" fn xmlSetupParserForBuffer(
1588    ctxt: *mut _xmlParserCtxt,
1589    buffer: *const xmlChar,
1590    filename: *const c_char,
1591) {
1592    if ctxt.is_null() || buffer.is_null() {
1593        return;
1594    }
1595    unsafe {
1596        xmlCtxtReset(ctxt);
1597        let len = string::xml_strlen(buffer);
1598        let uri = if filename.is_null() {
1599            None
1600        } else {
1601            CStr::from_ptr(filename).to_str().ok()
1602        };
1603        let input = InputBuffer::from_memory(
1604            core::slice::from_raw_parts(buffer, len),
1605            uri,
1606        );
1607        helpers::setup_parser_input(ctxt, input);
1608    }
1609}
1610
1611/// Push an input stream onto the context's input stack.
1612///
1613/// # UPSTREAM-PARITY
1614///
1615/// ```c
1616/// int xmlPushInput(xmlParserCtxtPtr ctxt, xmlParserInputPtr input);
1617/// ```
1618#[no_mangle]
1619pub unsafe extern "C" fn xmlPushInput(
1620    ctxt: *mut _xmlParserCtxt,
1621    input: *mut _xmlParserInput,
1622) -> c_int {
1623    if ctxt.is_null() || input.is_null() {
1624        return -1;
1625    }
1626    unsafe {
1627        let c = &mut *ctxt;
1628        if c.inputNr >= c.inputMax {
1629            let new_max = if c.inputMax == 0 { 5 } else { c.inputMax * 2 };
1630            let new_tab = xmlRealloc(
1631                c.inputTab as *mut c_void,
1632                (new_max as usize) * core::mem::size_of::<*mut _xmlParserInput>(),
1633            ) as *mut *mut _xmlParserInput;
1634            if new_tab.is_null() {
1635                return -1;
1636            }
1637            c.inputTab = new_tab;
1638            c.inputMax = new_max;
1639        }
1640        *c.inputTab.add(c.inputNr as usize) = input;
1641        c.input = input;
1642        (*input).id = c.input_id;
1643        c.input_id += 1;
1644        let idx = c.inputNr;
1645        c.inputNr += 1;
1646        idx
1647    }
1648}
1649
1650/// Pop the top input from the context's input stack and free it; returns the
1651/// current character after the pop (0 at end of input).
1652///
1653/// # UPSTREAM-PARITY
1654///
1655/// ```c
1656/// xmlChar xmlPopInput(xmlParserCtxtPtr ctxt);
1657/// ```
1658#[no_mangle]
1659pub unsafe extern "C" fn xmlPopInput(ctxt: *mut _xmlParserCtxt) -> xmlChar {
1660    if ctxt.is_null() || (*ctxt).inputNr <= 1 {
1661        return 0;
1662    }
1663    unsafe {
1664        let c = &mut *ctxt;
1665        c.inputNr -= 1;
1666        let popped = *c.inputTab.add(c.inputNr as usize);
1667        *c.inputTab.add(c.inputNr as usize) = ptr::null_mut();
1668        if c.inputNr > 0 {
1669            c.input = *c.inputTab.add((c.inputNr - 1) as usize);
1670        } else {
1671            c.input = ptr::null_mut();
1672        }
1673        if !popped.is_null() {
1674            helpers::free_parser_input(popped);
1675        }
1676        if c.input.is_null() {
1677            return 0;
1678        }
1679        let cur = (*c.input).cur;
1680        let end = (*c.input).end;
1681        if cur.is_null() || cur >= end {
1682            0
1683        } else {
1684            *cur
1685        }
1686    }
1687}
1688
1689// ═══════════════════════════════════════════════════════════════════════════════
1690// Encoding switching
1691// ═══════════════════════════════════════════════════════════════════════════════
1692
1693/// Switch the input encoding of the current input.
1694///
1695/// # UPSTREAM-PARITY
1696///
1697/// ```c
1698/// int xmlSwitchEncoding(xmlParserCtxtPtr ctxt, xmlCharEncoding enc);
1699/// ```
1700#[no_mangle]
1701pub unsafe extern "C" fn xmlSwitchEncoding(ctxt: *mut _xmlParserCtxt, enc: c_int) -> c_int {
1702    if ctxt.is_null() || (*ctxt).input.is_null() {
1703        return -1;
1704    }
1705    if enc == xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int {
1706        return 0;
1707    }
1708    unsafe {
1709        let handler = encoding_handler_for(enc);
1710        if handler.is_null() {
1711            return -1;
1712        }
1713        xmlSwitchToEncoding(ctxt, handler)
1714    }
1715}
1716
1717/// Switch the input encoding by name.
1718///
1719/// # UPSTREAM-PARITY
1720///
1721/// ```c
1722/// int xmlSwitchEncodingName(xmlParserCtxtPtr ctxt, const char *encoding);
1723/// ```
1724#[no_mangle]
1725pub unsafe extern "C" fn xmlSwitchEncodingName(
1726    ctxt: *mut _xmlParserCtxt,
1727    encoding: *const c_char,
1728) -> c_int {
1729    if ctxt.is_null() || encoding.is_null() {
1730        return -1;
1731    }
1732    unsafe {
1733        let handler = encoding::find_encoding_handler(encoding as *const xmlChar);
1734        if handler.is_null() {
1735            return -1;
1736        }
1737        xmlSwitchToEncoding(ctxt, handler)
1738    }
1739}
1740
1741/// Switch the encoding of a specific parser input using an encoding handler.
1742///
1743/// # UPSTREAM-PARITY
1744///
1745/// ```c
1746/// int xmlSwitchInputEncoding(xmlParserCtxtPtr ctxt, xmlParserInputPtr input,
1747///                            xmlCharEncodingHandlerPtr handler);
1748/// ```
1749#[no_mangle]
1750pub unsafe extern "C" fn xmlSwitchInputEncoding(
1751    ctxt: *mut _xmlParserCtxt,
1752    input: *mut _xmlParserInput,
1753    handler: *mut _xmlCharEncodingHandler,
1754) -> c_int {
1755    let _ = ctxt;
1756    if input.is_null() {
1757        return -1;
1758    }
1759    unsafe {
1760        if (*input).buf.is_null() {
1761            return -1;
1762        }
1763        io::input_buffer_set_encoder((*input).buf, handler);
1764    }
1765    0
1766}
1767
1768/// Switch the encoding of the current input using an encoding handler.
1769///
1770/// # UPSTREAM-PARITY
1771///
1772/// ```c
1773/// int xmlSwitchToEncoding(xmlParserCtxtPtr ctxt,
1774///                         xmlCharEncodingHandlerPtr handler);
1775/// ```
1776#[no_mangle]
1777pub unsafe extern "C" fn xmlSwitchToEncoding(
1778    ctxt: *mut _xmlParserCtxt,
1779    handler: *mut _xmlCharEncodingHandler,
1780) -> c_int {
1781    if ctxt.is_null() {
1782        return -1;
1783    }
1784    unsafe {
1785        let input = (*ctxt).input;
1786        if input.is_null() || (*input).buf.is_null() {
1787            return -1;
1788        }
1789        io::input_buffer_set_encoder((*input).buf, handler);
1790    }
1791    0
1792}
1793
1794// ═══════════════════════════════════════════════════════════════════════════════
1795// Node info sequence (deprecated, parser.h)
1796// ═══════════════════════════════════════════════════════════════════════════════
1797
1798/// Initialise a node info sequence.
1799///
1800/// # UPSTREAM-PARITY
1801///
1802/// ```c
1803/// void xmlInitNodeInfoSeq(xmlParserNodeInfoSeqPtr seq);
1804/// ```
1805#[no_mangle]
1806pub unsafe extern "C" fn xmlInitNodeInfoSeq(seq: *mut _xmlParserNodeInfoSeq) {
1807    if seq.is_null() {
1808        return;
1809    }
1810    unsafe {
1811        (*seq).block = ptr::null_mut();
1812        (*seq).index = ptr::null_mut();
1813        (*seq).block_max = 0;
1814        (*seq).size = 0;
1815    }
1816}
1817
1818/// Clear (release and reinitialise) a node info sequence.
1819///
1820/// # UPSTREAM-PARITY
1821///
1822/// ```c
1823/// void xmlClearNodeInfoSeq(xmlParserNodeInfoSeqPtr seq);
1824/// ```
1825#[no_mangle]
1826pub unsafe extern "C" fn xmlClearNodeInfoSeq(seq: *mut _xmlParserNodeInfoSeq) {
1827    if seq.is_null() {
1828        return;
1829    }
1830    unsafe {
1831        if !(*seq).block.is_null() {
1832            xmlFree((*seq).block as *mut c_void);
1833        }
1834        if !(*seq).index.is_null() {
1835            xmlFree((*seq).index as *mut c_void);
1836        }
1837        xmlInitNodeInfoSeq(seq);
1838    }
1839}
1840
1841/// Find the index where the info record for `node` is (or should be) in the
1842/// sorted sequence; binary search by node pointer.
1843///
1844/// # UPSTREAM-PARITY
1845///
1846/// ```c
1847/// unsigned long xmlParserFindNodeInfoIndex(xmlParserNodeInfoSeqPtr seq,
1848///                                          xmlNodePtr node);
1849/// ```
1850#[no_mangle]
1851pub unsafe extern "C" fn xmlParserFindNodeInfoIndex(
1852    seq: *mut _xmlParserNodeInfoSeq,
1853    node: *mut _xmlNode,
1854) -> c_ulong {
1855    if seq.is_null() || node.is_null() {
1856        return c_ulong::MAX;
1857    }
1858    unsafe {
1859        let s = &*seq;
1860        if s.block.is_null() || s.size == 0 {
1861            return 0;
1862        }
1863        let mut lower: usize = 0;
1864        let mut upper: usize = s.size as usize;
1865        while lower < upper {
1866            let middle = lower + (upper - lower) / 2;
1867            let cur_node = (*s.block.add(middle)).node;
1868            if cur_node == node {
1869                return middle as c_ulong;
1870            }
1871            if (cur_node as usize) < (node as usize) {
1872                lower = middle + 1;
1873            } else {
1874                upper = middle;
1875            }
1876        }
1877        lower as c_ulong
1878    }
1879}
1880
1881/// Find the node info record for a given node, or NULL.
1882///
1883/// # UPSTREAM-PARITY
1884///
1885/// ```c
1886/// const xmlParserNodeInfo *xmlParserFindNodeInfo(xmlParserCtxtPtr ctxt,
1887///                                                xmlNodePtr node);
1888/// ```
1889#[no_mangle]
1890pub unsafe extern "C" fn xmlParserFindNodeInfo(
1891    ctxt: *mut _xmlParserCtxt,
1892    node: *mut _xmlNode,
1893) -> *const _xmlParserNodeInfo {
1894    if ctxt.is_null() || node.is_null() {
1895        return ptr::null();
1896    }
1897    unsafe {
1898        let seq = &(*ctxt).node_seq;
1899        let seq_mut = seq as *const _ as *mut _xmlParserNodeInfoSeq;
1900        let pos = xmlParserFindNodeInfoIndex(seq_mut, node);
1901        if !seq.block.is_null() && (pos as usize) < (seq.size as usize) {
1902            let info = &*seq.block.add(pos as usize);
1903            if info.node == node {
1904                return info;
1905            }
1906        }
1907        ptr::null()
1908    }
1909}
1910
1911/// Insert a node info record into the context's sorted sequence.
1912///
1913/// # UPSTREAM-PARITY
1914///
1915/// ```c
1916/// void xmlParserAddNodeInfo(xmlParserCtxtPtr ctxt, xmlParserNodeInfoPtr info);
1917/// ```
1918#[no_mangle]
1919pub unsafe extern "C" fn xmlParserAddNodeInfo(
1920    ctxt: *mut _xmlParserCtxt,
1921    info: *mut _xmlParserNodeInfo,
1922) {
1923    if ctxt.is_null() || info.is_null() {
1924        return;
1925    }
1926    unsafe {
1927        let seq = &mut (*ctxt).node_seq;
1928        let node = (*info).node;
1929        let pos = xmlParserFindNodeInfoIndex(seq, node as *mut _xmlNode) as usize;
1930
1931        if !seq.block.is_null() && pos < seq.size as usize && (*seq.block.add(pos)).node == node {
1932            ptr::copy_nonoverlapping(info, seq.block.add(pos), 1);
1933            return;
1934        }
1935
1936        // Grow the block.
1937        if seq.size + 1 > seq.block_max {
1938            let new_max = if seq.block_max == 0 { 4 } else { seq.block_max * 2 };
1939            let new_block = xmlRealloc(
1940                seq.block as *mut c_void,
1941                (new_max as usize) * core::mem::size_of::<_xmlParserNodeInfo>(),
1942            ) as *mut _xmlParserNodeInfo;
1943            if new_block.is_null() {
1944                xmlCtxtErrMemory(ctxt);
1945                return;
1946            }
1947            seq.block = new_block;
1948            seq.block_max = new_max;
1949        }
1950
1951        // Shift elements right to make room at `pos`.
1952        let size = seq.size as usize;
1953        for i in (pos + 1..=size).rev() {
1954            ptr::copy_nonoverlapping(seq.block.add(i - 1), seq.block.add(i), 1);
1955        }
1956        ptr::copy_nonoverlapping(info, seq.block.add(pos), 1);
1957        seq.size += 1;
1958    }
1959}
1960
1961// ═══════════════════════════════════════════════════════════════════════════════
1962// I/O callback registration (xmlIO.h)
1963// ═══════════════════════════════════════════════════════════════════════════════
1964
1965/// Register a new set of input I/O callbacks.
1966///
1967/// # UPSTREAM-PARITY
1968///
1969/// ```c
1970/// int xmlRegisterInputCallbacks(xmlInputMatchCallback matchFunc,
1971///                               xmlInputOpenCallback openFunc,
1972///                               xmlInputReadCallback readFunc,
1973///                               xmlInputCloseCallback closeFunc);
1974/// ```
1975#[no_mangle]
1976pub unsafe extern "C" fn xmlRegisterInputCallbacks(
1977    matchFunc: Option<xmlInputMatchCallback>,
1978    openFunc: Option<xmlInputOpenCallback>,
1979    readFunc: Option<xmlInputReadCallback>,
1980    closeFunc: Option<xmlInputCloseCallback>,
1981) -> c_int {
1982    unsafe {
1983        globals::init_parser();
1984    }
1985    let mut table = INPUT_CALLBACKS.lock();
1986    if table.len() >= 10 {
1987        return -1;
1988    }
1989    table.push(InputCallbackEntry {
1990        matchcb: matchFunc,
1991        opencb: openFunc,
1992        readcb: readFunc,
1993        closecb: closeFunc,
1994    });
1995    (table.len() - 1) as c_int
1996}
1997
1998/// Register the default compiled-in input callbacks (the `xmlFile*` pair).
1999///
2000/// # UPSTREAM-PARITY
2001///
2002/// ```c
2003/// void xmlRegisterDefaultInputCallbacks(void);
2004/// ```
2005#[no_mangle]
2006pub unsafe extern "C" fn xmlRegisterDefaultInputCallbacks() {
2007    unsafe {
2008        xmlRegisterInputCallbacks(
2009            Some(xmlFileMatch),
2010            Some(xmlFileOpen),
2011            Some(xmlFileRead),
2012            Some(xmlFileClose),
2013        );
2014    }
2015}
2016
2017/// Remove the top input callback from the stack.
2018///
2019/// # UPSTREAM-PARITY
2020///
2021/// ```c
2022/// int xmlPopInputCallbacks(void);
2023/// ```
2024#[no_mangle]
2025pub unsafe extern "C" fn xmlPopInputCallbacks() -> c_int {
2026    unsafe {
2027        globals::init_parser();
2028    }
2029    let mut table = INPUT_CALLBACKS.lock();
2030    if table.is_empty() {
2031        return -1;
2032    }
2033    table.pop();
2034    table.len() as c_int
2035}
2036
2037/// Clear the entire input callback table.
2038///
2039/// # UPSTREAM-PARITY
2040///
2041/// ```c
2042/// void xmlCleanupInputCallbacks(void);
2043/// ```
2044#[no_mangle]
2045pub unsafe extern "C" fn xmlCleanupInputCallbacks() {
2046    unsafe {
2047        globals::init_parser();
2048    }
2049    INPUT_CALLBACKS.lock().clear();
2050}
2051
2052/// Register a new set of output I/O callbacks.
2053///
2054/// # UPSTREAM-PARITY
2055///
2056/// ```c
2057/// int xmlRegisterOutputCallbacks(xmlOutputMatchCallback matchFunc,
2058///                                xmlOutputOpenCallback openFunc,
2059///                                xmlOutputWriteCallback writeFunc,
2060///                                xmlOutputCloseCallback closeFunc);
2061/// ```
2062#[no_mangle]
2063pub unsafe extern "C" fn xmlRegisterOutputCallbacks(
2064    matchFunc: Option<xmlOutputMatchCallback>,
2065    openFunc: Option<xmlOutputOpenCallback>,
2066    writeFunc: Option<xmlOutputWriteCallback>,
2067    closeFunc: Option<xmlOutputCloseCallback>,
2068) -> c_int {
2069    unsafe {
2070        globals::init_parser();
2071    }
2072    let mut table = OUTPUT_CALLBACKS.lock();
2073    if table.len() >= 10 {
2074        return -1;
2075    }
2076    table.push(OutputCallbackEntry {
2077        matchcb: matchFunc,
2078        opencb: openFunc,
2079        writecb: writeFunc,
2080        closecb: closeFunc,
2081    });
2082    (table.len() - 1) as c_int
2083}
2084
2085/// Register the default compiled-in output callbacks.
2086///
2087/// # UPSTREAM-PARITY
2088///
2089/// ```c
2090/// void xmlRegisterDefaultOutputCallbacks(void);
2091/// ```
2092#[no_mangle]
2093pub unsafe extern "C" fn xmlRegisterDefaultOutputCallbacks() {
2094    unsafe {
2095        xmlRegisterOutputCallbacks(Some(xmlFileMatch), None, None, None);
2096    }
2097}
2098
2099/// Register the HTTP POST output callbacks (upstream: default output callbacks).
2100///
2101/// # UPSTREAM-PARITY
2102///
2103/// ```c
2104/// void xmlRegisterHTTPPostCallbacks(void);
2105/// ```
2106#[no_mangle]
2107pub unsafe extern "C" fn xmlRegisterHTTPPostCallbacks() {
2108    unsafe { xmlRegisterDefaultOutputCallbacks() }
2109}
2110
2111/// Remove the top output callback from the stack.
2112///
2113/// # UPSTREAM-PARITY
2114///
2115/// ```c
2116/// int xmlPopOutputCallbacks(void);
2117/// ```
2118#[no_mangle]
2119pub unsafe extern "C" fn xmlPopOutputCallbacks() -> c_int {
2120    unsafe {
2121        globals::init_parser();
2122    }
2123    let mut table = OUTPUT_CALLBACKS.lock();
2124    if table.is_empty() {
2125        return -1;
2126    }
2127    table.pop();
2128    table.len() as c_int
2129}
2130
2131/// Clear the entire output callback table.
2132///
2133/// # UPSTREAM-PARITY
2134///
2135/// ```c
2136/// void xmlCleanupOutputCallbacks(void);
2137/// ```
2138#[no_mangle]
2139pub unsafe extern "C" fn xmlCleanupOutputCallbacks() {
2140    unsafe {
2141        globals::init_parser();
2142    }
2143    OUTPUT_CALLBACKS.lock().clear();
2144}
2145
2146// ═══════════════════════════════════════════════════════════════════════════════
2147// External entity loaders (parser.h)
2148// ═══════════════════════════════════════════════════════════════════════════════
2149
2150/// Default external entity loader: resolve `url` against the filesystem,
2151/// honouring XML_PARSE_NONET.
2152///
2153/// # Safety
2154///
2155/// `url`/`publicId` must be valid C strings or NULL; `ctxt` may be NULL.
2156unsafe extern "C" fn default_external_entity_loader(
2157    url: *const c_char,
2158    public_id: *const c_char,
2159    ctxt: *mut _xmlParserCtxt,
2160) -> *mut _xmlParserInput {
2161    let _ = public_id;
2162    if url.is_null() {
2163        return ptr::null_mut();
2164    }
2165    unsafe {
2166        // Refuse network access when NONET is set.
2167        if !ctxt.is_null() && (*ctxt).options & XML_PARSE_NONET != 0 {
2168            let len = libc::strlen(url);
2169            if len >= 7 && libc::strncasecmp(url, b"http://\0".as_ptr() as *const c_char, 7) == 0 {
2170                return ptr::null_mut();
2171            }
2172        }
2173        // Try the registered input callbacks first.
2174        let table = INPUT_CALLBACKS.lock();
2175        for entry in table.iter() {
2176            if let (Some(match_cb), Some(open_cb)) = (entry.matchcb, entry.opencb) {
2177                if match_cb(url) != 0 {
2178                    let ctx = open_cb(url);
2179                    if !ctx.is_null() {
2180                        let buf = helpers::alloc_parser_input_buffer();
2181                        if buf.is_null() {
2182                            if let Some(close_cb) = entry.closecb {
2183                                close_cb(ctx);
2184                            }
2185                            return ptr::null_mut();
2186                        }
2187                        (*buf).context = ctx;
2188                        (*buf).readcallback = entry.readcb;
2189                        (*buf).closecallback = entry.closecb;
2190                        return parser_input_from_buf(buf);
2191                    }
2192                }
2193            }
2194        }
2195
2196        // Fall back to a plain file open.
2197        let buf = io::input_buffer_create_file(url, xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int);
2198        if buf.is_null() {
2199            return ptr::null_mut();
2200        }
2201        parser_input_from_buf(buf)
2202    }
2203}
2204
2205/// Set the application-wide external entity loader.
2206///
2207/// # UPSTREAM-PARITY
2208///
2209/// ```c
2210/// void xmlSetExternalEntityLoader(xmlExternalEntityLoader f);
2211/// ```
2212#[no_mangle]
2213pub unsafe extern "C" fn xmlSetExternalEntityLoader(f: Option<xmlExternalEntityLoader>) {
2214    *EXTERNAL_ENTITY_LOADER.lock() = f;
2215}
2216
2217/// Get the current external entity loader.
2218///
2219/// # UPSTREAM-PARITY
2220///
2221/// ```c
2222/// xmlExternalEntityLoader xmlGetExternalEntityLoader(void);
2223/// ```
2224#[no_mangle]
2225pub unsafe extern "C" fn xmlGetExternalEntityLoader() -> Option<xmlExternalEntityLoader> {
2226    *EXTERNAL_ENTITY_LOADER.lock()
2227}
2228
2229/// External entity loader that disables network access.
2230///
2231/// # UPSTREAM-PARITY
2232///
2233/// ```c
2234/// xmlParserInputPtr xmlNoNetExternalEntityLoader(const char *URL,
2235///                                                const char *ID,
2236///                                                xmlParserCtxtPtr ctxt);
2237/// ```
2238#[no_mangle]
2239pub unsafe extern "C" fn xmlNoNetExternalEntityLoader(
2240    URL: *const c_char,
2241    ID: *const c_char,
2242    ctxt: *mut _xmlParserCtxt,
2243) -> *mut _xmlParserInput {
2244    unsafe {
2245        let old_options = if ctxt.is_null() { 0 } else { (*ctxt).options };
2246        if !ctxt.is_null() {
2247            (*ctxt).options |= XML_PARSE_NONET;
2248        }
2249        let input = default_external_entity_loader(URL, ID, ctxt);
2250        if !ctxt.is_null() {
2251            (*ctxt).options = old_options;
2252        }
2253        input
2254    }
2255}
2256
2257/// Load an external entity using the registered loader.
2258///
2259/// # UPSTREAM-PARITY
2260///
2261/// ```c
2262/// xmlParserInputPtr xmlLoadExternalEntity(const char *URL, const char *ID,
2263///                                         xmlParserCtxtPtr ctxt);
2264/// ```
2265#[no_mangle]
2266pub unsafe extern "C" fn xmlLoadExternalEntity(
2267    URL: *const c_char,
2268    ID: *const c_char,
2269    ctxt: *mut _xmlParserCtxt,
2270) -> *mut _xmlParserInput {
2271    let loader = *EXTERNAL_ENTITY_LOADER.lock();
2272    match loader {
2273        Some(f) => unsafe { f(URL, ID, ctxt) },
2274        None => unsafe { default_external_entity_loader(URL, ID, ctxt) },
2275    }
2276}
2277
2278/// Check an input for HTTP access; with XML_PARSE_NONET set, HTTP inputs are
2279/// refused and freed.
2280///
2281/// # UPSTREAM-PARITY
2282///
2283/// ```c
2284/// xmlParserInputPtr xmlCheckHTTPInput(xmlParserCtxtPtr ctxt,
2285///                                     xmlParserInputPtr ret);
2286/// ```
2287#[no_mangle]
2288pub unsafe extern "C" fn xmlCheckHTTPInput(
2289    ctxt: *mut _xmlParserCtxt,
2290    ret: *mut _xmlParserInput,
2291) -> *mut _xmlParserInput {
2292    if ret.is_null() {
2293        return ptr::null_mut();
2294    }
2295    unsafe {
2296        if !ctxt.is_null() && (*ctxt).options & XML_PARSE_NONET != 0 {
2297            let filename = (*ret).filename;
2298            if !filename.is_null() {
2299                let len = libc::strlen(filename);
2300                if len >= 7 && libc::strncasecmp(filename, b"http://\0".as_ptr() as *const c_char, 7) == 0 {
2301                    if !(*ret).buf.is_null() {
2302                        io::input_buffer_free((*ret).buf);
2303                    }
2304                    helpers::free_parser_input(ret);
2305                    return ptr::null_mut();
2306                }
2307            }
2308        }
2309        ret
2310    }
2311}
2312
2313// ═══════════════════════════════════════════════════════════════════════════════
2314// xmlFile* I/O callbacks (xmlIO.c)
2315// ═══════════════════════════════════════════════════════════════════════════════
2316
2317/// Match callback: the file I/O handlers accept every filename.
2318///
2319/// # UPSTREAM-PARITY
2320///
2321/// ```c
2322/// int xmlFileMatch(const char *filename);
2323/// ```
2324#[no_mangle]
2325pub unsafe extern "C" fn xmlFileMatch(_filename: *const c_char) -> c_int {
2326    1
2327}
2328
2329/// Open a file and return a `FILE *` I/O context (cast to `void *`).
2330///
2331/// # UPSTREAM-PARITY
2332///
2333/// ```c
2334/// void *xmlFileOpen(const char *filename);
2335/// ```
2336#[no_mangle]
2337pub unsafe extern "C" fn xmlFileOpen(filename: *const c_char) -> *mut c_void {
2338    if filename.is_null() {
2339        return ptr::null_mut();
2340    }
2341    unsafe { libc::fopen(filename, b"rb\0".as_ptr() as *const c_char) as *mut c_void }
2342}
2343
2344/// Read up to `len` bytes from a `FILE *` I/O context.
2345///
2346/// # UPSTREAM-PARITY
2347///
2348/// ```c
2349/// int xmlFileRead(void *context, char *buffer, int len);
2350/// ```
2351#[no_mangle]
2352pub unsafe extern "C" fn xmlFileRead(
2353    context: *mut c_void,
2354    buffer: *mut c_char,
2355    len: c_int,
2356) -> c_int {
2357    if context.is_null() || buffer.is_null() || len <= 0 {
2358        return -1;
2359    }
2360    unsafe {
2361        let n = libc::fread(buffer as *mut c_void, 1, len as usize, context as *mut libc::FILE);
2362        if n < len as usize && libc::ferror(context as *mut libc::FILE) != 0 {
2363            return -1;
2364        }
2365        n as c_int
2366    }
2367}
2368
2369/// Close a `FILE *` I/O context.
2370///
2371/// # UPSTREAM-PARITY
2372///
2373/// ```c
2374/// int xmlFileClose(void *context);
2375/// ```
2376#[no_mangle]
2377pub unsafe extern "C" fn xmlFileClose(context: *mut c_void) -> c_int {
2378    if context.is_null() {
2379        return -1;
2380    }
2381    unsafe {
2382        let file = context as *mut libc::FILE;
2383        let fd = libc::fileno(file);
2384        if fd == 0 {
2385            // stdin must not be closed.
2386            return 0;
2387        }
2388        if fd == 1 || fd == 2 {
2389            // stdout/stderr are only flushed.
2390            return if libc::fflush(file) == 0 { 0 } else { -1 };
2391        }
2392        libc::fclose(file)
2393    }
2394}
2395
2396// ═══════════════════════════════════════════════════════════════════════════════
2397// Low-level character scanning (parserInternals.c)
2398// ═══════════════════════════════════════════════════════════════════════════════
2399
2400/// Return the current character (UTF-8 decoded, EOL normalised) and its byte
2401/// length in `*len`. Does not advance the input pointer.
2402///
2403/// # UPSTREAM-PARITY
2404///
2405/// ```c
2406/// int xmlCurrentChar(xmlParserCtxtPtr ctxt, int *len);
2407/// ```
2408#[no_mangle]
2409pub unsafe extern "C" fn xmlCurrentChar(ctxt: *mut _xmlParserCtxt, len: *mut c_int) -> c_int {
2410    if ctxt.is_null() || len.is_null() || (*ctxt).input.is_null() {
2411        return 0;
2412    }
2413    unsafe {
2414        let pi = &*((*ctxt).input);
2415        let cur = pi.cur;
2416        if cur.is_null() {
2417            *len = 0;
2418            return 0;
2419        }
2420        let avail = (pi.end as usize).saturating_sub(cur as usize);
2421        let c = *cur;
2422
2423        if c < 0x80 {
2424            if c == b'\r' {
2425                // EOL normalisation: CR (optionally CRLF) becomes LF.
2426                if avail >= 2 && *cur.add(1) == b'\n' {
2427                    (*(*ctxt).input).cur = cur.add(1);
2428                }
2429                *len = 1;
2430                return b'\n' as c_int;
2431            }
2432            if c == 0 {
2433                if avail == 0 {
2434                    *len = 0;
2435                } else {
2436                    *len = 1;
2437                }
2438                return 0;
2439            }
2440            *len = 1;
2441            return c as c_int;
2442        }
2443
2444        // Multi-byte UTF-8.
2445        if avail < 2 || (*cur.add(1) & 0xc0) != 0x80 {
2446            *len = 1;
2447            return XML_INVALID_CHAR;
2448        }
2449        if c < 0xe0 {
2450            if c < 0xc2 {
2451                *len = 1;
2452                return XML_INVALID_CHAR;
2453            }
2454            let val = (((c & 0x1f) as c_int) << 6) | ((*cur.add(1) & 0x3f) as c_int);
2455            *len = 2;
2456            return val;
2457        }
2458        if avail < 3 || (*cur.add(2) & 0xc0) != 0x80 {
2459            *len = 1;
2460            return XML_INVALID_CHAR;
2461        }
2462        if c < 0xf0 {
2463            let val = (((c & 0x0f) as c_int) << 12)
2464                | (((*cur.add(1) & 0x3f) as c_int) << 6)
2465                | ((*cur.add(2) & 0x3f) as c_int);
2466            if val < 0x800 || (val >= 0xd800 && val < 0xe000) {
2467                *len = 1;
2468                return XML_INVALID_CHAR;
2469            }
2470            *len = 3;
2471            return val;
2472        }
2473        if avail < 4 || (*cur.add(3) & 0xc0) != 0x80 {
2474            *len = 1;
2475            return XML_INVALID_CHAR;
2476        }
2477        let val = (((c & 0x07) as c_int) << 18)
2478            | (((*cur.add(1) & 0x3f) as c_int) << 12)
2479            | (((*cur.add(2) & 0x3f) as c_int) << 6)
2480            | ((*cur.add(3) & 0x3f) as c_int);
2481        if val < 0x10000 || val >= 0x110000 {
2482            *len = 1;
2483            return XML_INVALID_CHAR;
2484        }
2485        *len = 4;
2486        val
2487    }
2488}
2489
2490/// Advance to the next character, updating line/column accounting.
2491///
2492/// # UPSTREAM-PARITY
2493///
2494/// ```c
2495/// void xmlNextChar(xmlParserCtxtPtr ctxt);
2496/// ```
2497#[no_mangle]
2498pub unsafe extern "C" fn xmlNextChar(ctxt: *mut _xmlParserCtxt) {
2499    if ctxt.is_null() || (*ctxt).input.is_null() {
2500        return;
2501    }
2502    unsafe {
2503        let pi = &mut *((*ctxt).input);
2504        let cur = pi.cur;
2505        if cur.is_null() {
2506            return;
2507        }
2508        let avail = (pi.end as usize).saturating_sub(cur as usize);
2509        if avail == 0 {
2510            return;
2511        }
2512        let c = *cur;
2513
2514        if c < 0x80 {
2515            if c == b'\n' {
2516                pi.cur = cur.add(1);
2517                pi.line += 1;
2518                pi.col = 1;
2519            } else if c == b'\r' {
2520                // CRLF is a single line break.
2521                pi.cur = cur.add(if avail >= 2 && *cur.add(1) == b'\n' { 2 } else { 1 });
2522                pi.line += 1;
2523                pi.col = 1;
2524            } else {
2525                pi.cur = cur.add(1);
2526                pi.col += 1;
2527            }
2528            return;
2529        }
2530
2531        pi.col += 1;
2532
2533        if avail < 2 || (*cur.add(1) & 0xc0) != 0x80 {
2534            pi.cur = cur.add(1);
2535            return;
2536        }
2537        if c < 0xe0 {
2538            if c < 0xc2 {
2539                pi.cur = cur.add(1);
2540                return;
2541            }
2542            pi.cur = cur.add(2);
2543            return;
2544        }
2545        if avail < 3 || (*cur.add(2) & 0xc0) != 0x80 {
2546            pi.cur = cur.add(1);
2547            return;
2548        }
2549        if c < 0xf0 {
2550            let val = (((c as c_int) << 8) as u32) | (*cur.add(1) as u32);
2551            if (val < 0xe0a0) || (val >= 0xeda0 && val < 0xee00) {
2552                pi.cur = cur.add(1);
2553                return;
2554            }
2555            pi.cur = cur.add(3);
2556            return;
2557        }
2558        if avail < 4 || (*cur.add(3) & 0xc0) != 0x80 {
2559            pi.cur = cur.add(1);
2560            return;
2561        }
2562        let val = (((c as c_int) << 8) as u32) | (*cur.add(1) as u32);
2563        if val < 0xf090 || val >= 0xf490 {
2564            pi.cur = cur.add(1);
2565            return;
2566        }
2567        pi.cur = cur.add(4);
2568    }
2569}
2570
2571/// Skip blank characters (space, tab, LF, CR), updating line/column.
2572/// Returns the number of blanks skipped.
2573///
2574/// # UPSTREAM-PARITY
2575///
2576/// ```c
2577/// int xmlSkipBlankChars(xmlParserCtxtPtr ctxt);
2578/// ```
2579#[no_mangle]
2580pub unsafe extern "C" fn xmlSkipBlankChars(ctxt: *mut _xmlParserCtxt) -> c_int {
2581    if ctxt.is_null() || (*ctxt).input.is_null() {
2582        return 0;
2583    }
2584    unsafe {
2585        let pi = &mut *((*ctxt).input);
2586        let mut cur = pi.cur;
2587        if cur.is_null() {
2588            return 0;
2589        }
2590        let end = pi.end;
2591        let mut res = 0;
2592        while cur < end && (*cur == 0x20 || *cur == 0x09 || *cur == 0x0a || *cur == 0x0d) {
2593            if *cur == b'\n' {
2594                pi.line += 1;
2595                pi.col = 1;
2596            } else {
2597                pi.col += 1;
2598            }
2599            cur = cur.add(1);
2600            res += 1;
2601        }
2602        pi.cur = cur;
2603        res
2604    }
2605}
2606
2607/// XML 1.0 5th-edition NameStartChar predicate (upstream `xmlIsNameStartCharNew`).
2608fn is_name_start_char_new(c: c_int) -> bool {
2609    if c == b' ' as c_int || c == b'>' as c_int || c == b'/' as c_int {
2610        return false;
2611    }
2612    (c >= b'a' as c_int && c <= b'z' as c_int)
2613        || (c >= b'A' as c_int && c <= b'Z' as c_int)
2614        || c == b'_' as c_int
2615        || c == b':' as c_int
2616        || (c >= 0xC0 && c <= 0xD6)
2617        || (c >= 0xD8 && c <= 0xF6)
2618        || (c >= 0xF8 && c <= 0x2FF)
2619        || (c >= 0x370 && c <= 0x37D)
2620        || (c >= 0x37F && c <= 0x1FFF)
2621        || (c >= 0x200C && c <= 0x200D)
2622        || (c >= 0x2070 && c <= 0x218F)
2623        || (c >= 0x2C00 && c <= 0x2FEF)
2624        || (c >= 0x3001 && c <= 0xD7FF)
2625        || (c >= 0xF900 && c <= 0xFDCF)
2626        || (c >= 0xFDF0 && c <= 0xFFFD)
2627        || (c >= 0x10000 && c <= 0xEFFFF)
2628}
2629
2630/// XML 1.0 5th-edition NameChar predicate (upstream `xmlIsNameCharNew`).
2631fn is_name_char_new(c: c_int) -> bool {
2632    if c == b' ' as c_int || c == b'>' as c_int || c == b'/' as c_int {
2633        return false;
2634    }
2635    (c >= b'a' as c_int && c <= b'z' as c_int)
2636        || (c >= b'A' as c_int && c <= b'Z' as c_int)
2637        || (c >= b'0' as c_int && c <= b'9' as c_int)
2638        || c == b'_' as c_int
2639        || c == b':' as c_int
2640        || c == b'-' as c_int
2641        || c == b'.' as c_int
2642        || c == 0xB7
2643        || (c >= 0xC0 && c <= 0xD6)
2644        || (c >= 0xD8 && c <= 0xF6)
2645        || (c >= 0xF8 && c <= 0x2FF)
2646        || (c >= 0x300 && c <= 0x36F)
2647        || (c >= 0x370 && c <= 0x37D)
2648        || (c >= 0x37F && c <= 0x1FFF)
2649        || (c >= 0x200C && c <= 0x200D)
2650        || (c >= 0x203F && c <= 0x2040)
2651        || (c >= 0x2070 && c <= 0x218F)
2652        || (c >= 0x2C00 && c <= 0x2FEF)
2653        || (c >= 0x3001 && c <= 0xD7FF)
2654        || (c >= 0xF900 && c <= 0xFDCF)
2655        || (c >= 0xFDF0 && c <= 0xFFFD)
2656        || (c >= 0x10000 && c <= 0xEFFFF)
2657}
2658
2659/// Scan an XML Name (or NCName/Nmtoken) at `ctxt->input->cur`, advancing the
2660/// input pointer. Returns a pointer to the end of the name, or NULL when the
2661/// name exceeds `max` bytes.
2662///
2663/// # UPSTREAM-PARITY
2664///
2665/// ```c
2666/// const xmlChar *xmlScanName(xmlParserCtxtPtr ctxt, int max, int flags);
2667/// ```
2668#[no_mangle]
2669pub unsafe extern "C" fn xmlScanName(
2670    ctxt: *mut _xmlParserCtxt,
2671    max: c_int,
2672    flags: c_int,
2673) -> *const xmlChar {
2674    if ctxt.is_null() || (*ctxt).input.is_null() || max <= 0 {
2675        return ptr::null();
2676    }
2677    unsafe {
2678        let pi = &mut *((*ctxt).input);
2679        let mut ptr = pi.cur;
2680        if ptr.is_null() {
2681            return ptr::null();
2682        }
2683        let end = pi.end;
2684        let mut remaining = max as usize;
2685        let stop: u8 = if flags & XML_SCAN_NC != 0 { b':' } else { 0 };
2686        let old10 = flags & XML_SCAN_OLD10 != 0;
2687        let mut f = flags;
2688
2689        loop {
2690            if ptr >= end {
2691                break;
2692            }
2693            let c = *ptr;
2694            let (cp, len) = if c < 0x80 {
2695                if stop != 0 && c == stop {
2696                    break;
2697                }
2698                (c as c_int, 1usize)
2699            } else {
2700                // Decode a multi-byte UTF-8 character.
2701                let avail = (end as usize).saturating_sub(ptr as usize);
2702                let mut l = 4usize;
2703                let cp = decode_utf8_char(ptr, avail, &mut l);
2704                if cp < 0 {
2705                    break;
2706                }
2707                (cp, l)
2708            };
2709
2710            let ok = if f & XML_SCAN_NMTOKEN != 0 {
2711                if old10 {
2712                    is_name_char_old10(cp)
2713                } else {
2714                    is_name_char_new(cp)
2715                }
2716            } else if old10 {
2717                is_name_start_char_old10(cp)
2718            } else {
2719                is_name_start_char_new(cp)
2720            };
2721            if !ok {
2722                break;
2723            }
2724            if len > remaining {
2725                return ptr::null();
2726            }
2727            ptr = ptr.add(len);
2728            remaining -= len;
2729            f |= XML_SCAN_NMTOKEN;
2730        }
2731
2732        pi.cur = ptr;
2733        ptr
2734    }
2735}
2736
2737/// Decode a UTF-8 character at `ptr` with `avail` bytes available; returns the
2738/// codepoint (or -1 on invalid/truncated input) and sets `*len` to its length.
2739unsafe fn decode_utf8_char(ptr: *const u8, avail: usize, len: &mut usize) -> c_int {
2740    unsafe {
2741        let c = *ptr;
2742        if avail < 2 || (*ptr.add(1) & 0xc0) != 0x80 {
2743            return -1;
2744        }
2745        if c < 0xe0 {
2746            if c < 0xc2 {
2747                return -1;
2748            }
2749            *len = 2;
2750            return (((c & 0x1f) as c_int) << 6) | ((*ptr.add(1) & 0x3f) as c_int);
2751        }
2752        if avail < 3 || (*ptr.add(2) & 0xc0) != 0x80 {
2753            return -1;
2754        }
2755        if c < 0xf0 {
2756            let val = (((c & 0x0f) as c_int) << 12)
2757                | (((*ptr.add(1) & 0x3f) as c_int) << 6)
2758                | ((*ptr.add(2) & 0x3f) as c_int);
2759            if val < 0x800 || (val >= 0xd800 && val < 0xe000) {
2760                return -1;
2761            }
2762            *len = 3;
2763            return val;
2764        }
2765        if avail < 4 || (*ptr.add(3) & 0xc0) != 0x80 {
2766            return -1;
2767        }
2768        let val = (((c & 0x07) as c_int) << 18)
2769            | (((*ptr.add(1) & 0x3f) as c_int) << 12)
2770            | (((*ptr.add(2) & 0x3f) as c_int) << 6)
2771            | ((*ptr.add(3) & 0x3f) as c_int);
2772        if val < 0x10000 || val >= 0x110000 {
2773            return -1;
2774        }
2775        *len = 4;
2776        val
2777    }
2778}
2779
2780/// XML 1.0 (pre-revision-5) NameStartChar predicate: Letter, '_' or ':'.
2781fn is_name_start_char_old10(c: c_int) -> bool {
2782    (c >= b'a' as c_int && c <= b'z' as c_int)
2783        || (c >= b'A' as c_int && c <= b'Z' as c_int)
2784        || c == b'_' as c_int
2785        || c == b':' as c_int
2786        || (c >= 0xC0 && c <= 0xD6)
2787        || (c >= 0xD8 && c <= 0xF6)
2788        || (c >= 0xF8 && c <= 0x2FF)
2789        || (c >= 0x370 && c <= 0x37D)
2790        || (c >= 0x37F && c <= 0x1FFF)
2791        || (c >= 0x200C && c <= 0x200D)
2792        || (c >= 0x2070 && c <= 0x218F)
2793        || (c >= 0x2C00 && c <= 0x2FEF)
2794        || (c >= 0x3001 && c <= 0xD7FF)
2795        || (c >= 0xF900 && c <= 0xFDCF)
2796        || (c >= 0xFDF0 && c <= 0xFFFD)
2797        || (c >= 0x10000 && c <= 0xEFFFF)
2798}
2799
2800/// XML 1.0 (pre-revision-5) NameChar predicate: NameStartChar, digits, '.',
2801/// '-', combining chars and extenders.
2802fn is_name_char_old10(c: c_int) -> bool {
2803    is_name_start_char_old10(c)
2804        || (c >= b'0' as c_int && c <= b'9' as c_int)
2805        || c == b'.' as c_int
2806        || c == b'-' as c_int
2807        || c == 0xB7
2808        || (c >= 0x300 && c <= 0x36F)
2809        || c == 0x02D0
2810        || c == 0x02D1
2811        || c == 0x0387
2812        || c == 0x0640
2813        || c == 0x0E46
2814        || c == 0x0EC6
2815        || c == 0x3005
2816        || (c >= 0x3031 && c <= 0x3035)
2817        || (c >= 0x309D && c <= 0x309E)
2818        || (c >= 0x30FC && c <= 0x30FE)
2819}
2820
2821/// Decode entities from the current input position: char references and
2822/// (predefined and DTD-declared) entity references are substituted. Stops at
2823/// the first of `end`/`end2`/`end3`, or after `len` bytes (`len < 0` = rest).
2824///
2825/// # UPSTREAM-PARITY
2826///
2827/// ```c
2828/// xmlChar *xmlDecodeEntities(xmlParserCtxtPtr ctxt, int len, xmlChar end,
2829///                            xmlChar end2, xmlChar end3);
2830/// ```
2831#[no_mangle]
2832pub unsafe extern "C" fn xmlDecodeEntities(
2833    ctxt: *mut _xmlParserCtxt,
2834    len: c_int,
2835    end: xmlChar,
2836    end2: xmlChar,
2837    end3: xmlChar,
2838) -> *mut xmlChar {
2839    if ctxt.is_null() || (*ctxt).input.is_null() {
2840        return ptr::null_mut();
2841    }
2842    unsafe {
2843        let pi = &*((*ctxt).input);
2844        let cur = pi.cur;
2845        if cur.is_null() {
2846            return ptr::null_mut();
2847        }
2848        let avail = (pi.end as usize).saturating_sub(cur as usize);
2849        let n = if len < 0 {
2850            avail
2851        } else {
2852            (len as usize).min(avail)
2853        };
2854
2855        let mut out: Vec<u8> = Vec::new();
2856        let mut i = 0usize;
2857
2858        while i < n {
2859            let c = *cur.add(i);
2860            if c == end || c == end2 || c == end3 {
2861                break;
2862            }
2863            if c != b'&' {
2864                out.push(c);
2865                i += 1;
2866                continue;
2867            }
2868
2869            // Character reference: &#...; or &#x...;
2870            if i + 1 < n && *cur.add(i + 1) == b'#' {
2871                let (value, consumed) = parse_char_ref(cur.add(i), n - i);
2872                if consumed == 0 {
2873                    out.push(b'&');
2874                    i += 1;
2875                    continue;
2876                }
2877                let mut buf = [0u8; 4];
2878                let blen = copy_char_utf8(&mut buf, value);
2879                out.extend_from_slice(&buf[..blen]);
2880                i += consumed;
2881                continue;
2882            }
2883
2884            // Entity reference: &name;
2885            let mut j = i + 1;
2886            while j < n
2887                && ((*cur.add(j)).is_ascii_alphanumeric()
2888                    || *cur.add(j) == b'_'
2889                    || *cur.add(j) == b'-'
2890                    || *cur.add(j) == b'.'
2891                    || *cur.add(j) == b':')
2892            {
2893                j += 1;
2894            }
2895            if j < n && *cur.add(j) == b';' {
2896                let name = core::slice::from_raw_parts(cur.add(i + 1), j - i - 1);
2897                let mut replaced = false;
2898                // Predefined entities.
2899                let content: Option<&[u8]> = match name {
2900                    b"amp" => Some(b"&"),
2901                    b"lt" => Some(b"<"),
2902                    b"gt" => Some(b">"),
2903                    b"quot" => Some(b"\""),
2904                    b"apos" => Some(b"'"),
2905                    _ => None,
2906                };
2907                if let Some(c) = content {
2908                    out.extend_from_slice(c);
2909                    replaced = true;
2910                } else {
2911                    // DTD-declared entity.
2912                    let mut name_nul = name.to_vec();
2913                    name_nul.push(0);
2914                    let ent = entities::get_entity((*ctxt).myDoc, name_nul.as_ptr());
2915                    if !ent.is_null() && !(*ent).content.is_null() {
2916                        let clen = string::xml_strlen((*ent).content);
2917                        out.extend_from_slice(core::slice::from_raw_parts(
2918                            (*ent).content,
2919                            clen,
2920                        ));
2921                        replaced = true;
2922                    }
2923                }
2924                if replaced {
2925                    i = j + 1;
2926                    continue;
2927                }
2928            }
2929            out.push(b'&');
2930            i += 1;
2931        }
2932
2933        out.push(0);
2934        let result = xmlMalloc(out.len()) as *mut xmlChar;
2935        if result.is_null() {
2936            return ptr::null_mut();
2937        }
2938        ptr::copy_nonoverlapping(out.as_ptr(), result, out.len());
2939        result
2940    }
2941}
2942
2943/// Parse a numeric character reference at `ptr` (starting at '&#'); returns
2944/// the value and total bytes consumed, or (0, 0) when malformed.
2945unsafe fn parse_char_ref(ptr: *const u8, avail: usize) -> (c_int, usize) {
2946    unsafe {
2947        if avail < 3 || *ptr != b'&' || *ptr.add(1) != b'#' {
2948            return (0, 0);
2949        }
2950        let mut i = 2usize;
2951        let hex = i < avail && (*ptr.add(i) == b'x' || *ptr.add(i) == b'X');
2952        if hex {
2953            i += 1;
2954        }
2955        let start = i;
2956        let mut value: u32 = 0;
2957        while i < avail && *ptr.add(i) != b';' {
2958            let d = (*ptr.add(i) as char).to_digit(if hex { 16 } else { 10 });
2959            match d {
2960                Some(d) => {
2961                    value = value
2962                        .saturating_mul(if hex { 16 } else { 10 })
2963                        .saturating_add(d);
2964                    i += 1;
2965                }
2966                None => return (0, 0),
2967            }
2968        }
2969        if i == start || i >= avail || *ptr.add(i) != b';' {
2970            return (0, 0);
2971        }
2972        (value as c_int, i + 1)
2973    }
2974}
2975
2976/// Encode a codepoint into a UTF-8 byte sequence; returns the byte count.
2977fn copy_char_utf8(out: &mut [u8; 4], val: c_int) -> usize {
2978    if val < 0x80 {
2979        out[0] = val as u8;
2980        1
2981    } else if val < 0x800 {
2982        out[0] = 0xC0 | ((val >> 6) as u8);
2983        out[1] = 0x80 | ((val & 0x3F) as u8);
2984        2
2985    } else if val < 0x10000 {
2986        out[0] = 0xE0 | ((val >> 12) as u8);
2987        out[1] = 0x80 | (((val >> 6) & 0x3F) as u8);
2988        out[2] = 0x80 | ((val & 0x3F) as u8);
2989        3
2990    } else if val < 0x110000 {
2991        out[0] = 0xF0 | ((val >> 18) as u8);
2992        out[1] = 0x80 | (((val >> 12) & 0x3F) as u8);
2993        out[2] = 0x80 | (((val >> 6) & 0x3F) as u8);
2994        out[3] = 0x80 | ((val & 0x3F) as u8);
2995        4
2996    } else {
2997        out[0] = 0;
2998        1
2999    }
3000}
3001
3002/// Detect the character encoding of a buffer from its initial bytes.
3003///
3004/// # UPSTREAM-PARITY
3005///
3006/// ```c
3007/// xmlCharEncoding xmlDetectCharEncoding(const unsigned char *in, int len);
3008/// ```
3009#[no_mangle]
3010pub unsafe extern "C" fn xmlDetectCharEncoding(in_: *const c_uchar, len: c_int) -> c_int {
3011    if in_.is_null() {
3012        return xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int;
3013    }
3014    unsafe {
3015        if len >= 4 {
3016            if *in_ == 0x00 && *in_.add(1) == 0x00 && *in_.add(2) == 0x00 && *in_.add(3) == 0x3C {
3017                return xmlCharEncoding::XML_CHAR_ENCODING_UCS4BE as c_int;
3018            }
3019            if *in_ == 0x3C && *in_.add(1) == 0x00 && *in_.add(2) == 0x00 && *in_.add(3) == 0x00 {
3020                return xmlCharEncoding::XML_CHAR_ENCODING_UCS4LE as c_int;
3021            }
3022            if *in_ == 0x4C && *in_.add(1) == 0x6F && *in_.add(2) == 0xA7 && *in_.add(3) == 0x94 {
3023                return xmlCharEncoding::XML_CHAR_ENCODING_EBCDIC as c_int;
3024            }
3025            if *in_ == 0x3C && *in_.add(1) == 0x3F && *in_.add(2) == 0x78 && *in_.add(3) == 0x6D {
3026                return xmlCharEncoding::XML_CHAR_ENCODING_UTF8 as c_int;
3027            }
3028            if *in_ == 0x3C && *in_.add(1) == 0x00 && *in_.add(2) == 0x3F && *in_.add(3) == 0x00 {
3029                return xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE as c_int;
3030            }
3031            if *in_ == 0x00 && *in_.add(1) == 0x3C && *in_.add(2) == 0x00 && *in_.add(3) == 0x3F {
3032                return xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE as c_int;
3033            }
3034        }
3035        if len >= 3 && *in_ == 0xEF && *in_.add(1) == 0xBB && *in_.add(2) == 0xBF {
3036            return xmlCharEncoding::XML_CHAR_ENCODING_UTF8 as c_int;
3037        }
3038        if len >= 2 {
3039            if *in_ == 0xFE && *in_.add(1) == 0xFF {
3040                return xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE as c_int;
3041            }
3042            if *in_ == 0xFF && *in_.add(1) == 0xFE {
3043                return xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE as c_int;
3044            }
3045        }
3046    }
3047    xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int
3048}
3049
3050/// Convert the first line of `in` using the encoding handler, appending the
3051/// result to `out`.
3052///
3053/// # UPSTREAM-PARITY
3054///
3055/// ```c
3056/// int xmlCharEncFirstLine(xmlCharEncodingHandlerPtr handler,
3057///                         struct _xmlBuffer *out, struct _xmlBuffer *in);
3058/// ```
3059#[no_mangle]
3060pub unsafe extern "C" fn xmlCharEncFirstLine(
3061    handler: *mut _xmlCharEncodingHandler,
3062    out: *mut _xmlBuffer,
3063    in_: *mut _xmlBuffer,
3064) -> c_int {
3065    encoding::xmlCharEncInFunc(handler, out, in_)
3066}
3067
3068/// Check whether the current thread is the main thread.
3069///
3070/// # UPSTREAM-PARITY
3071///
3072/// ```c
3073/// int xmlIsMainThread(void);
3074/// ```
3075#[no_mangle]
3076pub unsafe extern "C" fn xmlIsMainThread() -> c_int {
3077    1
3078}
3079
3080// ═══════════════════════════════════════════════════════════════════════════════
3081// Error reporting helpers (xmlerror.h)
3082// ═══════════════════════════════════════════════════════════════════════════════
3083
3084/// Print file and line information for a parser input to the generic error
3085/// channel.
3086///
3087/// # UPSTREAM-PARITY
3088///
3089/// ```c
3090/// void xmlParserPrintFileInfo(struct _xmlParserInput *input);
3091/// ```
3092#[no_mangle]
3093pub unsafe extern "C" fn xmlParserPrintFileInfo(input: *mut _xmlParserInput) {
3094    if input.is_null() {
3095        return;
3096    }
3097    unsafe {
3098        let channel: Option<xmlGenericErrorFunc> = globals::get_generic_error_func();
3099        let data = globals::get_generic_error_ctx();
3100        let Some(ch) = channel else { return };
3101        let msg;
3102        if !(*input).filename.is_null() {
3103            let file = CStr::from_ptr((*input).filename);
3104            let s = format!("{}:{}: ", file.to_string_lossy(), (*input).line);
3105            msg = std::ffi::CString::new(s).unwrap_or_default();
3106        } else {
3107            let s = format!("Entity: line {}: ", (*input).line);
3108            msg = std::ffi::CString::new(s).unwrap_or_default();
3109        }
3110        ch(data, msg.as_ptr());
3111    }
3112}
3113
3114/// Print the input context around the current error position to the generic
3115/// error channel.
3116///
3117/// # UPSTREAM-PARITY
3118///
3119/// ```c
3120/// void xmlParserPrintFileContext(struct _xmlParserInput *input);
3121/// ```
3122#[no_mangle]
3123pub unsafe extern "C" fn xmlParserPrintFileContext(input: *mut _xmlParserInput) {
3124    if input.is_null() || (*input).cur.is_null() {
3125        return;
3126    }
3127    unsafe {
3128        let channel: Option<xmlGenericErrorFunc> = globals::get_generic_error_func();
3129        let data = globals::get_generic_error_ctx();
3130        let Some(ch) = channel else { return };
3131
3132        let pi = &*input;
3133        let cur = pi.cur;
3134        let base = pi.base;
3135        let end = pi.end;
3136
3137        // Build a window of up to 80 bytes ending at `cur`.
3138        let before = if base.is_null() {
3139            0
3140        } else {
3141            (cur as usize).saturating_sub(base as usize)
3142        };
3143        let take = before.min(LINE_LEN);
3144        let start = cur.sub(take);
3145        let n = (end as usize).saturating_sub(start as usize).min(LINE_LEN);
3146
3147        let mut content = vec![0u8; n];
3148        if n > 0 {
3149            ptr::copy_nonoverlapping(start, content.as_mut_ptr(), n);
3150        }
3151        let line = std::ffi::CString::new(content.clone()).unwrap_or_default();
3152        ch(data, line.as_ptr());
3153
3154        // Caret line pointing at the current character.
3155        let mut caret = vec![b' '; take];
3156        if take + 1 <= LINE_LEN + 1 {
3157            caret.push(b'^');
3158        }
3159        let caret_c = std::ffi::CString::new(caret).unwrap_or_default();
3160        ch(data, caret_c.as_ptr());
3161    }
3162}
3163
3164// ═══════════════════════════════════════════════════════════════════════════════
3165// SAX/DTD parse front-ends
3166// ═══════════════════════════════════════════════════════════════════════════════
3167
3168/// Handle an entity reference by pushing the entity's content as a new input
3169/// stream (deprecated internal API).
3170///
3171/// # UPSTREAM-PARITY
3172///
3173/// ```c
3174/// void xmlHandleEntity(xmlParserCtxtPtr ctxt, xmlEntityPtr entity);
3175/// ```
3176#[no_mangle]
3177pub unsafe extern "C" fn xmlHandleEntity(ctxt: *mut _xmlParserCtxt, entity: *mut c_void) {
3178    if ctxt.is_null() {
3179        return;
3180    }
3181    unsafe {
3182        let ent = entity as *mut _xmlEntity;
3183        if ent.is_null() {
3184            return;
3185        }
3186        // Unparsed entities cannot be included by reference.
3187        if (*ent).etype == xmlEntityType::XML_EXTERNAL_GENERAL_UNPARSED_ENTITY as c_int {
3188            return;
3189        }
3190
3191        let mut input = ptr::null_mut();
3192        if !(*ent).content.is_null() {
3193            // Internal entity: push its replacement text as a new stream.
3194            let content = (*ent).content;
3195            let pi = xmlNewInputStream(ctxt);
3196            if pi.is_null() {
3197                return;
3198            }
3199            let len = string::xml_strlen(content);
3200            (*pi).base = content;
3201            (*pi).cur = content;
3202            (*pi).end = content.add(len);
3203            (*pi).length = len as c_int;
3204            (*pi).entity = ent;
3205            input = pi;
3206        } else if !(*ent).URI.is_null() {
3207            // External parsed entity: load it through the entity loader.
3208            input = xmlLoadExternalEntity(
3209                (*ent).URI as *const c_char,
3210                (*ent).ExternalID as *const c_char,
3211                ctxt,
3212            );
3213            if !input.is_null() {
3214                (*input).entity = ent;
3215            }
3216        }
3217
3218        if input.is_null() {
3219            return;
3220        }
3221        xmlPushInput(ctxt, input);
3222    }
3223}
3224
3225/// Load and parse a DTD, returning the resulting `xmlDtd` (detached from any
3226/// document).
3227///
3228/// # UPSTREAM-PARITY
3229///
3230/// ```c
3231/// xmlDtdPtr xmlSAXParseDTD(xmlSAXHandlerPtr sax, const xmlChar *publicId,
3232///                          const xmlChar *systemId);
3233/// ```
3234#[no_mangle]
3235pub unsafe extern "C" fn xmlSAXParseDTD(
3236    sax: *mut _xmlSAXHandler,
3237    publicId: *const xmlChar,
3238    systemId: *const xmlChar,
3239) -> *mut _xmlDtd {
3240    if publicId.is_null() && systemId.is_null() {
3241        return ptr::null_mut();
3242    }
3243    unsafe {
3244        let ctxt = xmlNewSAXParserCtxt(sax, ptr::null_mut());
3245        if ctxt.is_null() {
3246            return ptr::null_mut();
3247        }
3248        apply_options(ctxt, XML_PARSE_DTDLOAD);
3249
3250        // Resolve via the SAX resolveEntity callback when available, else
3251        // load the system ID directly.
3252        let mut input = ptr::null_mut();
3253        if !sax.is_null() {
3254            if let Some(resolve) = (*sax).resolveEntity {
3255                input = resolve((*ctxt).userData, publicId, systemId);
3256            }
3257        }
3258        if input.is_null() {
3259            if systemId.is_null() {
3260                helpers::free_parser_ctxt(ctxt);
3261                return ptr::null_mut();
3262            }
3263            input = xmlLoadExternalEntity(systemId as *const c_char, ptr::null(), ctxt);
3264        }
3265        if input.is_null() {
3266            helpers::free_parser_ctxt(ctxt);
3267            return ptr::null_mut();
3268        }
3269
3270        // Materialise the DTD text before freeing the input struct.
3271        let data: Vec<u8> = {
3272            let pi = &*input;
3273            if !pi.base.is_null() && !pi.end.is_null() && pi.end >= pi.base {
3274                let len = (pi.end as usize).saturating_sub(pi.base as usize);
3275                core::slice::from_raw_parts(pi.base, len).to_vec()
3276            } else if !pi.buf.is_null() {
3277                input_buffer_data(pi.buf)
3278            } else {
3279                Vec::new()
3280            }
3281        };
3282        helpers::free_parser_input(input);
3283
3284        let dtd = parse_dtd_text(ctxt, &data, publicId, systemId);
3285        helpers::free_parser_ctxt(ctxt);
3286        dtd
3287    }
3288}
3289
3290/// Load and parse a DTD from an input buffer.
3291///
3292/// # UPSTREAM-PARITY
3293///
3294/// ```c
3295/// xmlDtdPtr xmlIOParseDTD(xmlSAXHandlerPtr sax, xmlParserInputBufferPtr input,
3296///                         xmlCharEncoding enc);
3297/// ```
3298#[no_mangle]
3299pub unsafe extern "C" fn xmlIOParseDTD(
3300    sax: *mut _xmlSAXHandler,
3301    input: *mut _xmlParserInputBuffer,
3302    enc: c_int,
3303) -> *mut _xmlDtd {
3304    if input.is_null() {
3305        return ptr::null_mut();
3306    }
3307    unsafe {
3308        let ctxt = xmlNewSAXParserCtxt(sax, ptr::null_mut());
3309        if ctxt.is_null() {
3310            io::input_buffer_free(input);
3311            return ptr::null_mut();
3312        }
3313        apply_options(ctxt, XML_PARSE_DTDLOAD);
3314        if enc != xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int {
3315            (*ctxt).charset = enc;
3316        }
3317
3318        // Materialise the data from the input buffer.
3319        let data: Vec<u8> = input_buffer_data(input);
3320        io::input_buffer_free(input);
3321
3322        let dtd = parse_dtd_text(ctxt, &data, ptr::null(), ptr::null());
3323        helpers::free_parser_ctxt(ctxt);
3324        dtd
3325    }
3326}
3327
3328/// Extract the buffered data of an input buffer as an owned byte vector.
3329unsafe fn input_buffer_data(buf: *mut _xmlParserInputBuffer) -> Vec<u8> {
3330    unsafe {
3331        if buf.is_null() {
3332            return Vec::new();
3333        }
3334        let b = &*buf;
3335        if let Some(read) = b.readcallback {
3336            let mut out = Vec::new();
3337            let mut tmp = [0u8; 4096];
3338            loop {
3339                let n = read(b.context, tmp.as_mut_ptr() as *mut c_char, tmp.len() as c_int);
3340                if n <= 0 {
3341                    break;
3342                }
3343                out.extend_from_slice(&tmp[..n as usize]);
3344            }
3345            return out;
3346        }
3347        if !b.buffer.is_null() {
3348            let xbuf = &*(b.buffer as *mut _xmlBuffer);
3349            if !xbuf.content.is_null() && xbuf.use_ > 0 {
3350                return core::slice::from_raw_parts(xbuf.content, xbuf.use_ as usize).to_vec();
3351            }
3352        }
3353        Vec::new()
3354    }
3355}
3356
3357/// Parse an external general entity and build a tree.
3358///
3359/// # UPSTREAM-PARITY
3360///
3361/// ```c
3362/// xmlDocPtr xmlSAXParseEntity(xmlSAXHandlerPtr sax, const char *filename);
3363/// ```
3364#[no_mangle]
3365pub unsafe extern "C" fn xmlSAXParseEntity(
3366    sax: *mut _xmlSAXHandler,
3367    filename: *const c_char,
3368) -> *mut _xmlDoc {
3369    if filename.is_null() {
3370        return ptr::null_mut();
3371    }
3372    unsafe {
3373        let ctxt = xmlNewSAXParserCtxt(sax, ptr::null_mut());
3374        if ctxt.is_null() {
3375            return ptr::null_mut();
3376        }
3377        let input = match helpers::input_from_file(filename) {
3378            Ok(i) => i,
3379            Err(_) => {
3380                helpers::free_parser_ctxt(ctxt);
3381                return ptr::null_mut();
3382            }
3383        };
3384        helpers::setup_parser_input(ctxt, input);
3385        let rc = helpers::parse_document(ctxt);
3386        let doc = (*ctxt).myDoc;
3387        (*ctxt).myDoc = ptr::null_mut();
3388        if rc != 0 || (*ctxt).wellFormed == 0 {
3389            if !doc.is_null() {
3390                tree::free_doc(doc);
3391            }
3392            helpers::free_parser_ctxt(ctxt);
3393            return ptr::null_mut();
3394        }
3395        helpers::free_parser_ctxt(ctxt);
3396        doc
3397    }
3398}
3399
3400// ═══════════════════════════════════════════════════════════════════════════════
3401// C14N: xmlC14NDocSave
3402// ═══════════════════════════════════════════════════════════════════════════════
3403
3404/// Canonicalise a document (or node set) and save it to a file.
3405///
3406/// # UPSTREAM-PARITY
3407///
3408/// ```c
3409/// int xmlC14NDocSave(xmlDocPtr doc, xmlNodeSetPtr nodes, int mode,
3410///                    xmlChar **inclusive_ns_prefixes, int with_comments,
3411///                    const char *filename, int compression);
3412/// ```
3413#[no_mangle]
3414pub unsafe extern "C" fn xmlC14NDocSave(
3415    doc: *mut _xmlDoc,
3416    nodes: *mut _xmlNodeSet,
3417    mode: c_int,
3418    inclusive_ns_prefixes: *mut *mut xmlChar,
3419    with_comments: c_int,
3420    filename: *const c_char,
3421    compression: c_int,
3422) -> c_int {
3423    if filename.is_null() {
3424        return -1;
3425    }
3426    unsafe {
3427        let output = io::output_buffer_create_filename(filename, ptr::null_mut(), compression);
3428        if output.is_null() {
3429            return -1;
3430        }
3431        let node_tab = if nodes.is_null() {
3432            ptr::null_mut()
3433        } else {
3434            (*nodes).nodeTab
3435        };
3436        let ret = crate::xml::c14n::xmlC14NDocSaveTo(
3437            doc,
3438            node_tab,
3439            mode,
3440            inclusive_ns_prefixes,
3441            with_comments,
3442            output,
3443        );
3444        if ret < 0 {
3445            io::output_buffer_close(output);
3446            return -1;
3447        }
3448        let close_ret = io::output_buffer_close(output);
3449        if close_ret < 0 {
3450            -1
3451        } else {
3452            ret
3453        }
3454    }
3455}