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