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