libxml_rs/xml/reader/mod.rs
1//! XML Reader API (§30, §85 Phase 7).
2//!
3//! Cursor-based streaming reader with node type, depth, attribute traversal,
4//! namespace lookup, value retrieval, validation integration.
5//!
6//! Implements the `xmlTextReader` API from libxml2, which provides a
7//! cursor-based streaming interface for reading XML documents. The reader
8//! parses the entire document into a tree on the first `Read()` call, then
9//! walks the tree in document order (depth-first traversal) generating
10//! node events for elements, text, comments, PIs, etc.
11//!
12//! # UPSTREAM-PARITY
13//!
14//! The reader API is defined in `libxml/xmlreader.h` and `libxml/xmlreader.c`.
15//! Key differences from upstream:
16//!
17//! - The reader parses the full document on first Read rather than using
18//! a true streaming/event-driven parser. This simplifies the implementation
19//! while preserving the observable API surface.
20//! - Pattern-based reader operations (xmlTextReaderPreservePattern, etc.)
21//! are not yet implemented.
22
23#![allow(
24 missing_docs,
25 non_snake_case,
26 non_camel_case_types,
27 non_upper_case_globals
28)]
29
30#[cfg(test)]
31use crate::xml::string::xmlstr_to_string;
32
33use core::ffi::c_void;
34use core::ptr;
35use std::os::raw::{c_char, c_int, c_long, c_uint};
36
37use crate::abi::allocator::xmlFreeImpl;
38use crate::abi::callbacks::{xmlInputCloseCallback, xmlInputReadCallback};
39use crate::abi::structs::{_xmlAttr, _xmlDoc, _xmlNode, _xmlParserCtxt, _xmlParserInputBuffer};
40
41use crate::abi::types::xmlElementType::*;
42use crate::abi::types::*;
43use crate::xml::parser::helpers::{
44 create_parser_ctxt, free_parser_ctxt, input_from_file, input_from_io, input_from_memory,
45 input_from_memory_named, parse_document, setup_parser_input, take_stashed_input_buffer,
46};
47use crate::xml::parser::input::{InputBuffer, InputStack};
48use crate::xml::string::{bytes_to_xmlstr, xml_strdup, xmlstr_to_bytes};
49use crate::xml::tree;
50use std::collections::HashSet;
51
52// ═══════════════════════════════════════════════════════════════════════════════
53// Reader Types (xmlreader.h)
54// ═══════════════════════════════════════════════════════════════════════════════
55
56/// Reader node types (xmlReaderTypes enum).
57///
58/// # UPSTREAM-PARITY
59///
60/// ```c
61/// typedef enum {
62/// XML_TEXTREADER_NONE = 0,
63/// XML_TEXTREADER_ELEMENT = 1,
64/// XML_TEXTREADER_ATTRIBUTE = 2,
65/// XML_TEXTREADER_TEXT = 3,
66/// XML_TEXTREADER_CDATA = 4,
67/// XML_TEXTREADER_ENTITY_REFERENCE = 5,
68/// XML_TEXTREADER_ENTITY = 6,
69/// XML_TEXTREADER_PROCESSING_INSTRUCTION = 7,
70/// XML_TEXTREADER_COMMENT = 8,
71/// XML_TEXTREADER_DOCUMENT = 9,
72/// XML_TEXTREADER_DOCUMENT_TYPE = 10,
73/// XML_TEXTREADER_DOCUMENT_FRAGMENT = 11,
74/// XML_TEXTREADER_NOTATION = 12,
75/// XML_TEXTREADER_WHITESPACE = 13,
76/// XML_TEXTREADER_SIGNIFICANT_WHITESPACE = 14,
77/// XML_TEXTREADER_END_ELEMENT = 15,
78/// XML_TEXTREADER_END_ENTITY = 16,
79/// XML_TEXTREADER_XML_DECLARATION = 17
80/// } xmlReaderTypes;
81/// ```
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83#[repr(i32)]
84pub enum ReaderNodeType {
85 NONE = 0,
86 ELEMENT = 1,
87 ATTRIBUTE = 2,
88 TEXT = 3,
89 CDATA = 4,
90 ENTITY_REFERENCE = 5,
91 ENTITY = 6,
92 PROCESSING_INSTRUCTION = 7,
93 COMMENT = 8,
94 DOCUMENT = 9,
95 DOCUMENT_TYPE = 10,
96 DOCUMENT_FRAGMENT = 11,
97 NOTATION = 12,
98 WHITESPACE = 13,
99 SIGNIFICANT_WHITESPACE = 14,
100 END_ELEMENT = 15,
101 END_ENTITY = 16,
102 XML_DECLARATION = 17,
103 NAMESPACE = 18,
104}
105
106/// Reader read state (xmlTextReaderReadState enum).
107///
108/// # UPSTREAM-PARITY
109///
110/// ```c
111/// typedef enum {
112/// XML_TEXTREADER_NOT_INITIALIZED = 0,
113/// XML_TEXTREADER_INITIALIZED = 1,
114/// XML_TEXTREADER_READING = 2,
115/// XML_TEXTREADER_EOF = 3,
116/// XML_TEXTREADER_CLOSED = 4,
117/// XML_TEXTREADER_ERROR = 5
118/// } xmlTextReaderReadState;
119/// ```
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121#[repr(i32)]
122pub enum ReadState {
123 NOT_INITIALIZED = 0,
124 INITIALIZED = 1,
125 READING = 2,
126 EOF = 3,
127 CLOSED = 4,
128 ERROR = 5,
129}
130
131/// Parser properties for xmlTextReaderGetParserProp / SetParserProp.
132///
133/// # UPSTREAM-PARITY
134///
135/// ```c
136/// typedef enum {
137/// XML_PARSER_LOADDTD = 1,
138/// XML_PARSER_DEFAULTATTRS = 2,
139/// XML_PARSER_VALIDATE = 3,
140/// XML_PARSER_SUBST_ENTITIES = 4
141/// } xmlParserProperties;
142/// ```
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144#[repr(i32)]
145#[allow(dead_code)]
146pub(crate) enum ParserProp {
147 LOADDTD = 1,
148 DEFAULTATTRS = 2,
149 VALIDATE = 3,
150 SUBST_ENTITIES = 4,
151}
152
153/// A traversal event in the document-order walk of the parsed tree.
154///
155/// Each event represents either entering a node (ELEMENT, TEXT, etc.) or
156/// exiting an element (END_ELEMENT). The `depth` is the element nesting
157/// depth at the time of the event.
158#[derive(Debug, Clone)]
159struct TraversalEvent {
160 /// The node this event refers to.
161 node: *mut _xmlNode,
162 /// Whether this is an "exit" event (END_ELEMENT).
163 is_end: bool,
164 /// The depth at this event (number of ancestor elements).
165 depth: i32,
166}
167
168/// Compute the element nesting depth of a node in the tree.
169///
170/// Counts the number of `XML_ELEMENT_NODE` ancestors.
171///
172/// # Safety
173///
174/// `node` must be a valid pointer to a node in a valid tree, or NULL.
175#[allow(dead_code)]
176unsafe fn compute_depth(node: *mut _xmlNode) -> i32 {
177 if node.is_null() {
178 return 0;
179 }
180 let mut depth: i32 = 0;
181 // SAFETY: node is valid, and parent pointers form a tree.
182 let mut cur = unsafe { (*node).parent };
183 while !cur.is_null() {
184 // SAFETY: cur is valid.
185 if unsafe { (*cur).type_ } == XML_ELEMENT_NODE as c_int {
186 depth += 1;
187 }
188 // SAFETY: cur's parent is valid.
189 cur = unsafe { (*cur).parent };
190 }
191 depth
192}
193
194/// Convert an `xmlElementType` to the corresponding `ReaderNodeType`.
195const fn element_type_to_reader_type(etype: c_int) -> ReaderNodeType {
196 match etype {
197 x if x == XML_ELEMENT_NODE as c_int => ReaderNodeType::ELEMENT,
198 x if x == XML_ATTRIBUTE_NODE as c_int => ReaderNodeType::ATTRIBUTE,
199 x if x == XML_TEXT_NODE as c_int => ReaderNodeType::TEXT,
200 x if x == XML_CDATA_SECTION_NODE as c_int => ReaderNodeType::CDATA,
201 x if x == XML_ENTITY_REF_NODE as c_int => ReaderNodeType::ENTITY_REFERENCE,
202 x if x == XML_ENTITY_NODE as c_int => ReaderNodeType::ENTITY,
203 x if x == XML_PI_NODE as c_int => ReaderNodeType::PROCESSING_INSTRUCTION,
204 x if x == XML_COMMENT_NODE as c_int => ReaderNodeType::COMMENT,
205 x if x == XML_DOCUMENT_NODE as c_int => ReaderNodeType::DOCUMENT,
206 x if x == XML_DOCUMENT_TYPE_NODE as c_int => ReaderNodeType::DOCUMENT_TYPE,
207 x if x == XML_DOCUMENT_FRAG_NODE as c_int => ReaderNodeType::DOCUMENT_FRAGMENT,
208 x if x == XML_NOTATION_NODE as c_int => ReaderNodeType::NOTATION,
209 x if x == XML_DTD_NODE as c_int => ReaderNodeType::DOCUMENT_TYPE,
210 x if x == XML_NAMESPACE_DECL as c_int => ReaderNodeType::NONE,
211 _ => ReaderNodeType::NONE,
212 }
213}
214
215/// Check whether a text node consists entirely of whitespace.
216fn is_whitespace_only(text: &[u8]) -> bool {
217 text.iter()
218 .all(|&b| b == b' ' || b == b'\t' || b == b'\n' || b == b'\r')
219}
220
221// ═══════════════════════════════════════════════════════════════════════════════
222// XmlTextReader — Internal Rust Type
223// ═══════════════════════════════════════════════════════════════════════════════
224
225/// The internal representation of an `xmlTextReader`.
226///
227/// This struct holds all state for the reader cursor: the parsed document,
228/// the current position in the traversal, attribute navigation state, and
229/// cached information about the current node.
230/// The element the reader is currently positioned on.
231#[derive(Clone, Copy)]
232enum AttrTarget {
233 None,
234 Ns(*mut crate::abi::structs::_xmlNs),
235 Prop(*mut _xmlAttr),
236}
237
238#[derive(Debug)]
239pub struct XmlTextReader {
240 /// The parsed XML document.
241 doc: *mut _xmlDoc,
242 /// The parser context used to parse the document (NULL after parsing).
243 ctxt: *mut _xmlParserCtxt,
244 /// The traversal events computed from the parsed tree.
245 events: Vec<TraversalEvent>,
246 /// Index into `events` for the current position.
247 event_index: usize,
248 /// Current read state.
249 state: ReadState,
250 /// The current node we're positioned on.
251 cur_node: *mut _xmlNode,
252 /// Current node type (reader node type).
253 node_type: ReaderNodeType,
254 /// Current depth.
255 depth: i32,
256 /// Cached name of the current node (xmlMalloc'd, NULL if none).
257 name: *mut xmlChar,
258 /// Cached value of the current node (xmlMalloc'd, NULL if none).
259 value: *mut xmlChar,
260 /// Number of attributes on the current element (-1 if not applicable).
261 attribute_count: i32,
262 /// Current attribute index (-1 = not on an attribute).
263 cur_attribute: i32,
264 /// Parser options bitmask.
265 options: c_int,
266 /// Document encoding string (xmlMalloc'd).
267 encoding: *mut xmlChar,
268 /// Document URL (xmlMalloc'd).
269 URL: *mut xmlChar,
270 /// Collected error messages.
271 errors: Vec<String>,
272 /// Whether the document has been parsed.
273 parsed: bool,
274 /// Reader error callback (xmlTextReaderSetErrorHandler).
275 error_handler: Option<xmlTextReaderErrorFunc>,
276 /// User data for the error callback.
277 error_arg: *mut c_void,
278 /// Structured error callback (xmlTextReaderSetStructuredErrorHandler).
279 structured_handler: Option<crate::abi::callbacks::xmlStructuredErrorFunc>,
280 /// User data for the structured callback.
281 structured_arg: *mut c_void,
282 /// Cached last-error struct (xmlTextReaderGetLastError); message owned.
283 last_err: crate::abi::structs::_xmlError,
284 /// Maximum entity amplification ratio (xmlTextReaderSetMaxAmplification).
285 max_amplification: c_int,
286 /// Schema set via xmlTextReaderSetSchema.
287 schema: *mut c_void,
288 /// RELAX NG schema set via xmlTextReaderRelaxNGSetSchema.
289 rng: *mut c_void,
290 /// Whether the reader owns `schema` (file-path xmlTextReaderSchemaValidate
291 /// compiles it; xmlTextReaderSetSchema hands in a caller-owned schema).
292 schema_owned: bool,
293 /// Whether the reader owns `rng` (xmlTextReaderRelaxNGValidate compiles
294 /// it; xmlTextReaderRelaxNGSetSchema hands in a caller-owned schema that
295 /// php_xmlreader keeps and frees itself).
296 rng_owned: bool,
297 /// Last deferred XML Schema validation outcome (1 valid, 0 invalid, -1
298 /// no XSD validation attached). Upstream reader->xsdValidCtxt->valid.
299 xsd_result: c_int,
300 /// Last deferred RELAX NG validation outcome (1 valid, 0 invalid, -1 no
301 /// RELAX NG validation attached). Upstream reader->rngValidCtxt->valid.
302 rng_result: c_int,
303 /// Whether the reader owns `doc` (parse paths: yes; walker: no).
304 owns_doc: bool,
305 /// Upstream `reader->preserve`: xmlTextReaderCurrentDoc sets it, so the
306 /// document ownership transfers to the caller (xmlFreeTextReader no
307 /// longer frees it; the caller frees with xmlFreeDoc). Without this the
308 /// upstream reader3.c pattern (CurrentDoc -> xmlFreeTextReader ->
309 /// xmlDocDump -> xmlFreeDoc) double-frees.
310 preserve: bool,
311 /// Whether the current attribute position is a namespace declaration
312 /// (xmlTextReaderIsNamespaceDecl; ns-decls are exposed as attributes).
313 cur_attr_is_ns: bool,
314 /// Compiled preserve-patterns (xmlTextReaderPreservePattern; upstream
315 /// `reader->patternTab`). Applied after the parse: matched nodes and
316 /// their element ancestors survive, everything else is unlinked and
317 /// freed (upstream xmlTextReaderRead's NODE_IS_PRESERVED pruning).
318 pattern_tab: Vec<crate::abi::exports_automata::xmlPatternPtr>,
319 /// DTD parse validity snapshot: `ctxt->valid` right after the parse,
320 /// meaningful only when the parse ran with ctxt->validate == 1
321 /// (xmlTextReaderIsValid, upstream xmlreader.c).
322 was_valid: c_int,
323 /// Whether the parse ran a DTD validation pass (ctxt->validate).
324 did_validate: c_int,
325 /// Whether the first parse was pause-tolerant and ended at an INCOMPLETE
326 /// document — the available input ended inside an open construct while
327 /// the document was still well-formed. The events deliver the completed
328 /// prefix (no END_ELEMENT for elements whose end tag never arrived); the
329 /// Read() that runs past the last event runs the deferred EOF finalize,
330 /// which re-parses the accumulated input with full diagnostics so the
331 /// premature-EOF error surfaces exactly on the read that needs more
332 /// input than the source supplies — upstream xmlTextReaderPushData's
333 /// terminating xmlParseChunk (SP-14.3.1-7, fromStream_broken_stream).
334 doc_incomplete: bool,
335 /// Whether the deferred EOF finalize has run (it runs once, when the
336 /// cursor runs past the last event of an incomplete document).
337 finalized: bool,
338 /// Re-parseable copy of the accumulated input, retained while the
339 /// document is incomplete (the first parse consumed the context's
340 /// stashed buffer). Consumed by `run_eof_finalize`.
341 reparse_input: Option<InputBuffer>,
342}
343
344impl XmlTextReader {
345 /// Create a new reader with the given parser context and options.
346 ///
347 /// The reader takes ownership of the parser context. The document will be
348 /// parsed on the first call to `Read()`.
349 ///
350 /// # Safety
351 ///
352 /// `ctxt` must be a valid parser context created by `create_parser_ctxt`
353 /// and set up with input via `setup_parser_input`.
354 unsafe fn new(ctxt: *mut _xmlParserCtxt, URL: Option<&[u8]>, encoding: Option<&[u8]>) -> Self {
355 let url_ptr = URL
356 .map(|u| unsafe { bytes_to_xmlstr(u) })
357 .unwrap_or(ptr::null_mut());
358 let enc_ptr = encoding
359 .map(|e| unsafe { bytes_to_xmlstr(e) })
360 .unwrap_or(ptr::null_mut());
361
362 XmlTextReader {
363 doc: ptr::null_mut(),
364 ctxt,
365 events: Vec::new(),
366 event_index: 0,
367 state: ReadState::INITIALIZED,
368 cur_node: ptr::null_mut(),
369 node_type: ReaderNodeType::NONE,
370 depth: 0,
371 name: ptr::null_mut(),
372 value: ptr::null_mut(),
373 attribute_count: -1,
374 cur_attribute: -1,
375 options: 0,
376 encoding: enc_ptr,
377 URL: url_ptr,
378 errors: Vec::new(),
379 parsed: false,
380 error_handler: None,
381 error_arg: ptr::null_mut(),
382 structured_handler: None,
383 structured_arg: ptr::null_mut(),
384 last_err: unsafe { core::mem::zeroed() },
385 max_amplification: 0,
386 schema: ptr::null_mut(),
387 rng: ptr::null_mut(),
388 schema_owned: false,
389 rng_owned: false,
390 xsd_result: -1,
391 rng_result: -1,
392 owns_doc: true,
393 preserve: false,
394 cur_attr_is_ns: false,
395 pattern_tab: Vec::new(),
396 was_valid: 0,
397 did_validate: 0,
398 doc_incomplete: false,
399 finalized: false,
400 reparse_input: None,
401 }
402 }
403
404 /// Parse the document and build the event list.
405 ///
406 /// The parse is pause-tolerant (SP-14.3.1-7): an end of the available
407 /// input inside an open construct pauses with the completed prefix kept
408 /// (upstream's streaming reader yields events as constructs complete)
409 /// instead of failing the whole document. The document is marked
410 /// incomplete in that case and the Read() that runs past the last
411 /// delivered event runs the deferred EOF finalize, which re-parses the
412 /// accumulated input with full diagnostics — the premature-EOF error
413 /// fires only then, exactly like upstream xmlTextReaderPushData's
414 /// terminating xmlParseChunk.
415 ///
416 /// Returns 0 on success (complete document, or incomplete document whose
417 /// prefix events are ready), -1 on a definitive parse error.
418 ///
419 /// # Safety
420 ///
421 /// `ctxt` must be a valid parser context with input set up.
422 unsafe fn parse_and_build_events(&mut self) -> c_int {
423 if self.ctxt.is_null() {
424 self.state = ReadState::ERROR;
425 self.errors.push("No parser context".to_string());
426 return -1;
427 }
428
429 // Set options on the context (mirrors xmlCtxtUseOptions so the
430 // historical struct members — validate/loadsubset/recovery/etc. —
431 // reflect XML_PARSE_* options; reader2.c relies on XML_PARSE_DTDVALID
432 // reaching ctxt->validate so the parser raises the no-DTD validity
433 // error, Phase-12 EXTERNAL-CONSUMERS court).
434 unsafe {
435 crate::abi::exports_parser::apply_options(self.ctxt, self.options);
436 }
437
438 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderSetup): the reader marks
439 // its parse XML_PARSE_READER. The parser records self-closed
440 // (`<a/>`) element nodes only for reader parses (state.rs
441 // parse_element), and the validation layer routes through the
442 // streaming branch when the consumer is a reader (validation/mod.rs
443 // add_ref).
444 unsafe {
445 (*self.ctxt).parseMode = crate::abi::types::xmlParserMode::XML_PARSE_READER as c_int;
446 }
447
448 // Take the stashed input buffer (the parse consumes it). Keep a
449 // re-parseable copy so an INCOMPLETE document can be re-parsed by the
450 // deferred EOF finalize.
451 let input_buf_ptr = take_stashed_input_buffer(self.ctxt);
452 if input_buf_ptr.is_null() {
453 self.state = ReadState::ERROR;
454 self.errors.push("Failed to parse document".to_string());
455 return -1;
456 }
457 // SAFETY: the pointer was stashed by setup_parser_input via Box::into_raw.
458 let input_buf = unsafe { Box::from_raw(input_buf_ptr) };
459 let spare = input_buf.duplicate_for_reparse();
460
461 // SAFETY: input_buf moves into the stack; its data lives inside it
462 // for the duration of the parse.
463 let input_stack = InputStack::new(*input_buf);
464
465 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderPushData): the reader
466 // parses whatever input the source currently holds and yields events
467 // as constructs complete; an end of the available input inside an
468 // open construct PAUSES instead of raising "Premature end of data"
469 // (which would wrongly fail a stream whose tail has not arrived yet —
470 // fromStream_broken_stream must deliver ELEMENT root and the comment
471 // before the stream completes). The eager-partial-delivery engine
472 // (SP-14.3.1-6) provides exactly that pause; the reader parse has no
473 // external SAX consumers, so its SAX delivery is just the tree build.
474 let mut parser = unsafe {
475 crate::xml::parser::state::XmlParser::new_with_partial_resume(input_stack, self.ctxt, 0)
476 };
477 let result = parser.parse_document();
478 let paused = parser.is_paused();
479 let truncated = parser.was_truncated_abort();
480
481 // Snapshot context state before the context is freed.
482 let doc = unsafe { (*self.ctxt).myDoc };
483 self.doc = doc;
484
485 // UPSTREAM-PARITY (xmlTextReaderIsValid): snapshot the DTD-parse
486 // validity state before the parser context is released.
487 self.was_valid = unsafe { (*self.ctxt).valid };
488 self.did_validate = unsafe { (*self.ctxt).validate };
489 let well_formed = unsafe { (*self.ctxt).wellFormed };
490
491 // Elements still open when the available input ended (their END tags
492 // were not seen): no END_ELEMENT event exists for them yet — upstream
493 // emits END only once the end tag parses or the document completes at
494 // the EOF finalize. nodeTab[0..nodeNr] is the open-element chain.
495 let mut open: HashSet<usize> = HashSet::new();
496 unsafe {
497 let node_tab = (*self.ctxt).nodeTab;
498 let node_nr = (*self.ctxt).nodeNr;
499 if !node_tab.is_null() {
500 for i in 0..node_nr.max(0) as usize {
501 let n = *node_tab.add(i);
502 if !n.is_null() {
503 open.insert(n as usize);
504 }
505 }
506 }
507 }
508
509 // Free the parser context - we no longer need it.
510 if !self.ctxt.is_null() {
511 unsafe { free_parser_ctxt(self.ctxt) };
512 }
513 self.ctxt = ptr::null_mut();
514
515 // An end of the available input inside an open construct while the
516 // document is still well-formed means "more input may arrive" — the
517 // completed prefix is deliverable and the premature-EOF diagnostic is
518 // deferred (SP-14.3.1-3 keeps the same distinction for the push API).
519 let incomplete = result != 0 && well_formed != 0 && (paused || truncated);
520
521 // Classify the parse outcome:
522 // - result == 0: a complete, well-formed document.
523 // - incomplete: the input ended inside an open construct (paused at
524 // a construct boundary, or truncated mid-construct) while the
525 // document was still well-formed — deliver the completed prefix
526 // now and defer the premature-EOF diagnostic to the read that
527 // runs past the last event.
528 // - anything else: a definitive failure whose diagnostics the parse
529 // already raised (the push API keeps the same distinction between
530 // "more data expected" and "broken", SP-14.3.1-3).
531 if result == 0 && well_formed != 0 && !doc.is_null() {
532 // Complete, well-formed document.
533 self.reparse_input = None;
534 self.doc_incomplete = false;
535 // `spare` is dropped here.
536
537 // UPSTREAM-PARITY (xmlreader.c
538 // xmlTextReaderSchemaValidateInternal /
539 // xmlTextReaderRelaxNGValidateInternal): schemas attached before
540 // the first Read() are validated against the document as it
541 // streams — the whole-tree reader runs the equivalent validation
542 // right after the parse, inside this first Read() call, so
543 // validity diagnostics are raised through the global libxml error
544 // channel at read time and the outcome is recorded for
545 // xmlTextReaderIsValid.
546 unsafe {
547 self.run_deferred_validation();
548 }
549
550 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderRead NODE_IS_PRESERVED
551 // pruning): registered preserve-patterns are applied to the
552 // parsed tree before events are built — matched nodes and their
553 // element ancestors survive, everything else is unlinked and
554 // freed. Upstream prunes while streaming; the whole-tree reader
555 // collapses that to the equivalent post-parse state (Phase-12
556 // EXTERNAL-CONSUMERS court: reader3.c).
557 unsafe {
558 self.apply_pattern_preservation();
559 }
560
561 // Set the encoding from the document if not already set.
562 if self.encoding.is_null() && !doc.is_null() {
563 // SAFETY: doc is valid.
564 let doc_enc = unsafe { (*doc).encoding };
565 if !doc_enc.is_null() {
566 self.encoding = unsafe { xml_strdup(doc_enc as *const xmlChar) };
567 }
568 }
569
570 // Build traversal events from the tree.
571 self.build_events();
572
573 self.parsed = true;
574 0
575 } else if incomplete {
576 // Incomplete document: keep the prefix tree and the accumulated
577 // input for the deferred EOF finalize. Deferred schema/RNG
578 // validation and preserve-pattern pruning are skipped — both
579 // operate on a complete tree (upstream validates/prunes as the
580 // streaming parse completes nodes; a document that never
581 // completes never reports validity).
582 self.reparse_input = Some(spare);
583 self.doc_incomplete = true;
584 if !doc.is_null() {
585 // Set the encoding from the document if not already set.
586 if self.encoding.is_null() {
587 // SAFETY: doc is valid.
588 let doc_enc = unsafe { (*doc).encoding };
589 if !doc_enc.is_null() {
590 self.encoding = unsafe { xml_strdup(doc_enc as *const xmlChar) };
591 }
592 }
593 self.build_events_with(&open);
594 } else {
595 self.events.clear();
596 }
597 self.parsed = true;
598 0
599 } else {
600 // Definitive failure (diagnostics were raised by the parse).
601 // Drop the self-closed markers of the failed parse (keyed by
602 // doc — the successful path consumes them during build_events).
603 // `spare` is dropped; nothing was delivered, so no finalize can
604 // be needed.
605 if !doc.is_null() {
606 crate::xml::parser::helpers::drop_self_closed(doc);
607 }
608 self.reparse_input = None;
609 self.doc_incomplete = false;
610 self.state = ReadState::ERROR;
611 self.errors.push("Failed to parse document".to_string());
612 return -1;
613 }
614 }
615
616 /// The EOF finalize parse of an incomplete document: re-parse the
617 /// accumulated input (retained in `reparse_input`) with full diagnostics
618 /// in a fresh context — the terminating parse that raises "Premature end
619 /// of data" / "Document is empty" on the read that needs more input than
620 /// the source supplied. Returns 0 when the accumulated input is a
621 /// complete, well-formed document, -1 otherwise (the parse raised the
622 /// premature-EOF diagnostics).
623 ///
624 /// # Safety
625 ///
626 /// - `self.reparse_input` must hold the accumulated input bytes.
627 unsafe fn run_eof_finalize(&mut self) -> c_int {
628 // The finalize runs once; take (and drop) the retained buffer.
629 let Some(reparse) = self.reparse_input.take() else {
630 return -1;
631 };
632
633 // SAFETY: create_parser_ctxt returns a valid context or NULL.
634 let ctxt = unsafe { create_parser_ctxt() };
635 if ctxt.is_null() {
636 return -1;
637 }
638 // SAFETY: ctxt is valid; the buffer moves into the context stash.
639 unsafe { setup_parser_input(ctxt, reparse) };
640 // SAFETY: ctxt is valid.
641 unsafe {
642 crate::abi::exports_parser::apply_options(ctxt, self.options);
643 (*ctxt).parseMode = crate::abi::types::xmlParserMode::XML_PARSE_READER as c_int;
644 }
645 // The terminating parse runs with diagnostics enabled (plain engine):
646 // an end of input inside an open construct raises "Premature end of
647 // data in tag ..." — surfaced now, on the read that needs more
648 // input.
649 let rc = unsafe { parse_document(ctxt) };
650 let doc = unsafe { (*ctxt).myDoc };
651 let well_formed = unsafe { (*ctxt).wellFormed };
652 // SAFETY: ctxt is valid.
653 unsafe { free_parser_ctxt(ctxt) };
654 if !doc.is_null() {
655 // The finalize only needed the outcome and diagnostics — release
656 // the tree it rebuilt (the reader keeps its own prefix tree).
657 crate::xml::parser::helpers::drop_self_closed(doc);
658 // SAFETY: doc was allocated by this parse and is owned by it.
659 unsafe { tree::free_doc(doc) };
660 }
661 if rc != 0 || well_formed == 0 {
662 -1
663 } else {
664 0
665 }
666 }
667
668 /// Run the deferred EOF finalize when the cursor runs past the last
669 /// delivered event of an INCOMPLETE document. Mirrors upstream
670 /// xmlTextReaderPushData: the premature-EOF failure surfaces only on the
671 /// read that needs more input than the source can supply, and the cursor
672 /// stays on the last delivered event (fromStream_broken_stream keeps
673 /// nodeType/depth after the failing read).
674 ///
675 /// Returns true when the finalize ran AND the accumulated input is not
676 /// well-formed (the caller reports -1 with the cursor untouched); false
677 /// when nothing needed finalizing or the input is a complete,
678 /// well-formed document.
679 unsafe fn try_finalize_incomplete(&mut self) -> bool {
680 if !self.doc_incomplete || self.finalized {
681 return false;
682 }
683 self.finalized = true;
684 let ok = unsafe { self.run_eof_finalize() } == 0;
685 if !ok {
686 self.state = ReadState::ERROR;
687 true
688 } else {
689 false
690 }
691 }
692
693 /// Run the deferred XML Schema / RELAX NG validation attached through
694 /// xmlTextReaderSchemaValidate / xmlTextReaderRelaxNGValidate /
695 /// xmlTextReaderSetSchema / xmlTextReaderRelaxNGSetSchema before the
696 /// first Read(). Diagnostics are raised through the global libxml error
697 /// channel (exactly the channel upstream's streaming validation uses once
698 /// the reader hits the offending node) and the outcome is recorded for
699 /// xmlTextReaderIsValid.
700 ///
701 /// # SAFETY
702 ///
703 /// - `self.doc` must be a valid parsed document; `self.schema`/`self.rng`
704 /// must be valid compiled schemas (or NULL).
705 unsafe fn run_deferred_validation(&mut self) {
706 // XML Schema first (upstream checks the RELAX NG plug, then the XSD
707 // plug, in xmlTextReaderIsValid; a reader only carries one of them in
708 // practice).
709 if !self.schema.is_null() {
710 let (valid, errors) = unsafe {
711 crate::xml::schemas::xsd_validate_doc_compiled(self.schema, self.doc, false)
712 };
713 self.xsd_result = if valid { 1 } else { 0 };
714 for msg in errors {
715 self.raise_validation_error(msg, crate::abi::types::XML_FROM_SCHEMASV);
716 }
717 }
718 if !self.rng.is_null() {
719 let (valid, errors) =
720 unsafe { crate::xml::relaxng::rng_validate_doc_compiled(self.rng, self.doc) };
721 self.rng_result = if valid { 1 } else { 0 };
722 for msg in errors {
723 self.raise_validation_error(msg, crate::abi::types::XML_FROM_RELAXNGV);
724 }
725 }
726 }
727
728 /// Raise one validation diagnostic through the global libxml error
729 /// channel. UPSTREAM-PARITY: validity messages end with a newline — PHP's
730 /// libxml error handler only raises messages that carry a trailing
731 /// newline (php_libxml_internal_error_handler_ex), and the message text
732 /// is passed through unmodified (no domain prefix).
733 fn raise_validation_error(&self, msg: String, domain: c_int) {
734 let text = if msg.ends_with('\n') {
735 msg
736 } else {
737 format!("{}\n", msg)
738 };
739 let Ok(cmsg) = std::ffi::CString::new(text) else {
740 return;
741 };
742 // SAFETY: cmsg outlives the call; all other pointers are NULL.
743 unsafe {
744 crate::xml::errors::raise_error(
745 ptr::null_mut(),
746 ptr::null_mut(),
747 ptr::null_mut(),
748 ptr::null_mut(),
749 ptr::null_mut(),
750 domain,
751 0,
752 crate::abi::types::xmlErrorLevel::XML_ERR_ERROR as c_int,
753 ptr::null(),
754 0,
755 ptr::null(),
756 ptr::null(),
757 ptr::null(),
758 0,
759 0,
760 cmsg.as_ptr(),
761 );
762 }
763 }
764
765 /// Apply registered preserve-patterns to the parsed tree (upstream
766 /// xmlreader.c: `xmlTextReaderPreserve` marks the matched node
767 /// NODE_IS_PRESERVED | NODE_IS_SPRESERVED and its element ancestors
768 /// NODE_IS_PRESERVED; the streaming `xmlTextReaderRead` prunes every
769 /// other node as it passes). The whole-tree reader collapses that to a
770 /// post-parse pass: a node survives iff it or an element ancestor is
771 /// matched by a registered pattern (SPRESERVED subtrees are kept
772 /// whole). DTD nodes survive like upstream's special-casing.
773 ///
774 /// # SAFETY
775 ///
776 /// - `self.doc` must be a valid parsed tree (or NULL); `self.pattern_tab`
777 /// holds compiled patterns owned by the reader.
778 unsafe fn apply_pattern_preservation(&mut self) {
779 if self.pattern_tab.is_empty() || self.doc.is_null() {
780 return;
781 }
782
783 // Phase 1: collect every node matched by any pattern.
784 let mut matched: Vec<*mut _xmlNode> = Vec::new();
785 unsafe {
786 let mut stack: Vec<*mut _xmlNode> = vec![self.doc as *mut _xmlNode];
787 while let Some(n) = stack.pop() {
788 if n.is_null() {
789 continue;
790 }
791 for p in &self.pattern_tab {
792 if crate::abi::exports_automata::xmlPatternMatch(*p, n) == 1 {
793 matched.push(n);
794 break;
795 }
796 }
797 let mut c = (*n).children;
798 while !c.is_null() {
799 stack.push(c);
800 c = (*c).next;
801 }
802 }
803 }
804
805 // Phase 2: keep-set = matched nodes + their element ancestors.
806 let mut keep: HashSet<usize> = HashSet::new();
807 for m in &matched {
808 keep.insert(*m as usize);
809 let mut p = unsafe { (**m).parent };
810 while !p.is_null() {
811 keep.insert(p as usize);
812 p = unsafe { (*p).parent };
813 }
814 }
815
816 // Phase 3: prune the children of every surviving node.
817 unsafe {
818 prune_unpreserved(self.doc as *mut _xmlNode, false, &keep, &self.pattern_tab);
819 }
820 }
821
822 /// Walk the tree in document order and build traversal events.
823 ///
824 /// Generates events for all nodes (ELEMENT, TEXT, COMMENT, PI, etc.)
825 /// and END_ELEMENT events for elements.
826 fn build_events(&mut self) {
827 // SAFETY: an empty suppression set — every closed element ends.
828 let empty = HashSet::new();
829 self.build_events_with(&empty);
830 }
831
832 /// Build traversal events from the tree, suppressing the END_ELEMENT
833 /// events of the elements in `suppress_ends` (elements whose end tag was
834 /// never parsed — the parse of an incomplete document paused with them
835 /// still open; upstream only emits END once the end tag parses or the
836 /// document completes, SP-14.3.1-7).
837 fn build_events_with(&mut self, suppress_ends: &HashSet<usize>) {
838 self.events.clear();
839
840 if self.doc.is_null() {
841 return;
842 }
843
844 // SAFETY: doc is valid.
845 let root = unsafe { (*self.doc).children };
846 if root.is_null() {
847 return;
848 }
849
850 // Walk all top-level children (PIs, comments, the root element, etc.)
851 // SAFETY: The tree is valid and all pointers are valid.
852 unsafe {
853 let mut n = root;
854 while !n.is_null() {
855 self.walk_tree(n, 0, suppress_ends);
856 n = (*n).next;
857 }
858 }
859 }
860
861 /// Recursively walk a subtree and generate events.
862 ///
863 /// # Safety
864 ///
865 /// `node` must be a valid pointer to a node in the parsed tree.
866 unsafe fn walk_tree(
867 &mut self,
868 node: *mut _xmlNode,
869 depth: i32,
870 suppress_ends: &HashSet<usize>,
871 ) {
872 if node.is_null() {
873 return;
874 }
875
876 // SAFETY: node is valid.
877 let node_type = unsafe { (*node).type_ };
878
879 // For elements, generate an enter event and then recursively visit children,
880 // then generate an exit (END_ELEMENT) event — unless the element is
881 // self-closed (`<a/>`; upstream xmlreader: only an explicitly closed
882 // element fires END_ELEMENT). Elements with children always end;
883 // childless elements end iff they were NOT parsed from `<a/>`
884 // (R-000144 note: the tree alone cannot tell `<a/>` from `<a></a>`,
885 // so the parser records self-closed nodes during reader parses). An
886 // element the paused parse left OPEN (its end tag never arrived) has
887 // no END event yet either (suppress_ends).
888 if node_type == XML_ELEMENT_NODE as c_int {
889 self.events.push(TraversalEvent {
890 node,
891 is_end: false,
892 depth,
893 });
894
895 // Walk children.
896 // SAFETY: node's children are valid.
897 let mut child = unsafe { (*node).children };
898 while !child.is_null() {
899 let child_depth = depth + 1;
900 self.walk_tree(child, child_depth, suppress_ends);
901 // SAFETY: child's next pointer is valid.
902 child = unsafe { (*child).next };
903 }
904
905 // Generate END_ELEMENT for explicitly closed elements only.
906 let self_closed = crate::xml::parser::helpers::take_self_closed(self.doc, node);
907 let still_open = suppress_ends.contains(&(node as usize));
908 if !self_closed && !still_open {
909 self.events.push(TraversalEvent {
910 node,
911 is_end: true,
912 depth,
913 });
914 }
915 } else if node_type == XML_TEXT_NODE as c_int
916 || node_type == XML_CDATA_SECTION_NODE as c_int
917 || node_type == XML_COMMENT_NODE as c_int
918 || node_type == XML_PI_NODE as c_int
919 || node_type == XML_ENTITY_REF_NODE as c_int
920 {
921 // Leaf nodes: text, CDATA, comment, PI, entity reference.
922 // Whitespace-only text is emitted (as SIGNIFICANT_WHITESPACE by
923 // position_at) — upstream reader default behavior without
924 // XML_PARSE_NOBLANKS.
925 self.events.push(TraversalEvent {
926 node,
927 is_end: false,
928 depth,
929 });
930 } else {
931 // Other node types (ENTITY, NOTATION, DTD, etc.) — skip or just enter.
932 self.events.push(TraversalEvent {
933 node,
934 is_end: false,
935 depth,
936 });
937 }
938 }
939
940 /// Position the reader on the event at the given index.
941 ///
942 /// Updates all cached fields (name, value, depth, node_type, etc.).
943 fn position_at(&mut self, index: usize) {
944 if index >= self.events.len() {
945 self.state = ReadState::EOF;
946 self.cur_node = ptr::null_mut();
947 self.node_type = ReaderNodeType::NONE;
948 self.depth = 0;
949 self.clear_cached_name();
950 self.clear_cached_value();
951 self.attribute_count = -1;
952 self.cur_attribute = -1;
953 return;
954 }
955
956 // Copy event data before any mutable self access to avoid borrow conflicts.
957 let ev_node: *mut _xmlNode;
958 let ev_is_end: bool;
959 let ev_depth: i32;
960 {
961 let event = &self.events[index];
962 ev_node = event.node;
963 ev_is_end = event.is_end;
964 ev_depth = event.depth;
965 }
966
967 self.event_index = index;
968 self.cur_node = ev_node;
969 self.depth = ev_depth;
970
971 // SAFETY: node is valid.
972 let etype = unsafe { (*ev_node).type_ };
973
974 if ev_is_end {
975 self.node_type = ReaderNodeType::END_ELEMENT;
976 } else {
977 self.node_type = element_type_to_reader_type(etype);
978 // UPSTREAM-PARITY: whitespace-only text is reported as
979 // SIGNIFICANT_WHITESPACE (14) unless XML_PARSE_NOBLANKS (R-000144).
980 if etype == XML_TEXT_NODE as c_int || etype == XML_CDATA_SECTION_NODE as c_int {
981 let content = unsafe { (*ev_node).content };
982 if !content.is_null() {
983 // SAFETY: content is a valid NUL-terminated C string owned by the node.
984 let len = unsafe { libc::strlen(content as *const libc::c_char) as usize };
985 // SAFETY: content points to len valid bytes (the NUL-terminated string).
986 let slice = unsafe { core::slice::from_raw_parts(content, len) };
987 if is_whitespace_only(slice) {
988 self.node_type = ReaderNodeType::SIGNIFICANT_WHITESPACE;
989 }
990 }
991 }
992 }
993
994 // Cache name and value.
995 // SAFETY: ev_node is a valid node pointer.
996 unsafe { self.cache_name_and_value(ev_node, ev_is_end) };
997
998 // Count attributes if this is an element.
999 if etype == XML_ELEMENT_NODE as c_int && !ev_is_end {
1000 // SAFETY: ev_node is a valid element node.
1001 self.attribute_count = unsafe { self.count_attributes(ev_node) };
1002 } else {
1003 self.attribute_count = -1;
1004 }
1005
1006 // Reset attribute cursor.
1007 self.cur_attribute = -1;
1008 self.cur_attr_is_ns = false;
1009 }
1010
1011 /// Cache the name of the current node.
1012 ///
1013 /// # Safety
1014 ///
1015 /// `node` must be a valid node pointer or NULL.
1016 unsafe fn cache_name_and_value(&mut self, node: *mut _xmlNode, is_end: bool) {
1017 self.clear_cached_name();
1018 self.clear_cached_value();
1019
1020 if node.is_null() {
1021 return;
1022 }
1023
1024 // SAFETY: node is valid.
1025 let etype = unsafe { (*node).type_ };
1026
1027 // Determine name.
1028 let name: *mut xmlChar = if is_end {
1029 // For END_ELEMENT, the name is the element name.
1030 // SAFETY: node is valid.
1031 unsafe { (*node).name as *mut xmlChar }
1032 } else {
1033 if etype == XML_ELEMENT_NODE as c_int
1034 || etype == XML_PI_NODE as c_int
1035 || etype == XML_ENTITY_REF_NODE as c_int
1036 || etype == XML_ENTITY_NODE as c_int
1037 || etype == XML_DOCUMENT_TYPE_NODE as c_int
1038 || etype == XML_DTD_NODE as c_int
1039 || etype == XML_NOTATION_NODE as c_int
1040 {
1041 // SAFETY: node is valid.
1042 unsafe { (*node).name as *mut xmlChar }
1043 } else if etype == XML_ATTRIBUTE_NODE as c_int {
1044 // For attribute nodes accessed via MoveToAttribute.
1045 ptr::null_mut()
1046 } else {
1047 ptr::null_mut()
1048 }
1049 };
1050
1051 if !name.is_null() {
1052 // UPSTREAM-PARITY: xmlTextReaderName/ConstName return the
1053 // qualified name for namespaced elements (e.g. "x:child"); the
1054 // candidate rebuilds it from the node's ns prefix.
1055 let qualified: *mut xmlChar = if etype == XML_ELEMENT_NODE as c_int && !node.is_null() {
1056 let ns = unsafe { (*node).ns };
1057 if !ns.is_null() && !unsafe { (*ns).prefix }.is_null() {
1058 let plen =
1059 libc::strlen(unsafe { (*ns).prefix } as *const libc::c_char) as usize;
1060 let nlen = libc::strlen(name as *const libc::c_char) as usize;
1061 let p =
1062 crate::abi::allocator::xmlMallocImpl(plen + 1 + nlen + 1) as *mut xmlChar;
1063 if !p.is_null() {
1064 libc::memcpy(
1065 p as *mut libc::c_void,
1066 unsafe { (*ns).prefix } as *const libc::c_void,
1067 plen,
1068 );
1069 *p.add(plen) = b':';
1070 libc::memcpy(
1071 p.add(plen + 1) as *mut libc::c_void,
1072 name as *const libc::c_void,
1073 nlen,
1074 );
1075 *p.add(plen + 1 + nlen) = 0;
1076 }
1077 p
1078 } else {
1079 unsafe { xml_strdup(name as *const xmlChar) }
1080 }
1081 } else {
1082 unsafe { xml_strdup(name as *const xmlChar) }
1083 };
1084 self.name = qualified;
1085 } else if !is_end {
1086 // UPSTREAM-PARITY (xmlTextReaderConstName): typed node kinds are
1087 // reported with fixed names rather than NULL.
1088 let fixed: &[u8] = match etype {
1089 x if x == XML_TEXT_NODE as c_int => b"#text\0",
1090 x if x == XML_CDATA_SECTION_NODE as c_int => b"#cdata-section\0",
1091 x if x == XML_COMMENT_NODE as c_int => b"#comment\0",
1092 x if x == XML_DOCUMENT_NODE as c_int => b"#document\0",
1093 x if x == XML_HTML_DOCUMENT_NODE as c_int => b"#document\0",
1094 x if x == XML_DOCUMENT_FRAG_NODE as c_int => b"#document-fragment\0",
1095 _ => b"",
1096 };
1097 if !fixed.is_empty() {
1098 self.name = unsafe { xml_strdup(fixed.as_ptr() as *const xmlChar) };
1099 }
1100 }
1101
1102 // Determine value.
1103 let value: *mut xmlChar = if etype == XML_TEXT_NODE as c_int
1104 || etype == XML_CDATA_SECTION_NODE as c_int
1105 || etype == XML_COMMENT_NODE as c_int
1106 {
1107 // SAFETY: node is valid.
1108 unsafe { (*node).content }
1109 } else if etype == XML_PI_NODE as c_int {
1110 // PI nodes store content as the PI value (after the target).
1111 // SAFETY: node is valid.
1112 unsafe { (*node).content }
1113 } else if etype == XML_ENTITY_REF_NODE as c_int {
1114 // Entity references may have content.
1115 // SAFETY: node is valid.
1116 unsafe { (*node).content }
1117 } else {
1118 ptr::null_mut()
1119 };
1120
1121 if !value.is_null() {
1122 // SAFETY: value is a valid null-terminated xmlChar string.
1123 self.value = unsafe { xml_strdup(value as *const xmlChar) };
1124 }
1125 }
1126
1127 /// Count the number of attributes on an element node.
1128 ///
1129 /// # Safety
1130 ///
1131 /// `node` must be a valid element node pointer.
1132 unsafe fn count_attributes(&self, node: *mut _xmlNode) -> i32 {
1133 let mut count: i32 = 0;
1134 // UPSTREAM-PARITY: namespace declarations count as attributes
1135 // (xmlTextReaderAttributeCount includes them, xmlreader.c).
1136 let mut ns = unsafe { (*node).nsDef };
1137 while !ns.is_null() {
1138 count += 1;
1139 ns = unsafe { (*ns).next };
1140 }
1141 // SAFETY: node is a valid element.
1142 let mut prop = unsafe { (*node).properties };
1143 while !prop.is_null() {
1144 count += 1;
1145 // SAFETY: prop is valid.
1146 prop = unsafe { (*prop).next };
1147 }
1148 count
1149 }
1150
1151 /// Unified attribute addressing: namespace declarations first, then
1152 /// regular attributes (upstream reader attribute iteration, R-000143:
1153 /// xmlns / xmlns:prefix count as attributes, ordered before properties).
1154 ///
1155 /// # Safety
1156 ///
1157 /// - `node` must be non-NULL (checked) and point to a valid `_xmlNode`.
1158 /// - The `nsDef` and `properties` lists of the node must be valid
1159 /// NULL-terminated linked lists of `_xmlNs` and `_xmlAttr` objects.
1160 /// - `index` must be non-negative (checked).
1161 unsafe fn attr_at(&self, node: *mut _xmlNode, index: i32) -> AttrTarget {
1162 if node.is_null() || index < 0 {
1163 return AttrTarget::None;
1164 }
1165 let mut i = 0;
1166 let mut ns = unsafe { (*node).nsDef };
1167 while !ns.is_null() {
1168 if i == index {
1169 return AttrTarget::Ns(ns);
1170 }
1171 i += 1;
1172 ns = unsafe { (*ns).next };
1173 }
1174 let mut prop = unsafe { (*node).properties };
1175 while !prop.is_null() {
1176 if i == index {
1177 return AttrTarget::Prop(prop);
1178 }
1179 i += 1;
1180 prop = unsafe { (*prop).next };
1181 }
1182 AttrTarget::None
1183 }
1184
1185 /// The index of the attribute matching `name` (ns decls use
1186 /// "xmlns:prefix"/"xmlns" names), or -1.
1187 ///
1188 /// # Safety
1189 ///
1190 /// - `node` must be non-NULL (checked) and point to a valid `_xmlNode`.
1191 /// - `name` must be non-NULL (checked) and point to a valid
1192 /// NUL-terminated `xmlChar` string; it is scanned with `libc::strlen`.
1193 /// - The `nsDef` and `properties` lists must be valid NULL-terminated
1194 /// linked lists, and every `prefix` and `prop.name` encountered must be
1195 /// NULL or a valid NUL-terminated `xmlChar` string.
1196 unsafe fn attr_index_by_name(&self, node: *mut _xmlNode, name: *const xmlChar) -> i32 {
1197 if node.is_null() || name.is_null() {
1198 return -1;
1199 }
1200 let mut i = 0;
1201 let mut ns = unsafe { (*node).nsDef };
1202 while !ns.is_null() {
1203 let n = unsafe { &*ns };
1204 let nsname: Vec<u8> = if n.prefix.is_null() {
1205 b"xmlns\0".to_vec()
1206 } else {
1207 let mut v = b"xmlns:\0".to_vec();
1208 let plen = libc::strlen(n.prefix as *const libc::c_char) as usize;
1209 v.extend_from_slice(core::slice::from_raw_parts(n.prefix, plen));
1210 v.push(0);
1211 v
1212 };
1213 let nlen = libc::strlen(name as *const libc::c_char) as usize;
1214 let nbytes = core::slice::from_raw_parts(name, nlen);
1215 if nbytes == &nsname[..nsname.len() - 1] {
1216 return i;
1217 }
1218 i += 1;
1219 ns = unsafe { (*ns).next };
1220 }
1221 let mut prop = unsafe { (*node).properties };
1222 while !prop.is_null() {
1223 let pn = unsafe { (*prop).name };
1224 if !pn.is_null()
1225 && libc::strcmp(pn as *const libc::c_char, name as *const libc::c_char) == 0
1226 {
1227 return i;
1228 }
1229 i += 1;
1230 prop = unsafe { (*prop).next };
1231 }
1232 -1
1233 }
1234
1235 /// Free the cached name.
1236 fn clear_cached_name(&mut self) {
1237 if !self.name.is_null() {
1238 // SAFETY: name was allocated by xmlMalloc (via xml_strdup).
1239 unsafe { xmlFreeImpl(self.name as *mut c_void) };
1240 self.name = ptr::null_mut();
1241 }
1242 }
1243
1244 /// Free the cached value.
1245 fn clear_cached_value(&mut self) {
1246 if !self.value.is_null() {
1247 // SAFETY: value was allocated by xmlMalloc (via xml_strdup).
1248 unsafe { xmlFreeImpl(self.value as *mut c_void) };
1249 self.value = ptr::null_mut();
1250 }
1251 }
1252
1253 // ─────────────────────────────────────────────────────────────────────────
1254 // Navigation methods
1255 // ─────────────────────────────────────────────────────────────────────────
1256
1257 /// Read the next node in document order.
1258 ///
1259 /// Returns 1 if a node was read, 0 if no more nodes (EOF), -1 on error.
1260 ///
1261 /// # SAFETY
1262 ///
1263 /// `self` must be a valid `XmlTextReader` obtained from a reader
1264 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1265 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1266 /// The `&mut self` borrow excludes concurrent access from other threads.
1267 /// Pointers returned by this method are valid only until the next
1268 /// traversal operation invalidates them, per the C API contract.
1269 pub unsafe fn Read(&mut self) -> c_int {
1270 if self.state == ReadState::ERROR || self.state == ReadState::CLOSED {
1271 return -1;
1272 }
1273
1274 // On first call, parse the document and build events.
1275 if !self.parsed {
1276 if self.parse_and_build_events() != 0 {
1277 self.state = ReadState::ERROR;
1278 return -1;
1279 }
1280 self.state = ReadState::READING;
1281 }
1282
1283 if self.state == ReadState::EOF {
1284 return 0;
1285 }
1286
1287 // If we're positioned on an attribute, return to the element first.
1288 if self.cur_attribute >= 0 {
1289 self.cur_attribute = -1;
1290 self.cur_attr_is_ns = false;
1291 // Re-cache the element info.
1292 if !self.cur_node.is_null() {
1293 // SAFETY: cur_node is valid.
1294 unsafe { self.cache_name_and_value(self.cur_node, false) };
1295 }
1296 }
1297
1298 // Advance to the next event.
1299 // If no events, we're at EOF (an incomplete document with NO completed
1300 // prefix events — empty input, or a construct truncated from the very
1301 // start — runs the deferred EOF finalize on this same read and
1302 // reports the failure, exactly like upstream's INITIAL read whose
1303 // push parse found no node before the stream ended).
1304 if self.events.is_empty() {
1305 if unsafe { self.try_finalize_incomplete() } {
1306 return -1;
1307 }
1308 self.state = ReadState::EOF;
1309 return 0;
1310 }
1311
1312 // Determine the next event index to position on.
1313 // If cur_node is NULL, this is the first Read() after parsing —
1314 // position at event 0. On subsequent calls, advance to the next event.
1315 // We use cur_node.is_null() rather than event_index checks because
1316 // after position_at(0), event_index == 0 and state == READING, which
1317 // is indistinguishable from the pre-read state.
1318 let next_index = if self.cur_node.is_null() {
1319 // First Read() after parsing — position at event 0.
1320 0
1321 } else {
1322 self.event_index + 1
1323 };
1324
1325 if next_index < self.events.len() {
1326 self.position_at(next_index);
1327 1
1328 } else if unsafe { self.try_finalize_incomplete() } {
1329 // The cursor ran past the last event of an INCOMPLETE document:
1330 // the deferred EOF finalize re-parsed the accumulated input and
1331 // the premature-EOF error fired (upstream xmlTextReaderPushData's
1332 // terminating xmlParseChunk on the read that needs more input
1333 // than the source can supply). Report the error with the cursor
1334 // untouched — the reader still exposes the last delivered node
1335 // (fromStream_broken_stream reads depth/nodeType after the
1336 // failing read).
1337 -1
1338 } else {
1339 self.state = ReadState::EOF;
1340 self.cur_node = ptr::null_mut();
1341 self.node_type = ReaderNodeType::NONE;
1342 self.depth = 0;
1343 self.clear_cached_name();
1344 self.clear_cached_value();
1345 self.attribute_count = -1;
1346 self.cur_attribute = -1;
1347 0
1348 }
1349 }
1350
1351 /// Skip to the next sibling of the current node.
1352 ///
1353 /// Returns 1 on success, 0 if no more siblings, -1 on error.
1354 ///
1355 /// # SAFETY
1356 ///
1357 /// `self` must be a valid `XmlTextReader` obtained from a reader
1358 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1359 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1360 /// The `&mut self` borrow excludes concurrent access from other threads.
1361 /// Pointers returned by this method are valid only until the next
1362 /// traversal operation invalidates them, per the C API contract.
1363 pub unsafe fn Next(&mut self) -> c_int {
1364 if self.state == ReadState::ERROR || self.state == ReadState::CLOSED {
1365 return -1;
1366 }
1367 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderNext): with no current
1368 // node (fresh reader — first call — or after the traversal ended) a
1369 // Next degrades to a plain xmlTextReaderRead: on a fresh reader this
1370 // STARTS the traversal (php gh19098 calls next("sparql") before any
1371 // read). The subtree-skip below applies only when the reader sits on
1372 // an element start.
1373 if self.cur_node.is_null() {
1374 return self.Read();
1375 }
1376 if self.state == ReadState::EOF {
1377 return 0;
1378 }
1379
1380 // Attributes are not independent positions: an attribute cursor is
1381 // backed by the element node (upstream keeps reader->node on the
1382 // element); reset it and treat the position as the element.
1383 if self.cur_attribute >= 0 {
1384 self.cur_attribute = -1;
1385 self.cur_attr_is_ns = false;
1386 }
1387
1388 // UPSTREAM-PARITY (xmlTextReader.c xmlTextReaderNext): when the
1389 // current node is NOT an element START (text/CDATA/comment/PI/END
1390 // etc.) Next degrades to a plain xmlTextReaderRead — a single step
1391 // forward in document order that may land on the parent's
1392 // END_ELEMENT event. The subtree-skip applies only when the reader
1393 // sits on an element start.
1394 if self.node_type != ReaderNodeType::ELEMENT {
1395 let nxt = self.event_index + 1;
1396 if nxt < self.events.len() {
1397 self.position_at(nxt);
1398 return 1;
1399 }
1400 // Ran past the last event: an incomplete document runs its
1401 // deferred EOF finalize here (upstream Next pushes the parser
1402 // like Read and surfaces the premature-EOF error the same way).
1403 if unsafe { self.try_finalize_incomplete() } {
1404 return -1;
1405 }
1406 self.position_at(self.events.len());
1407 return 0;
1408 }
1409
1410 // Element start: skip the whole subtree (all deeper events and the
1411 // element's own END event plus every ancestor END event) to the next
1412 // positionable event at the same or a shallower depth. When nothing
1413 // remains the traversal ends: the cursor is cleared (EOF) exactly
1414 // like upstream's backtrack-to-document state.
1415 let current_depth = self.depth;
1416 let mut i = self.event_index + 1;
1417 while i < self.events.len() {
1418 let event = &self.events[i];
1419 if !event.is_end && event.depth <= current_depth {
1420 self.position_at(i);
1421 return 1;
1422 }
1423 i += 1;
1424 }
1425 // Same deferred-EOF-finalize handling as the read path: nothing left
1426 // to skip to, and an incomplete document reports its premature-EOF
1427 // error on this call.
1428 if unsafe { self.try_finalize_incomplete() } {
1429 return -1;
1430 }
1431 self.position_at(self.events.len());
1432 0
1433 }
1434
1435 /// Move to the parent element (if currently on an attribute).
1436 ///
1437 /// Returns 1 on success, 0 if not on an attribute, -1 on error.
1438 ///
1439 /// # SAFETY
1440 ///
1441 /// `self` must be a valid `XmlTextReader` obtained from a reader
1442 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1443 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1444 /// The `&mut self` borrow excludes concurrent access from other threads.
1445 /// Pointers returned by this method are valid only until the next
1446 /// traversal operation invalidates them, per the C API contract.
1447 pub unsafe fn MoveToElement(&mut self) -> c_int {
1448 if self.cur_attribute < 0 {
1449 return 0;
1450 }
1451 self.cur_attribute = -1;
1452 self.cur_attr_is_ns = false;
1453 if !self.cur_node.is_null() {
1454 // SAFETY: cur_node is valid.
1455 unsafe { self.cache_name_and_value(self.cur_node, false) };
1456 self.node_type = ReaderNodeType::ELEMENT;
1457 }
1458 1
1459 }
1460
1461 /// Move to an attribute by name.
1462 ///
1463 /// Returns 1 on success, 0 if attribute not found, -1 on error.
1464 ///
1465 /// # SAFETY
1466 ///
1467 /// `self` must be a valid `XmlTextReader` obtained from a reader
1468 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1469 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1470 /// The `&mut self` borrow excludes concurrent access from other threads.
1471 /// Pointers returned by this method are valid only until the next
1472 /// traversal operation invalidates them, per the C API contract.
1473 pub unsafe fn MoveToAttribute(&mut self, name: *const xmlChar) -> c_int {
1474 if self.cur_node.is_null() {
1475 return -1;
1476 }
1477
1478 // SAFETY: cur_node is valid.
1479 let etype = unsafe { (*self.cur_node).type_ };
1480 if etype != XML_ELEMENT_NODE as c_int {
1481 return -1;
1482 }
1483
1484 // SAFETY: cur_node is an element.
1485 let idx = unsafe { self.attr_index_by_name(self.cur_node, name) };
1486 if idx < 0 {
1487 return 0;
1488 }
1489 self.cur_attribute = idx;
1490 let target = unsafe { self.attr_at(self.cur_node, idx) };
1491 // Cache attribute info.
1492 unsafe { self.cache_attribute_info(target) };
1493 1
1494 }
1495
1496 /// Move to an attribute by index.
1497 ///
1498 /// Returns 1 on success, 0 if index out of range, -1 on error.
1499 ///
1500 /// # SAFETY
1501 ///
1502 /// `self` must be a valid `XmlTextReader` obtained from a reader
1503 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1504 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1505 /// The `&mut self` borrow excludes concurrent access from other threads.
1506 /// Pointers returned by this method are valid only until the next
1507 /// traversal operation invalidates them, per the C API contract.
1508 pub unsafe fn MoveToAttributeNo(&mut self, index: c_int) -> c_int {
1509 if self.cur_node.is_null() || index < 0 {
1510 return -1;
1511 }
1512
1513 // SAFETY: cur_node is valid.
1514 let etype = unsafe { (*self.cur_node).type_ };
1515 if etype != XML_ELEMENT_NODE as c_int {
1516 return -1;
1517 }
1518
1519 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderMoveToAttributeNo): the
1520 // attribute cursor is reset to the ELEMENT on entry (reader->curnode
1521 // = NULL), so a failed lookup leaves the reader on the element — the
1522 // php-visible contract (003-move-errors: "node pointer moves back to
1523 // the element in this case").
1524 self.cur_attribute = -1;
1525 self.cur_attr_is_ns = false;
1526 if !self.cur_node.is_null() {
1527 unsafe { self.cache_name_and_value(self.cur_node, false) };
1528 self.node_type = ReaderNodeType::ELEMENT;
1529 }
1530
1531 // SAFETY: cur_node is an element.
1532 let target = unsafe { self.attr_at(self.cur_node, index) };
1533 match target {
1534 AttrTarget::None => 0,
1535 t => {
1536 self.cur_attribute = index;
1537 // Cache attribute info.
1538 unsafe { self.cache_attribute_info(t) };
1539 1
1540 }
1541 }
1542 }
1543
1544 /// Move to the first attribute of the current element.
1545 ///
1546 /// Returns 1 on success, 0 if no attributes, -1 on error.
1547 ///
1548 /// # SAFETY
1549 ///
1550 /// `self` must be a valid `XmlTextReader` obtained from a reader
1551 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1552 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1553 /// The `&mut self` borrow excludes concurrent access from other threads.
1554 /// Pointers returned by this method are valid only until the next
1555 /// traversal operation invalidates them, per the C API contract.
1556 pub unsafe fn MoveToFirstAttribute(&mut self) -> c_int {
1557 if self.cur_node.is_null() {
1558 return -1;
1559 }
1560
1561 // SAFETY: cur_node is valid.
1562 let etype = unsafe { (*self.cur_node).type_ };
1563 if etype != XML_ELEMENT_NODE as c_int {
1564 return -1;
1565 }
1566
1567 // SAFETY: cur_node is an element.
1568 let first = unsafe { self.attr_at(self.cur_node, 0) };
1569 match first {
1570 AttrTarget::None => 0,
1571 t => {
1572 self.cur_attribute = 0;
1573 // Cache attribute info.
1574 unsafe { self.cache_attribute_info(t) };
1575 1
1576 }
1577 }
1578 }
1579
1580 /// Move to the next attribute.
1581 ///
1582 /// Returns 1 on success, 0 if no more attributes, -1 on error.
1583 ///
1584 /// # SAFETY
1585 ///
1586 /// `self` must be a valid `XmlTextReader` obtained from a reader
1587 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1588 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1589 /// The `&mut self` borrow excludes concurrent access from other threads.
1590 /// Pointers returned by this method are valid only until the next
1591 /// traversal operation invalidates them, per the C API contract.
1592 pub unsafe fn MoveToNextAttribute(&mut self) -> c_int {
1593 if self.cur_node.is_null() {
1594 return -1;
1595 }
1596
1597 // SAFETY: cur_node is valid.
1598 let etype = unsafe { (*self.cur_node).type_ };
1599 if etype != XML_ELEMENT_NODE as c_int {
1600 return -1;
1601 }
1602
1603 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderMoveToNextAttribute):
1604 // "if the current node is an element, this moves to its first
1605 // attribute" — a next-attribute call from an element behaves like
1606 // MoveToFirstAttribute. Only when already on an attribute does it
1607 // advance to the following one.
1608 let next_index = if self.cur_attribute < 0 {
1609 0
1610 } else {
1611 self.cur_attribute + 1
1612 };
1613 let target = unsafe { self.attr_at(self.cur_node, next_index) };
1614 match target {
1615 AttrTarget::None => 0,
1616 t => {
1617 self.cur_attribute = next_index;
1618 unsafe { self.cache_attribute_info(t) };
1619 1
1620 }
1621 }
1622 }
1623
1624 /// Cache the current position info for an attribute (or namespace
1625 /// declaration, which the reader presents as an attribute).
1626 ///
1627 /// # Safety
1628 ///
1629 /// `target` must be a valid AttrTarget::Prop(_xmlAttr) or
1630 /// AttrTarget::Ns(_xmlNs).
1631 unsafe fn cache_attribute_info(&mut self, target: AttrTarget) {
1632 self.node_type = ReaderNodeType::ATTRIBUTE;
1633 self.cur_attr_is_ns = matches!(target, AttrTarget::Ns(_));
1634 self.clear_cached_name();
1635 self.clear_cached_value();
1636 match target {
1637 AttrTarget::Ns(ns) => {
1638 // UPSTREAM-PARITY: a namespace declaration is exposed as an
1639 // attribute named "xmlns:prefix" (or "xmlns" for the default
1640 // namespace) whose value is the namespace URI.
1641 let n = unsafe { &*ns };
1642 if n.prefix.is_null() {
1643 self.name = unsafe { xml_strdup(c"xmlns".as_ptr() as *const xmlChar) };
1644 } else {
1645 let plen = libc::strlen(n.prefix as *const libc::c_char) as usize;
1646 let mut v = Vec::with_capacity(6 + plen);
1647 v.extend_from_slice(b"xmlns:");
1648 v.extend_from_slice(core::slice::from_raw_parts(n.prefix, plen));
1649 v.push(0);
1650 let p = crate::abi::allocator::xmlMallocImpl(v.len()) as *mut xmlChar;
1651 if !p.is_null() {
1652 libc::memcpy(
1653 p as *mut libc::c_void,
1654 v.as_ptr() as *const libc::c_void,
1655 v.len(),
1656 );
1657 self.name = p;
1658 }
1659 }
1660 if !n.href.is_null() {
1661 self.value = unsafe { xml_strdup(n.href as *const xmlChar) };
1662 }
1663 }
1664 AttrTarget::Prop(prop) => {
1665 // SAFETY: prop is valid.
1666 let attr = unsafe { &*prop };
1667
1668 // Name — UPSTREAM-PARITY (xmlTextReaderConstName): a
1669 // namespace-qualified attribute is reported as
1670 // "prefix:localname" (constQString), unqualified attributes
1671 // keep the local name.
1672 if !attr.name.is_null() {
1673 if !attr.ns.is_null() && !unsafe { (*attr.ns).prefix }.is_null() {
1674 let plen = libc::strlen(unsafe { (*attr.ns).prefix } as *const libc::c_char)
1675 as usize;
1676 let nlen = libc::strlen(attr.name as *const libc::c_char) as usize;
1677 let p = crate::abi::allocator::xmlMallocImpl(plen + 1 + nlen + 1)
1678 as *mut xmlChar;
1679 if !p.is_null() {
1680 libc::memcpy(
1681 p as *mut libc::c_void,
1682 unsafe { (*attr.ns).prefix } as *const libc::c_void,
1683 plen,
1684 );
1685 *p.add(plen) = b':';
1686 libc::memcpy(
1687 p.add(plen + 1) as *mut libc::c_void,
1688 attr.name as *const libc::c_void,
1689 nlen,
1690 );
1691 *p.add(plen + 1 + nlen) = 0;
1692 self.name = p;
1693 }
1694 } else {
1695 // SAFETY: attr.name is null-terminated.
1696 self.name = unsafe { xml_strdup(attr.name as *const xmlChar) };
1697 }
1698 }
1699
1700 // Value — the attribute's text content is in its child text
1701 // node. An EXISTING attribute with an empty value (`bar=""`)
1702 // has no children and reports "" (upstream xmlGetProp /
1703 // xmlNodeGetAttrValue), never NULL.
1704 if !attr.children.is_null() {
1705 // SAFETY: attr.children is a text node.
1706 let val = unsafe { (*attr.children).content };
1707 if !val.is_null() {
1708 // SAFETY: val is null-terminated.
1709 self.value = unsafe { xml_strdup(val as *const xmlChar) };
1710 } else {
1711 self.value = unsafe { xml_strdup(c"".as_ptr() as *const xmlChar) };
1712 }
1713 } else {
1714 self.value = unsafe { xml_strdup(c"".as_ptr() as *const xmlChar) };
1715 }
1716 }
1717 AttrTarget::None => {}
1718 }
1719 }
1720
1721 /// Move to the previous sibling.
1722 ///
1723 /// Returns 1 on success, 0 if no previous sibling, -1 on error.
1724 ///
1725 /// # SAFETY
1726 ///
1727 /// `self` must be a valid `XmlTextReader` obtained from a reader
1728 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1729 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1730 /// The `&mut self` borrow excludes concurrent access from other threads.
1731 /// Pointers returned by this method are valid only until the next
1732 /// traversal operation invalidates them, per the C API contract.
1733 pub unsafe fn Prev(&mut self) -> c_int {
1734 if self.state != ReadState::READING || self.cur_node.is_null() {
1735 return -1;
1736 }
1737
1738 // Scan backward through events to find the previous sibling.
1739 let current_depth = self.depth;
1740 let mut i = if self.event_index > 0 {
1741 self.event_index - 1
1742 } else {
1743 return 0;
1744 };
1745
1746 loop {
1747 let event = &self.events[i];
1748 if event.depth == current_depth && !event.is_end {
1749 self.position_at(i);
1750 return 1;
1751 }
1752 if i == 0 {
1753 break;
1754 }
1755 i -= 1;
1756 }
1757
1758 0
1759 }
1760
1761 // ─────────────────────────────────────────────────────────────────────────
1762 // Information methods
1763 // ─────────────────────────────────────────────────────────────────────────
1764
1765 /// Get the depth of the current node.
1766 pub const fn Depth(&self) -> c_int {
1767 self.depth
1768 }
1769
1770 /// Get the node type of the current node.
1771 pub const fn NodeType(&self) -> ReaderNodeType {
1772 self.node_type
1773 }
1774
1775 /// Get the name of the current node.
1776 ///
1777 /// Returns a pointer to a newly allocated string (caller must free with `xmlFree`),
1778 /// or NULL if there is no name.
1779 ///
1780 /// # SAFETY
1781 ///
1782 /// `self` must be a valid `XmlTextReader` obtained from a reader
1783 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1784 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1785 /// The `&mut self` borrow excludes concurrent access from other threads.
1786 /// Pointers returned by this method are valid only until the next
1787 /// traversal operation invalidates them, per the C API contract.
1788 pub unsafe fn Name(&self) -> *mut xmlChar {
1789 if self.name.is_null() {
1790 return ptr::null_mut();
1791 }
1792 // SAFETY: name is a valid null-terminated xmlChar string.
1793 unsafe { xml_strdup(self.name as *const xmlChar) }
1794 }
1795
1796 /// Get the value of the current node.
1797 ///
1798 /// Returns a pointer to a newly allocated string (caller must free with `xmlFree`),
1799 /// or NULL if there is no value.
1800 ///
1801 /// # SAFETY
1802 ///
1803 /// `self` must be a valid `XmlTextReader` obtained from a reader
1804 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1805 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1806 /// The `&mut self` borrow excludes concurrent access from other threads.
1807 /// Pointers returned by this method are valid only until the next
1808 /// traversal operation invalidates them, per the C API contract.
1809 pub unsafe fn Value(&self) -> *mut xmlChar {
1810 if self.value.is_null() {
1811 return ptr::null_mut();
1812 }
1813 // SAFETY: value is a valid null-terminated xmlChar string.
1814 unsafe { xml_strdup(self.value as *const xmlChar) }
1815 }
1816
1817 /// Get a constant pointer to the name (no copy).
1818 ///
1819 /// The returned pointer is valid only while the reader is alive and positioned
1820 /// on the same node.
1821 pub const fn ConstName(&self) -> *const xmlChar {
1822 self.name as *const xmlChar
1823 }
1824
1825 /// Get a constant pointer to the value (no copy).
1826 ///
1827 /// The returned pointer is valid only while the reader is alive and positioned
1828 /// on the same node.
1829 pub const fn ConstValue(&self) -> *const xmlChar {
1830 self.value as *const xmlChar
1831 }
1832
1833 /// Check if the current node has a value.
1834 pub const fn HasValue(&self) -> c_int {
1835 if self.value.is_null() {
1836 0
1837 } else {
1838 1
1839 }
1840 }
1841
1842 /// Check if the current node has attributes.
1843 pub fn HasAttributes(&self) -> c_int {
1844 if self.cur_node.is_null() {
1845 return 0;
1846 }
1847 // SAFETY: cur_node is valid.
1848 let etype = unsafe { (*self.cur_node).type_ };
1849 if etype != XML_ELEMENT_NODE as c_int {
1850 return 0;
1851 }
1852 // SAFETY: cur_node is an element.
1853 // UPSTREAM-PARITY: namespace declarations count as attributes.
1854 let props = unsafe { (*self.cur_node).properties };
1855 let nsdefs = unsafe { (*self.cur_node).nsDef };
1856 if props.is_null() && nsdefs.is_null() {
1857 0
1858 } else {
1859 1
1860 }
1861 }
1862
1863 /// Check if the current element is an empty element (no children).
1864 ///
1865 /// UPSTREAM-PARITY (xmlreader.c xmlTextReaderIsEmptyElement): with no
1866 /// current node (pre-first-Read or after EOF) this returns -1 (error),
1867 /// which the PHP consumer turns into its "no XML data has been read yet"
1868 /// property error.
1869 pub fn IsEmptyElement(&self) -> c_int {
1870 if self.cur_node.is_null() {
1871 return -1;
1872 }
1873 // SAFETY: cur_node is valid.
1874 let etype = unsafe { (*self.cur_node).type_ };
1875 if etype != XML_ELEMENT_NODE as c_int {
1876 return 0;
1877 }
1878 // SAFETY: cur_node is an element.
1879 let children = unsafe { (*self.cur_node).children };
1880 if children.is_null() {
1881 1
1882 } else {
1883 0
1884 }
1885 }
1886
1887 /// Get the base URI of the current node.
1888 ///
1889 /// Returns a newly allocated string (caller must free with `xmlFree`),
1890 /// or NULL if not available.
1891 ///
1892 /// # SAFETY
1893 ///
1894 /// `self` must be a valid `XmlTextReader` obtained from a reader
1895 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1896 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1897 /// The `&mut self` borrow excludes concurrent access from other threads.
1898 /// Pointers returned by this method are valid only until the next
1899 /// traversal operation invalidates them, per the C API contract.
1900 pub unsafe fn BaseUri(&self) -> *mut xmlChar {
1901 // The base URI is typically the document URL. UPSTREAM-PARITY
1902 // (xmlreader.c xmlTextReaderBaseUri): NULL unless a node is current.
1903 if self.cur_node.is_null() {
1904 return ptr::null_mut();
1905 }
1906 if self.doc.is_null() {
1907 return ptr::null_mut();
1908 }
1909 // SAFETY: doc is valid.
1910 let url = unsafe { (*self.doc).URL };
1911 if url.is_null() {
1912 return ptr::null_mut();
1913 }
1914 // SAFETY: url is null-terminated.
1915 unsafe { xml_strdup(url as *const xmlChar) }
1916 }
1917
1918 /// Get the local name of the current node.
1919 ///
1920 /// For namespaced names, this strips the prefix.
1921 /// Returns a newly allocated string, or NULL.
1922 ///
1923 /// # SAFETY
1924 ///
1925 /// `self` must be a valid `XmlTextReader` obtained from a reader
1926 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1927 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1928 /// The `&mut self` borrow excludes concurrent access from other threads.
1929 /// Pointers returned by this method are valid only until the next
1930 /// traversal operation invalidates them, per the C API contract.
1931 pub unsafe fn LocalName(&self) -> *mut xmlChar {
1932 if self.name.is_null() {
1933 return ptr::null_mut();
1934 }
1935
1936 // SAFETY: name is a valid null-terminated string.
1937 let name_bytes = unsafe { xmlstr_to_bytes(self.name as *const xmlChar) };
1938
1939 // Find the colon separator.
1940 if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
1941 // Return everything after the colon.
1942 let local = &name_bytes[pos + 1..];
1943 if local.is_empty() {
1944 return ptr::null_mut();
1945 }
1946 // SAFETY: bytes_to_xmlstr allocates via xmlMalloc.
1947 unsafe { bytes_to_xmlstr(local) }
1948 } else {
1949 // No prefix, return the name as-is.
1950 // SAFETY: xml_strdup allocates via xmlMalloc.
1951 unsafe { xml_strdup(self.name as *const xmlChar) }
1952 }
1953 }
1954
1955 /// Get the namespace URI of the current node.
1956 ///
1957 /// Returns a newly allocated string, or NULL.
1958 ///
1959 /// # SAFETY
1960 ///
1961 /// `self` must be a valid `XmlTextReader` obtained from a reader
1962 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1963 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1964 /// The `&mut self` borrow excludes concurrent access from other threads.
1965 /// Pointers returned by this method are valid only until the next
1966 /// traversal operation invalidates them, per the C API contract.
1967 pub unsafe fn NamespaceUri(&self) -> *mut xmlChar {
1968 if self.cur_node.is_null() {
1969 return ptr::null_mut();
1970 }
1971
1972 // SAFETY: cur_node is valid.
1973 let ns = unsafe { (*self.cur_node).ns };
1974 if ns.is_null() {
1975 return ptr::null_mut();
1976 }
1977
1978 // SAFETY: ns is valid.
1979 let href = unsafe { (*ns).href };
1980 if href.is_null() {
1981 return ptr::null_mut();
1982 }
1983
1984 // SAFETY: href is null-terminated.
1985 unsafe { xml_strdup(href as *const xmlChar) }
1986 }
1987
1988 /// Get the prefix of the current node.
1989 ///
1990 /// Returns a newly allocated string, or NULL.
1991 ///
1992 /// # SAFETY
1993 ///
1994 /// `self` must be a valid `XmlTextReader` obtained from a reader
1995 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
1996 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
1997 /// The `&mut self` borrow excludes concurrent access from other threads.
1998 /// Pointers returned by this method are valid only until the next
1999 /// traversal operation invalidates them, per the C API contract.
2000 pub unsafe fn Prefix(&self) -> *mut xmlChar {
2001 if self.cur_node.is_null() {
2002 return ptr::null_mut();
2003 }
2004
2005 // SAFETY: cur_node is valid.
2006 let ns = unsafe { (*self.cur_node).ns };
2007 if ns.is_null() {
2008 return ptr::null_mut();
2009 }
2010
2011 // SAFETY: ns is valid.
2012 let prefix = unsafe { (*ns).prefix };
2013 if prefix.is_null() {
2014 return ptr::null_mut();
2015 }
2016
2017 // SAFETY: prefix is null-terminated.
2018 unsafe { xml_strdup(prefix as *const xmlChar) }
2019 }
2020
2021 /// Get the attribute count of the current element.
2022 ///
2023 /// UPSTREAM-PARITY (xmlreader.c xmlTextReaderAttributeCount): with no
2024 /// current node (pre-first-Read or after EOF) the count is 0, never -1;
2025 /// -1 is reserved for a NULL reader. At END_ELEMENT events (and other
2026 /// non-element positions) the count is 0 as well.
2027 pub const fn AttributeCount(&self) -> c_int {
2028 if self.cur_node.is_null() {
2029 0
2030 } else if self.attribute_count < 0 {
2031 0
2032 } else {
2033 self.attribute_count
2034 }
2035 }
2036
2037 /// Get the read state.
2038 pub const fn ReadState(&self) -> ReadState {
2039 self.state
2040 }
2041
2042 /// Get an attribute value by name.
2043 ///
2044 /// Returns a newly allocated string, or NULL.
2045 ///
2046 /// # SAFETY
2047 ///
2048 /// `self` must be a valid `XmlTextReader` obtained from a reader
2049 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
2050 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
2051 /// The `&mut self` borrow excludes concurrent access from other threads.
2052 /// Pointers returned by this method are valid only until the next
2053 /// traversal operation invalidates them, per the C API contract.
2054 pub unsafe fn GetAttribute(&self, name: *const xmlChar) -> *mut xmlChar {
2055 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderGetAttribute): while the
2056 // attribute cursor is active (upstream curnode != NULL — a
2057 // moveToAttribute/moveToNextAttribute is in effect) the attribute
2058 // value accessors return NULL; they read from the ELEMENT position
2059 // only. The candidate tracks that cursor with `cur_attribute`.
2060 if self.cur_attribute >= 0 || self.cur_node.is_null() {
2061 return ptr::null_mut();
2062 }
2063
2064 // SAFETY: cur_node is valid.
2065 let etype = unsafe { (*self.cur_node).type_ };
2066 if etype != XML_ELEMENT_NODE as c_int {
2067 return ptr::null_mut();
2068 }
2069
2070 // SAFETY: cur_node is an element.
2071 let idx = unsafe { self.attr_index_by_name(self.cur_node, name) };
2072 if idx < 0 {
2073 return ptr::null_mut();
2074 }
2075 match unsafe { self.attr_at(self.cur_node, idx) } {
2076 AttrTarget::Ns(ns) => {
2077 let href = unsafe { (*ns).href };
2078 if href.is_null() {
2079 ptr::null_mut()
2080 } else {
2081 unsafe { xml_strdup(href as *const xmlChar) }
2082 }
2083 }
2084 AttrTarget::Prop(prop) => {
2085 // Get the attribute value from its child text node; an
2086 // existing attribute with an empty value has no children
2087 // and reports "" (upstream xmlGetProp).
2088 let val = unsafe { (*prop).children };
2089 if !val.is_null() {
2090 let content = unsafe { (*val).content };
2091 if !content.is_null() {
2092 return unsafe { xml_strdup(content as *const xmlChar) };
2093 }
2094 }
2095 unsafe { xml_strdup(c"".as_ptr() as *const xmlChar) }
2096 }
2097 AttrTarget::None => ptr::null_mut(),
2098 }
2099 }
2100
2101 /// Get an attribute value by index.
2102 ///
2103 /// Returns a newly allocated string, or NULL.
2104 ///
2105 /// # SAFETY
2106 ///
2107 /// `self` must be a valid `XmlTextReader` obtained from a reader
2108 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
2109 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
2110 /// The `&mut self` borrow excludes concurrent access from other threads.
2111 /// Pointers returned by this method are valid only until the next
2112 /// traversal operation invalidates them, per the C API contract.
2113 pub unsafe fn GetAttributeNo(&self, index: c_int) -> *mut xmlChar {
2114 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderGetAttributeNo): the
2115 // accessor reads from the ELEMENT position only — an active
2116 // attribute cursor returns NULL.
2117 if self.cur_attribute >= 0 || self.cur_node.is_null() || index < 0 {
2118 return ptr::null_mut();
2119 }
2120
2121 // SAFETY: cur_node is valid.
2122 let etype = unsafe { (*self.cur_node).type_ };
2123 if etype != XML_ELEMENT_NODE as c_int {
2124 return ptr::null_mut();
2125 }
2126
2127 // SAFETY: cur_node is an element.
2128 match unsafe { self.attr_at(self.cur_node, index) } {
2129 AttrTarget::Ns(ns) => {
2130 let href = unsafe { (*ns).href };
2131 if href.is_null() {
2132 ptr::null_mut()
2133 } else {
2134 unsafe { xml_strdup(href as *const xmlChar) }
2135 }
2136 }
2137 AttrTarget::Prop(prop) => {
2138 let val = unsafe { (*prop).children };
2139 if !val.is_null() {
2140 let content = unsafe { (*val).content };
2141 if !content.is_null() {
2142 return unsafe { xml_strdup(content as *const xmlChar) };
2143 }
2144 }
2145 unsafe { xml_strdup(c"".as_ptr() as *const xmlChar) }
2146 }
2147 AttrTarget::None => ptr::null_mut(),
2148 }
2149 }
2150
2151 /// Get an attribute value by local name and namespace URI.
2152 ///
2153 /// Returns a newly allocated string, or NULL.
2154 ///
2155 /// # SAFETY
2156 ///
2157 /// `self` must be a valid `XmlTextReader` obtained from a reader
2158 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
2159 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
2160 /// The `&mut self` borrow excludes concurrent access from other threads.
2161 /// Pointers returned by this method are valid only until the next
2162 /// traversal operation invalidates them, per the C API contract.
2163 pub unsafe fn GetAttributeNs(
2164 &self,
2165 localName: *const xmlChar,
2166 namespaceURI: *const xmlChar,
2167 ) -> *mut xmlChar {
2168 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderGetAttributeNs): the
2169 // accessor reads from the ELEMENT position only — an active
2170 // attribute cursor returns NULL.
2171 if self.cur_attribute >= 0 || self.cur_node.is_null() {
2172 return ptr::null_mut();
2173 }
2174
2175 // SAFETY: cur_node is valid.
2176 let etype = unsafe { (*self.cur_node).type_ };
2177 if etype != XML_ELEMENT_NODE as c_int {
2178 return ptr::null_mut();
2179 }
2180
2181 // SAFETY: cur_node is an element.
2182 let mut prop = unsafe { (*self.cur_node).properties };
2183 while !prop.is_null() {
2184 // SAFETY: prop is valid.
2185 let prop_local = unsafe { (*prop).name };
2186 let prop_ns = unsafe { (*prop).ns };
2187
2188 // Check local name match.
2189 if prop_local.is_null() {
2190 // SAFETY: prop's next is valid.
2191 prop = unsafe { (*prop).next };
2192 continue;
2193 }
2194
2195 // SAFETY: prop_local is null-terminated.
2196 let name_match = unsafe {
2197 crate::xml::string::xml_strcmp(prop_local as *const xmlChar, localName) == 0
2198 };
2199
2200 if name_match {
2201 // Check namespace URI match.
2202 let ns_match = if namespaceURI.is_null() {
2203 prop_ns.is_null()
2204 } else if prop_ns.is_null() {
2205 false
2206 } else {
2207 // SAFETY: Both hrefs are null-terminated.
2208 unsafe {
2209 crate::xml::string::xml_strcmp(
2210 (*prop_ns).href as *const xmlChar,
2211 namespaceURI,
2212 ) == 0
2213 }
2214 };
2215
2216 if ns_match {
2217 // SAFETY: prop is valid. An existing attribute with an
2218 // empty value has no children and reports "" (upstream
2219 // xmlGetProp).
2220 let val = unsafe { (*prop).children };
2221 if !val.is_null() {
2222 // SAFETY: val's content is null-terminated.
2223 let content = unsafe { (*val).content };
2224 if !content.is_null() {
2225 // SAFETY: content is null-terminated.
2226 return unsafe { xml_strdup(content as *const xmlChar) };
2227 }
2228 }
2229 return unsafe { xml_strdup(c"".as_ptr() as *const xmlChar) };
2230 }
2231 }
2232
2233 // SAFETY: prop's next is valid.
2234 prop = unsafe { (*prop).next };
2235 }
2236
2237 ptr::null_mut()
2238 }
2239
2240 /// Look up a namespace by prefix.
2241 ///
2242 /// Returns a newly allocated string with the namespace URI, or NULL.
2243 ///
2244 /// # SAFETY
2245 ///
2246 /// `self` must be a valid `XmlTextReader` obtained from a reader
2247 /// constructor (`xmlReaderForDoc`/`xmlReaderForFile`/`xmlReaderForMemory`/
2248 /// `xmlReaderForIO` or `xmlNewTextReader`) and not yet closed or freed.
2249 /// The `&mut self` borrow excludes concurrent access from other threads.
2250 /// Pointers returned by this method are valid only until the next
2251 /// traversal operation invalidates them, per the C API contract.
2252 pub unsafe fn LookupNamespace(&self, prefix: *const xmlChar) -> *mut xmlChar {
2253 if self.cur_node.is_null() {
2254 return ptr::null_mut();
2255 }
2256
2257 // Walk up the tree looking for a namespace declaration matching the prefix.
2258 // SAFETY: cur_node is valid.
2259 let mut cur = self.cur_node;
2260 while !cur.is_null() {
2261 // SAFETY: cur is valid.
2262 let mut ns_def = unsafe { (*cur).nsDef };
2263 while !ns_def.is_null() {
2264 // SAFETY: ns_def is valid.
2265 let ns_prefix = unsafe { (*ns_def).prefix };
2266
2267 let match_prefix = if prefix.is_null() || *prefix == 0 {
2268 // Looking for default namespace.
2269 ns_prefix.is_null()
2270 } else if ns_prefix.is_null() {
2271 false
2272 } else {
2273 // SAFETY: Both are null-terminated.
2274 unsafe {
2275 crate::xml::string::xml_strcmp(ns_prefix as *const xmlChar, prefix) == 0
2276 }
2277 };
2278
2279 if match_prefix {
2280 // SAFETY: ns_def is valid.
2281 let href = unsafe { (*ns_def).href };
2282 if !href.is_null() {
2283 // SAFETY: href is null-terminated.
2284 return unsafe { xml_strdup(href as *const xmlChar) };
2285 }
2286 return ptr::null_mut();
2287 }
2288
2289 // SAFETY: ns_def's next is valid.
2290 ns_def = unsafe { (*ns_def).next };
2291 }
2292
2293 // SAFETY: cur's parent is valid.
2294 cur = unsafe { (*cur).parent };
2295 }
2296
2297 ptr::null_mut()
2298 }
2299
2300 /// Get a parser property.
2301 pub const fn GetParserProp(&self, prop: c_int) -> c_int {
2302 match prop {
2303 1 /* XML_PARSER_LOADDTD */ => {
2304 if (self.options & XML_PARSE_DTDLOAD) != 0 { 1 } else { 0 }
2305 }
2306 2 /* XML_PARSER_DEFAULTATTRS */ => {
2307 if (self.options & XML_PARSE_DTDATTR) != 0 { 1 } else { 0 }
2308 }
2309 3 /* XML_PARSER_VALIDATE */ => {
2310 if (self.options & XML_PARSE_DTDVALID) != 0 { 1 } else { 0 }
2311 }
2312 4 /* XML_PARSER_SUBST_ENTITIES */ => {
2313 if (self.options & XML_PARSE_NOENT) != 0 { 1 } else { 0 }
2314 }
2315 _ => -1,
2316 }
2317 }
2318
2319 /// Set a parser property.
2320 pub const fn SetParserProp(&mut self, prop: c_int, value: c_int) -> c_int {
2321 match prop {
2322 1 /* XML_PARSER_LOADDTD */ => {
2323 if value != 0 {
2324 self.options |= XML_PARSE_DTDLOAD;
2325 } else {
2326 self.options &= !XML_PARSE_DTDLOAD;
2327 }
2328 0
2329 }
2330 2 /* XML_PARSER_DEFAULTATTRS */ => {
2331 if value != 0 {
2332 self.options |= XML_PARSE_DTDATTR;
2333 } else {
2334 self.options &= !XML_PARSE_DTDATTR;
2335 }
2336 0
2337 }
2338 3 /* XML_PARSER_VALIDATE */ => {
2339 if value != 0 {
2340 self.options |= XML_PARSE_DTDVALID;
2341 } else {
2342 self.options &= !XML_PARSE_DTDVALID;
2343 }
2344 0
2345 }
2346 4 /* XML_PARSER_SUBST_ENTITIES */ => {
2347 if value != 0 {
2348 self.options |= XML_PARSE_NOENT;
2349 } else {
2350 self.options &= !XML_PARSE_NOENT;
2351 }
2352 0
2353 }
2354 _ => -1,
2355 }
2356 }
2357
2358 /// Get the current document.
2359 pub const fn CurrentDoc(&self) -> *mut _xmlDoc {
2360 self.doc
2361 }
2362}
2363
2364/// Recursively prune the children of `parent`: a child survives iff it is a
2365/// DTD node, it or an element ancestor is matched by a registered pattern
2366/// (`parent_matched` tracks the latter), or it is in the keep-set (matched
2367/// nodes and their element ancestors). Non-surviving subtrees are unlinked
2368/// and freed wholesale. This reproduces the post-stream state of upstream
2369/// `xmlTextReaderRead`'s NODE_IS_PRESERVED pruning for the whole-tree
2370/// reader.
2371///
2372/// # SAFETY
2373///
2374/// - `parent` must be a valid `_xmlNode`; `keep` must map every node pointer
2375/// that must survive; `patterns` are compiled patterns owned by the
2376/// reader.
2377unsafe fn prune_unpreserved(
2378 parent: *mut _xmlNode,
2379 parent_matched: bool,
2380 keep: &HashSet<usize>,
2381 patterns: &[crate::abi::exports_automata::xmlPatternPtr],
2382) {
2383 let mut child = unsafe { (*parent).children };
2384 while !child.is_null() {
2385 let next = unsafe { (*child).next };
2386 let is_dtd =
2387 unsafe { (*child).type_ } == crate::abi::types::xmlElementType::XML_DTD_NODE as c_int;
2388 let is_matched = patterns
2389 .iter()
2390 .any(|p| unsafe { crate::abi::exports_automata::xmlPatternMatch(*p, child) == 1 });
2391 let survives = is_dtd || parent_matched || is_matched || keep.contains(&(child as usize));
2392 if survives {
2393 unsafe {
2394 prune_unpreserved(child, parent_matched || is_matched, keep, patterns);
2395 }
2396 } else {
2397 // SAFETY: child is linked into the tree; unlink first so the
2398 // parent chain stays consistent, then free the subtree.
2399 unsafe {
2400 tree::unlink_node(child);
2401 tree::free_node_list(child);
2402 }
2403 }
2404 child = next;
2405 }
2406}
2407
2408impl Drop for XmlTextReader {
2409 fn drop(&mut self) {
2410 // Free cached strings.
2411 self.clear_cached_name();
2412 self.clear_cached_value();
2413
2414 // Free compiled preserve-patterns (xmlTextReaderPreservePattern;
2415 // upstream xmlTextReaderFree frees patternTab).
2416 for p in self.pattern_tab.drain(..) {
2417 // SAFETY: each pattern was compiled by xmlPatterncompile and is
2418 // owned by the reader.
2419 unsafe { crate::abi::exports_automata::xmlFreePattern(p) };
2420 }
2421
2422 // Free the cached last-error message (owned, xmlMalloc'd).
2423 if !self.last_err.message.is_null() {
2424 // SAFETY: message was allocated by xmlMalloc in GetLastError.
2425 unsafe { libc::free(self.last_err.message as *mut libc::c_void) };
2426 self.last_err.message = ptr::null_mut();
2427 }
2428
2429 // Free encoding and URL.
2430 if !self.encoding.is_null() {
2431 // SAFETY: encoding was allocated by xmlMalloc.
2432 unsafe { xmlFreeImpl(self.encoding as *mut c_void) };
2433 self.encoding = ptr::null_mut();
2434 }
2435 if !self.URL.is_null() {
2436 // SAFETY: URL was allocated by xmlMalloc.
2437 unsafe { xmlFreeImpl(self.URL as *mut c_void) };
2438 self.URL = ptr::null_mut();
2439 }
2440
2441 // Free the document if we own it (walker readers borrow the doc).
2442 // xmlTextReaderCurrentDoc sets `preserve`, transferring ownership to
2443 // the caller exactly like upstream's reader->preserve (xmlreader.c:
2444 // xmlTextReaderCurrentDoc sets preserve=1; xmlTextReaderClose frees
2445 // ctxt->myDoc only when preserve==0).
2446 if !self.doc.is_null() && self.owns_doc && !self.preserve {
2447 // SAFETY: doc was created by the parser, which allocates via xmlMalloc.
2448 // We own the doc since we created it.
2449 unsafe { tree::free_doc(self.doc) };
2450 self.doc = ptr::null_mut();
2451 }
2452
2453 // Free the parser context if still alive.
2454 if !self.ctxt.is_null() {
2455 // SAFETY: ctxt was created by create_parser_ctxt.
2456 unsafe { free_parser_ctxt(self.ctxt) };
2457 self.ctxt = ptr::null_mut();
2458 }
2459
2460 // Free compiled schemas the reader owns (xmlTextReaderSchemaValidate /
2461 // xmlTextReaderRelaxNGValidate compile them from a file path).
2462 // Caller-owned schemas (xmlTextReaderSetSchema /
2463 // xmlTextReaderRelaxNGSetSchema) are released by their owner — php
2464 // keeps them in intern->schema and frees them on re-set/close.
2465 if self.schema_owned && !self.schema.is_null() {
2466 // SAFETY: schema was compiled by xmlSchemaParse (Box<XsdSchema>).
2467 unsafe { crate::xml::schemas::xmlSchemaFree(self.schema) };
2468 self.schema = ptr::null_mut();
2469 self.schema_owned = false;
2470 }
2471 if self.rng_owned && !self.rng.is_null() {
2472 // SAFETY: rng was compiled by xmlRelaxNGParse (Box<RelaxNgSchema>).
2473 unsafe { crate::xml::relaxng::xmlRelaxNGFree(self.rng) };
2474 self.rng = ptr::null_mut();
2475 self.rng_owned = false;
2476 }
2477 }
2478}
2479
2480// ═══════════════════════════════════════════════════════════════════════════════
2481// Reader construction helpers
2482// ═══════════════════════════════════════════════════════════════════════════════
2483
2484/// Create a reader from a parser input buffer.
2485///
2486/// # Safety
2487///
2488/// `input` must be a valid `_xmlParserInputBuffer` pointer.
2489unsafe fn reader_from_input(
2490 input: *mut _xmlParserInputBuffer,
2491 URL: *const c_char,
2492 encoding: *const c_char,
2493 options: c_int,
2494) -> *mut XmlTextReader {
2495 if input.is_null() {
2496 return ptr::null_mut();
2497 }
2498
2499 // Create a parser context.
2500 let ctxt = create_parser_ctxt();
2501 if ctxt.is_null() {
2502 return ptr::null_mut();
2503 }
2504
2505 // Read all data from the input buffer using the read callback. A
2506 // memory buffer (xmlParserInputBufferCreateMem) has NO read callback —
2507 // upstream serves the parser from the buffer's own content, which the
2508 // candidate keeps in the input-buffer content stash (helpers.rs); an
2509 // I/O buffer's callback slurps the stream (XMLReader::fromStream).
2510 let mut data = Vec::new();
2511 let mut tmp = [0u8; 4096];
2512
2513 // SAFETY: input is valid.
2514 let read_cb = unsafe { (*input).readcallback };
2515 let ioctx = unsafe { (*input).context };
2516
2517 if let Some(read) = read_cb {
2518 loop {
2519 // SAFETY: The read callback must be valid and ioctx must be a valid context.
2520 let n = unsafe { read(ioctx, tmp.as_mut_ptr() as *mut c_char, tmp.len() as c_int) };
2521 if n <= 0 {
2522 break;
2523 }
2524 data.extend_from_slice(&tmp[..n as usize]);
2525 }
2526 } else {
2527 data = crate::xml::parser::helpers::take_input_buf_content(input).unwrap_or_default();
2528 }
2529
2530 // Close the input if there's a close callback.
2531 // SAFETY: input is valid.
2532 let close_cb = unsafe { (*input).closecallback };
2533 if let Some(close) = close_cb {
2534 // SAFETY: The close callback must be valid.
2535 unsafe { close(ioctx) };
2536 }
2537
2538 // Create an InputBuffer from the data.
2539 let input_buf = InputBuffer::from_memory(&data, None);
2540
2541 // Set up the parser context with the input.
2542 setup_parser_input(ctxt, input_buf);
2543
2544 // Set options (mirrors xmlCtxtUseOptions so the historical struct
2545 // members track XML_PARSE_* options — reader2.c validates via
2546 // XML_PARSE_DTDVALID; Phase-12 EXTERNAL-CONSUMERS court).
2547 unsafe {
2548 crate::abi::exports_parser::apply_options(ctxt, options);
2549 }
2550
2551 // Build URL and encoding strings.
2552 let url_bytes = if URL.is_null() {
2553 None
2554 } else {
2555 // SAFETY: URL is a valid C string.
2556 unsafe {
2557 let cstr = std::ffi::CStr::from_ptr(URL);
2558 Some(cstr.to_bytes().to_vec())
2559 }
2560 };
2561
2562 let enc_bytes = if encoding.is_null() {
2563 None
2564 } else {
2565 // SAFETY: encoding is a valid C string.
2566 unsafe {
2567 let cstr = std::ffi::CStr::from_ptr(encoding);
2568 Some(cstr.to_bytes().to_vec())
2569 }
2570 };
2571
2572 // Create the reader.
2573 let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
2574 reader.options = options;
2575
2576 // Box and leak the reader to return a raw pointer.
2577 Box::into_raw(Box::new(reader))
2578}
2579
2580// ═══════════════════════════════════════════════════════════════════════════════
2581// Public API functions
2582// ═══════════════════════════════════════════════════════════════════════════════
2583
2584/// Create a new text reader from an input buffer.
2585///
2586/// # UPSTREAM-PARITY
2587///
2588/// ```c
2589/// xmlTextReaderPtr xmlNewTextReader(xmlParserInputBufferPtr input, const char *URI);
2590/// ```
2591///
2592/// # Safety
2593///
2594/// - `input` must be a valid `_xmlParserInputBuffer` pointer or NULL.
2595/// - `URI` must be a valid C string or NULL.
2596#[no_mangle]
2597pub unsafe extern "C" fn xmlNewTextReader(
2598 input: *mut _xmlParserInputBuffer,
2599 URI: *const c_char,
2600) -> *mut XmlTextReader {
2601 // SAFETY: Forward to the helper.
2602 unsafe { reader_from_input(input, URI, ptr::null(), 0) }
2603}
2604
2605/// Create a text reader for a file.
2606///
2607/// # UPSTREAM-PARITY
2608///
2609/// ```c
2610/// xmlTextReaderPtr xmlReaderForFile(const char *filename, const char *encoding, int options);
2611/// ```
2612///
2613/// # Safety
2614///
2615/// - `filename` must be a valid C string or NULL.
2616/// - `encoding` must be a valid C string or NULL.
2617#[no_mangle]
2618pub unsafe extern "C" fn xmlReaderForFile(
2619 filename: *const c_char,
2620 encoding: *const c_char,
2621 options: c_int,
2622) -> *mut XmlTextReader {
2623 if filename.is_null() {
2624 return ptr::null_mut();
2625 }
2626
2627 // SAFETY: create_parser_ctxt returns a valid context or NULL.
2628 let ctxt = unsafe { create_parser_ctxt() };
2629 if ctxt.is_null() {
2630 return ptr::null_mut();
2631 }
2632
2633 // SAFETY: the open routes through the registered loader (php streams)
2634 // first, else the built-in file read; filename is a valid C string.
2635 // UPSTREAM-PARITY (reader.c xmlNewTextReaderFilename): the reader builds
2636 // its input via xmlParserInputBufferCreateFilename, which does NOT raise
2637 // xmlCtxtErrIO on XML_IO_ENOENT — a failed open returns NULL silently and
2638 // the php binding reports "Unable to open source data".
2639 let input =
2640 match unsafe { crate::abi::exports_parser::open_filename_routed_input_only(filename) } {
2641 crate::abi::exports_parser::RoutedFileOpen::Loaded(input) => input,
2642 crate::abi::exports_parser::RoutedFileOpen::Failed
2643 | crate::abi::exports_parser::RoutedFileOpen::EntityLoaderFailed => {
2644 // SAFETY: ctxt is valid.
2645 unsafe { free_parser_ctxt(ctxt) };
2646 return ptr::null_mut();
2647 }
2648 crate::abi::exports_parser::RoutedFileOpen::Builtin => {
2649 match unsafe { input_from_file(filename) } {
2650 Ok(input) => input,
2651 Err(_) => {
2652 // SAFETY: ctxt is valid.
2653 unsafe { free_parser_ctxt(ctxt) };
2654 return ptr::null_mut();
2655 }
2656 }
2657 }
2658 };
2659
2660 // SAFETY: ctxt and input are valid.
2661 unsafe { setup_parser_input(ctxt, input) };
2662 unsafe {
2663 (*ctxt).options = options;
2664 }
2665
2666 let enc_bytes = if encoding.is_null() {
2667 None
2668 } else {
2669 // SAFETY: encoding is a valid C string.
2670 unsafe {
2671 let cstr = std::ffi::CStr::from_ptr(encoding);
2672 Some(cstr.to_bytes().to_vec())
2673 }
2674 };
2675
2676 let mut reader = XmlTextReader::new(ctxt, None, enc_bytes.as_deref());
2677 reader.options = options;
2678 Box::into_raw(Box::new(reader))
2679}
2680
2681/// Create a text reader from memory.
2682///
2683/// # UPSTREAM-PARITY
2684///
2685/// ```c
2686/// xmlTextReaderPtr xmlReaderForMemory(const char *buffer, int size,
2687/// const char *URL, const char *encoding, int options);
2688/// ```
2689///
2690/// # Safety
2691///
2692/// - `buffer` must be a valid pointer with at least `size` readable bytes.
2693/// - `URL` and `encoding` must be valid C strings or NULL.
2694#[no_mangle]
2695pub unsafe extern "C" fn xmlReaderForMemory(
2696 buffer: *const c_char,
2697 size: c_int,
2698 URL: *const c_char,
2699 encoding: *const c_char,
2700 options: c_int,
2701) -> *mut XmlTextReader {
2702 if buffer.is_null() || size <= 0 {
2703 return ptr::null_mut();
2704 }
2705
2706 // SAFETY: create_parser_ctxt returns a valid context or NULL.
2707 let ctxt = unsafe { create_parser_ctxt() };
2708 if ctxt.is_null() {
2709 return ptr::null_mut();
2710 }
2711
2712 // SAFETY: input_from_memory copies the data; buffer and size are valid.
2713 // UPSTREAM-PARITY: the URL is recorded as the input's filename.
2714 let input = unsafe { input_from_memory_named(buffer, size, URL) };
2715
2716 // SAFETY: ctxt and input are valid.
2717 unsafe { setup_parser_input(ctxt, input) };
2718 unsafe {
2719 (*ctxt).options = options;
2720 }
2721
2722 let url_bytes = if URL.is_null() {
2723 None
2724 } else {
2725 // SAFETY: URL is a valid C string.
2726 unsafe {
2727 let cstr = std::ffi::CStr::from_ptr(URL);
2728 Some(cstr.to_bytes().to_vec())
2729 }
2730 };
2731
2732 let enc_bytes = if encoding.is_null() {
2733 None
2734 } else {
2735 // SAFETY: encoding is a valid C string.
2736 unsafe {
2737 let cstr = std::ffi::CStr::from_ptr(encoding);
2738 Some(cstr.to_bytes().to_vec())
2739 }
2740 };
2741
2742 let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
2743 reader.options = options;
2744 Box::into_raw(Box::new(reader))
2745}
2746
2747/// Create a text reader from a file descriptor.
2748///
2749/// # UPSTREAM-PARITY
2750///
2751/// ```c
2752/// xmlTextReaderPtr xmlReaderForFd(int fd, const char *URL,
2753/// const char *encoding, int options);
2754/// ```
2755///
2756/// # Safety
2757///
2758/// - `fd` must be a valid open file descriptor.
2759/// - `URL` and `encoding` must be valid C strings or NULL.
2760#[no_mangle]
2761pub unsafe extern "C" fn xmlReaderForFd(
2762 fd: c_int,
2763 URL: *const c_char,
2764 encoding: *const c_char,
2765 options: c_int,
2766) -> *mut XmlTextReader {
2767 // SAFETY: create_parser_ctxt returns a valid context or NULL.
2768 let ctxt = unsafe { create_parser_ctxt() };
2769 if ctxt.is_null() {
2770 return ptr::null_mut();
2771 }
2772
2773 // Read all data from the fd.
2774 let mut buf = Vec::new();
2775 let mut tmp = [0u8; 4096];
2776 loop {
2777 // SAFETY: fd must be a valid open file descriptor.
2778 let n = unsafe { libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len()) };
2779 if n <= 0 {
2780 break;
2781 }
2782 buf.extend_from_slice(&tmp[..n as usize]);
2783 }
2784
2785 // SAFETY: input_from_memory copies the buffer contents.
2786 let input = unsafe { input_from_memory(buf.as_ptr() as *const c_char, buf.len() as c_int) };
2787
2788 // SAFETY: ctxt and input are valid.
2789 unsafe { setup_parser_input(ctxt, input) };
2790 unsafe {
2791 (*ctxt).options = options;
2792 }
2793
2794 let url_bytes = if URL.is_null() {
2795 None
2796 } else {
2797 // SAFETY: URL is a valid C string.
2798 unsafe {
2799 let cstr = std::ffi::CStr::from_ptr(URL);
2800 Some(cstr.to_bytes().to_vec())
2801 }
2802 };
2803
2804 let enc_bytes = if encoding.is_null() {
2805 None
2806 } else {
2807 // SAFETY: encoding is a valid C string.
2808 unsafe {
2809 let cstr = std::ffi::CStr::from_ptr(encoding);
2810 Some(cstr.to_bytes().to_vec())
2811 }
2812 };
2813
2814 let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
2815 reader.options = options;
2816 Box::into_raw(Box::new(reader))
2817}
2818
2819/// Create a text reader from I/O callbacks.
2820///
2821/// # UPSTREAM-PARITY
2822///
2823/// ```c
2824/// xmlTextReaderPtr xmlReaderForIO(xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
2825/// void *ioctx, const char *URL,
2826/// const char *encoding, int options);
2827/// ```
2828///
2829/// # Safety
2830///
2831/// - `ioread` and `ioclose` must be valid function pointers or None.
2832/// - `ioctx` must be a valid context pointer for the callbacks.
2833/// - `URL` and `encoding` must be valid C strings or NULL.
2834#[no_mangle]
2835pub unsafe extern "C" fn xmlReaderForIO(
2836 ioread: Option<xmlInputReadCallback>,
2837 ioclose: Option<xmlInputCloseCallback>,
2838 ioctx: *mut c_void,
2839 URL: *const c_char,
2840 encoding: *const c_char,
2841 options: c_int,
2842) -> *mut XmlTextReader {
2843 // SAFETY: create_parser_ctxt returns a valid context or NULL.
2844 let ctxt = unsafe { create_parser_ctxt() };
2845 if ctxt.is_null() {
2846 return ptr::null_mut();
2847 }
2848
2849 // SAFETY: input_from_io reads all data via callbacks.
2850 let input = unsafe { input_from_io(ioread, ioclose, ioctx) };
2851
2852 // SAFETY: ctxt and input are valid.
2853 unsafe { setup_parser_input(ctxt, input) };
2854 unsafe {
2855 (*ctxt).options = options;
2856 }
2857
2858 let url_bytes = if URL.is_null() {
2859 None
2860 } else {
2861 // SAFETY: URL is a valid C string.
2862 unsafe {
2863 let cstr = std::ffi::CStr::from_ptr(URL);
2864 Some(cstr.to_bytes().to_vec())
2865 }
2866 };
2867
2868 let enc_bytes = if encoding.is_null() {
2869 None
2870 } else {
2871 // SAFETY: encoding is a valid C string.
2872 unsafe {
2873 let cstr = std::ffi::CStr::from_ptr(encoding);
2874 Some(cstr.to_bytes().to_vec())
2875 }
2876 };
2877
2878 let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
2879 reader.options = options;
2880 Box::into_raw(Box::new(reader))
2881}
2882
2883// ─────────────────────────────────────────────────────────────────────────────
2884// Navigation functions
2885// ─────────────────────────────────────────────────────────────────────────────
2886
2887/// Advance the reader to the next node in document order.
2888///
2889/// Returns 1 on success, 0 if EOF, -1 on error.
2890///
2891/// # UPSTREAM-PARITY
2892///
2893/// ```c
2894/// int xmlTextReaderRead(xmlTextReaderPtr reader);
2895/// ```
2896///
2897/// # Safety
2898///
2899/// `reader` must be a valid pointer returned by `xmlNewTextReader` or one of the
2900/// `xmlReaderFor*` functions, or NULL (in which case -1 is returned).
2901#[no_mangle]
2902pub unsafe extern "C" fn xmlTextReaderRead(reader: *mut XmlTextReader) -> c_int {
2903 if reader.is_null() {
2904 return -1;
2905 }
2906 // SAFETY: reader is valid.
2907 unsafe { (*reader).Read() }
2908}
2909
2910/// Skip to the next sibling of the current node.
2911///
2912/// Returns 1 on success, 0 if no more siblings, -1 on error.
2913///
2914/// # UPSTREAM-PARITY
2915///
2916/// ```c
2917/// int xmlTextReaderNext(xmlTextReaderPtr reader);
2918/// ```
2919///
2920/// # Safety
2921///
2922/// `reader` must be a valid pointer or NULL.
2923#[no_mangle]
2924pub unsafe extern "C" fn xmlTextReaderNext(reader: *mut XmlTextReader) -> c_int {
2925 if reader.is_null() {
2926 return -1;
2927 }
2928 // SAFETY: reader is valid.
2929 unsafe { (*reader).Next() }
2930}
2931
2932/// Skip to the next sibling (same as xmlTextReaderNext).
2933///
2934/// # UPSTREAM-PARITY
2935///
2936/// ```c
2937/// int xmlTextReaderNextSibling(xmlTextReaderPtr reader);
2938/// ```
2939///
2940/// # Safety
2941///
2942/// `reader` must be a valid pointer or NULL.
2943#[no_mangle]
2944pub unsafe extern "C" fn xmlTextReaderNextSibling(reader: *mut XmlTextReader) -> c_int {
2945 if reader.is_null() {
2946 return -1;
2947 }
2948 // SAFETY: reader is valid.
2949 unsafe { (*reader).Next() }
2950}
2951
2952/// Skip to the previous sibling of the current node.
2953///
2954/// Returns 1 on success, 0 if no previous sibling, -1 on error.
2955///
2956/// # UPSTREAM-PARITY
2957///
2958/// ```c
2959/// int xmlTextReaderPrev(xmlTextReaderPtr reader);
2960/// ```
2961///
2962/// # Safety
2963///
2964/// `reader` must be a valid pointer or NULL.
2965#[no_mangle]
2966pub unsafe extern "C" fn xmlTextReaderPrev(reader: *mut XmlTextReader) -> c_int {
2967 if reader.is_null() {
2968 return -1;
2969 }
2970 // SAFETY: reader is valid.
2971 unsafe { (*reader).Prev() }
2972}
2973
2974/// Move the reader back to the parent element (from an attribute).
2975///
2976/// Returns 1 on success, 0 if not on an attribute, -1 on error.
2977///
2978/// # UPSTREAM-PARITY
2979///
2980/// ```c
2981/// int xmlTextReaderMoveToElement(xmlTextReaderPtr reader);
2982/// ```
2983///
2984/// # Safety
2985///
2986/// `reader` must be a valid pointer or NULL.
2987#[no_mangle]
2988pub unsafe extern "C" fn xmlTextReaderMoveToElement(reader: *mut XmlTextReader) -> c_int {
2989 if reader.is_null() {
2990 return -1;
2991 }
2992 // SAFETY: reader is valid.
2993 unsafe { (*reader).MoveToElement() }
2994}
2995
2996/// Move to an attribute by name.
2997///
2998/// Returns 1 on success, 0 if not found, -1 on error.
2999///
3000/// # UPSTREAM-PARITY
3001///
3002/// ```c
3003/// int xmlTextReaderMoveToAttribute(xmlTextReaderPtr reader, const xmlChar *name);
3004/// ```
3005///
3006/// # Safety
3007///
3008/// `reader` must be a valid pointer or NULL. `name` must be a valid C string or NULL.
3009#[no_mangle]
3010pub unsafe extern "C" fn xmlTextReaderMoveToAttribute(
3011 reader: *mut XmlTextReader,
3012 name: *const xmlChar,
3013) -> c_int {
3014 if reader.is_null() || name.is_null() {
3015 return -1;
3016 }
3017 // SAFETY: reader and name are valid.
3018 unsafe { (*reader).MoveToAttribute(name) }
3019}
3020
3021/// Move to an attribute by index.
3022///
3023/// Returns 1 on success, 0 if not found, -1 on error.
3024///
3025/// # UPSTREAM-PARITY
3026///
3027/// ```c
3028/// int xmlTextReaderMoveToAttributeNo(xmlTextReaderPtr reader, int index);
3029/// ```
3030///
3031/// # Safety
3032///
3033/// `reader` must be a valid pointer or NULL.
3034#[no_mangle]
3035pub unsafe extern "C" fn xmlTextReaderMoveToAttributeNo(
3036 reader: *mut XmlTextReader,
3037 index: c_int,
3038) -> c_int {
3039 if reader.is_null() {
3040 return -1;
3041 }
3042 // SAFETY: reader is valid.
3043 unsafe { (*reader).MoveToAttributeNo(index) }
3044}
3045
3046/// Move to the first attribute of the current element.
3047///
3048/// Returns 1 on success, 0 if no attributes, -1 on error.
3049///
3050/// # UPSTREAM-PARITY
3051///
3052/// ```c
3053/// int xmlTextReaderMoveToFirstAttribute(xmlTextReaderPtr reader);
3054/// ```
3055///
3056/// # Safety
3057///
3058/// `reader` must be a valid pointer or NULL.
3059#[no_mangle]
3060pub unsafe extern "C" fn xmlTextReaderMoveToFirstAttribute(reader: *mut XmlTextReader) -> c_int {
3061 if reader.is_null() {
3062 return -1;
3063 }
3064 // SAFETY: reader is valid.
3065 unsafe { (*reader).MoveToFirstAttribute() }
3066}
3067
3068/// Move to the next attribute.
3069///
3070/// Returns 1 on success, 0 if no more attributes, -1 on error.
3071///
3072/// # UPSTREAM-PARITY
3073///
3074/// ```c
3075/// int xmlTextReaderMoveToNextAttribute(xmlTextReaderPtr reader);
3076/// ```
3077///
3078/// # Safety
3079///
3080/// `reader` must be a valid pointer or NULL.
3081#[no_mangle]
3082pub unsafe extern "C" fn xmlTextReaderMoveToNextAttribute(reader: *mut XmlTextReader) -> c_int {
3083 if reader.is_null() {
3084 return -1;
3085 }
3086 // SAFETY: reader is valid.
3087 unsafe { (*reader).MoveToNextAttribute() }
3088}
3089
3090// ─────────────────────────────────────────────────────────────────────────────
3091// Information methods
3092// ─────────────────────────────────────────────────────────────────────────────
3093
3094/// Get the attribute count of the current element.
3095///
3096/// Returns the number of attributes, or -1 if not on an element.
3097///
3098/// # UPSTREAM-PARITY
3099///
3100/// ```c
3101/// int xmlTextReaderAttributeCount(xmlTextReaderPtr reader);
3102/// ```
3103///
3104/// # Safety
3105///
3106/// `reader` must be a valid pointer or NULL.
3107#[no_mangle]
3108pub unsafe extern "C" fn xmlTextReaderAttributeCount(reader: *mut XmlTextReader) -> c_int {
3109 if reader.is_null() {
3110 return -1;
3111 }
3112 // SAFETY: reader is valid.
3113 unsafe { (*reader).AttributeCount() }
3114}
3115
3116/// Get the depth of the current node.
3117///
3118/// Returns the depth (0 for root element), or -1 on error.
3119///
3120/// # UPSTREAM-PARITY
3121///
3122/// ```c
3123/// int xmlTextReaderDepth(xmlTextReaderPtr reader);
3124/// ```
3125///
3126/// # Safety
3127///
3128/// `reader` must be a valid pointer or NULL.
3129#[no_mangle]
3130pub unsafe extern "C" fn xmlTextReaderDepth(reader: *mut XmlTextReader) -> c_int {
3131 if reader.is_null() {
3132 return -1;
3133 }
3134 // SAFETY: reader is valid.
3135 unsafe { (*reader).Depth() }
3136}
3137
3138/// Get the node type of the current node.
3139///
3140/// Returns one of the `xmlReaderTypes` constants, or -1 on error.
3141///
3142/// # UPSTREAM-PARITY
3143///
3144/// ```c
3145/// int xmlTextReaderNodeType(xmlTextReaderPtr reader);
3146/// ```
3147///
3148/// # Safety
3149///
3150/// `reader` must be a valid pointer or NULL.
3151#[no_mangle]
3152pub unsafe extern "C" fn xmlTextReaderNodeType(reader: *mut XmlTextReader) -> c_int {
3153 if reader.is_null() {
3154 return -1;
3155 }
3156 // SAFETY: reader is valid.
3157 unsafe { (*reader).NodeType() as c_int }
3158}
3159
3160/// Get the name of the current node.
3161///
3162/// Returns a newly allocated string (caller must free with `xmlFree`),
3163/// or NULL if there is no name.
3164///
3165/// # UPSTREAM-PARITY
3166///
3167/// ```c
3168/// xmlChar *xmlTextReaderName(xmlTextReaderPtr reader);
3169/// ```
3170///
3171/// # Safety
3172///
3173/// `reader` must be a valid pointer or NULL.
3174#[no_mangle]
3175pub unsafe extern "C" fn xmlTextReaderName(reader: *mut XmlTextReader) -> *mut xmlChar {
3176 if reader.is_null() {
3177 return ptr::null_mut();
3178 }
3179 // SAFETY: reader is valid.
3180 unsafe { (*reader).Name() }
3181}
3182
3183/// Get the value of the current node.
3184///
3185/// Returns a newly allocated string (caller must free with `xmlFree`),
3186/// or NULL if there is no value.
3187///
3188/// # UPSTREAM-PARITY
3189///
3190/// ```c
3191/// xmlChar *xmlTextReaderValue(xmlTextReaderPtr reader);
3192/// ```
3193///
3194/// # Safety
3195///
3196/// `reader` must be a valid pointer or NULL.
3197#[no_mangle]
3198pub unsafe extern "C" fn xmlTextReaderValue(reader: *mut XmlTextReader) -> *mut xmlChar {
3199 if reader.is_null() {
3200 return ptr::null_mut();
3201 }
3202 // SAFETY: reader is valid.
3203 unsafe { (*reader).Value() }
3204}
3205
3206/// Get a constant pointer to the name (no copy).
3207///
3208/// The returned pointer is valid only while the reader is alive and positioned
3209/// on the same node.
3210///
3211/// # UPSTREAM-PARITY
3212///
3213/// ```c
3214/// const xmlChar *xmlTextReaderConstName(xmlTextReaderPtr reader);
3215/// ```
3216///
3217/// # Safety
3218///
3219/// `reader` must be a valid pointer or NULL.
3220#[no_mangle]
3221pub unsafe extern "C" fn xmlTextReaderConstName(reader: *mut XmlTextReader) -> *const xmlChar {
3222 if reader.is_null() {
3223 return ptr::null();
3224 }
3225 // SAFETY: reader is valid.
3226 unsafe { (*reader).ConstName() }
3227}
3228
3229/// Get a constant pointer to the value (no copy).
3230///
3231/// The returned pointer is valid only while the reader is alive and positioned
3232/// on the same node.
3233///
3234/// # UPSTREAM-PARITY
3235///
3236/// ```c
3237/// const xmlChar *xmlTextReaderConstValue(xmlTextReaderPtr reader);
3238/// ```
3239///
3240/// # Safety
3241///
3242/// `reader` must be a valid pointer or NULL.
3243#[no_mangle]
3244pub unsafe extern "C" fn xmlTextReaderConstValue(reader: *mut XmlTextReader) -> *const xmlChar {
3245 if reader.is_null() {
3246 return ptr::null();
3247 }
3248 // SAFETY: reader is valid.
3249 unsafe { (*reader).ConstValue() }
3250}
3251
3252/// Get the base URI of the current node.
3253///
3254/// Returns a newly allocated string (caller must free with `xmlFree`),
3255/// or NULL if not available.
3256///
3257/// # UPSTREAM-PARITY
3258///
3259/// ```c
3260/// xmlChar *xmlTextReaderBaseUri(xmlTextReaderPtr reader);
3261/// ```
3262///
3263/// # Safety
3264///
3265/// `reader` must be a valid pointer or NULL.
3266#[no_mangle]
3267pub unsafe extern "C" fn xmlTextReaderBaseUri(reader: *mut XmlTextReader) -> *mut xmlChar {
3268 if reader.is_null() {
3269 return ptr::null_mut();
3270 }
3271 // SAFETY: reader is valid.
3272 unsafe { (*reader).BaseUri() }
3273}
3274
3275/// Get the local name of the current node.
3276///
3277/// Returns a newly allocated string, or NULL.
3278///
3279/// # UPSTREAM-PARITY
3280///
3281/// ```c
3282/// xmlChar *xmlTextReaderLocalName(xmlTextReaderPtr reader);
3283/// ```
3284///
3285/// # Safety
3286///
3287/// `reader` must be a valid pointer or NULL.
3288#[no_mangle]
3289pub unsafe extern "C" fn xmlTextReaderLocalName(reader: *mut XmlTextReader) -> *mut xmlChar {
3290 if reader.is_null() {
3291 return ptr::null_mut();
3292 }
3293 // SAFETY: reader is valid.
3294 unsafe { (*reader).LocalName() }
3295}
3296
3297/// Get the namespace URI of the current node.
3298///
3299/// Returns a newly allocated string, or NULL.
3300///
3301/// # UPSTREAM-PARITY
3302///
3303/// ```c
3304/// xmlChar *xmlTextReaderNamespaceUri(xmlTextReaderPtr reader);
3305/// ```
3306///
3307/// # Safety
3308///
3309/// `reader` must be a valid pointer or NULL.
3310#[no_mangle]
3311pub unsafe extern "C" fn xmlTextReaderNamespaceUri(reader: *mut XmlTextReader) -> *mut xmlChar {
3312 if reader.is_null() {
3313 return ptr::null_mut();
3314 }
3315 // SAFETY: reader is valid.
3316 unsafe { (*reader).NamespaceUri() }
3317}
3318
3319/// Get the prefix of the current node.
3320///
3321/// Returns a newly allocated string, or NULL.
3322///
3323/// # UPSTREAM-PARITY
3324///
3325/// ```c
3326/// xmlChar *xmlTextReaderPrefix(xmlTextReaderPtr reader);
3327/// ```
3328///
3329/// # Safety
3330///
3331/// `reader` must be a valid pointer or NULL.
3332#[no_mangle]
3333pub unsafe extern "C" fn xmlTextReaderPrefix(reader: *mut XmlTextReader) -> *mut xmlChar {
3334 if reader.is_null() {
3335 return ptr::null_mut();
3336 }
3337 // SAFETY: reader is valid.
3338 unsafe { (*reader).Prefix() }
3339}
3340
3341/// Check if the current node has a value.
3342///
3343/// Returns 1 if the node has a value, 0 otherwise.
3344///
3345/// # UPSTREAM-PARITY
3346///
3347/// ```c
3348/// int xmlTextReaderHasValue(xmlTextReaderPtr reader);
3349/// ```
3350///
3351/// # Safety
3352///
3353/// `reader` must be a valid pointer or NULL.
3354#[no_mangle]
3355pub unsafe extern "C" fn xmlTextReaderHasValue(reader: *mut XmlTextReader) -> c_int {
3356 if reader.is_null() {
3357 return 0;
3358 }
3359 // SAFETY: reader is valid.
3360 unsafe { (*reader).HasValue() }
3361}
3362
3363/// Check if the current node has attributes.
3364///
3365/// Returns 1 if the node has attributes, 0 otherwise.
3366///
3367/// # UPSTREAM-PARITY
3368///
3369/// ```c
3370/// int xmlTextReaderHasAttributes(xmlTextReaderPtr reader);
3371/// ```
3372///
3373/// # Safety
3374///
3375/// `reader` must be a valid pointer or NULL.
3376#[no_mangle]
3377pub unsafe extern "C" fn xmlTextReaderHasAttributes(reader: *mut XmlTextReader) -> c_int {
3378 if reader.is_null() {
3379 return 0;
3380 }
3381 // SAFETY: reader is valid.
3382 unsafe { (*reader).HasAttributes() }
3383}
3384
3385/// Check if the current element is an empty element (no children).
3386///
3387/// Returns 1 if empty, 0 otherwise.
3388///
3389/// # UPSTREAM-PARITY
3390///
3391/// ```c
3392/// int xmlTextReaderIsEmptyElement(xmlTextReaderPtr reader);
3393/// ```
3394///
3395/// # Safety
3396///
3397/// `reader` must be a valid pointer or NULL.
3398#[no_mangle]
3399pub unsafe extern "C" fn xmlTextReaderIsEmptyElement(reader: *mut XmlTextReader) -> c_int {
3400 if reader.is_null() {
3401 return 0;
3402 }
3403 // SAFETY: reader is valid.
3404 unsafe { (*reader).IsEmptyElement() }
3405}
3406
3407/// Get the read state.
3408///
3409/// Returns one of the `xmlTextReaderReadState` constants.
3410///
3411/// # UPSTREAM-PARITY
3412///
3413/// ```c
3414/// int xmlTextReaderReadState(xmlTextReaderPtr reader);
3415/// ```
3416///
3417/// # Safety
3418///
3419/// `reader` must be a valid pointer or NULL.
3420#[no_mangle]
3421pub unsafe extern "C" fn xmlTextReaderReadState(reader: *mut XmlTextReader) -> c_int {
3422 if reader.is_null() {
3423 return ReadState::ERROR as c_int;
3424 }
3425 // SAFETY: reader is valid.
3426 unsafe { (*reader).ReadState() as c_int }
3427}
3428
3429// ─────────────────────────────────────────────────────────────────────────────
3430// Attribute access
3431// ─────────────────────────────────────────────────────────────────────────────
3432
3433/// Get an attribute value by name.
3434///
3435/// Returns a newly allocated string, or NULL.
3436///
3437/// # UPSTREAM-PARITY
3438///
3439/// ```c
3440/// xmlChar *xmlTextReaderGetAttribute(xmlTextReaderPtr reader, const xmlChar *name);
3441/// ```
3442///
3443/// # Safety
3444///
3445/// `reader` and `name` must be valid pointers or NULL.
3446#[no_mangle]
3447pub unsafe extern "C" fn xmlTextReaderGetAttribute(
3448 reader: *mut XmlTextReader,
3449 name: *const xmlChar,
3450) -> *mut xmlChar {
3451 if reader.is_null() || name.is_null() {
3452 return ptr::null_mut();
3453 }
3454 // SAFETY: reader and name are valid.
3455 unsafe { (*reader).GetAttribute(name) }
3456}
3457
3458/// Get an attribute value by index.
3459///
3460/// Returns a newly allocated string, or NULL.
3461///
3462/// # UPSTREAM-PARITY
3463///
3464/// ```c
3465/// xmlChar *xmlTextReaderGetAttributeNo(xmlTextReaderPtr reader, int index);
3466/// ```
3467///
3468/// # Safety
3469///
3470/// `reader` must be a valid pointer or NULL.
3471#[no_mangle]
3472pub unsafe extern "C" fn xmlTextReaderGetAttributeNo(
3473 reader: *mut XmlTextReader,
3474 index: c_int,
3475) -> *mut xmlChar {
3476 if reader.is_null() {
3477 return ptr::null_mut();
3478 }
3479 // SAFETY: reader is valid.
3480 unsafe { (*reader).GetAttributeNo(index) }
3481}
3482
3483/// Get an attribute value by local name and namespace URI.
3484///
3485/// Returns a newly allocated string, or NULL.
3486///
3487/// # UPSTREAM-PARITY
3488///
3489/// ```c
3490/// xmlChar *xmlTextReaderGetAttributeNs(xmlTextReaderPtr reader,
3491/// const xmlChar *localName,
3492/// const xmlChar *namespaceURI);
3493/// ```
3494///
3495/// # Safety
3496///
3497/// `reader`, `localName`, and `namespaceURI` must be valid pointers or NULL.
3498#[no_mangle]
3499pub unsafe extern "C" fn xmlTextReaderGetAttributeNs(
3500 reader: *mut XmlTextReader,
3501 localName: *const xmlChar,
3502 namespaceURI: *const xmlChar,
3503) -> *mut xmlChar {
3504 if reader.is_null() || localName.is_null() {
3505 return ptr::null_mut();
3506 }
3507 // SAFETY: reader, localName, and namespaceURI are valid.
3508 unsafe { (*reader).GetAttributeNs(localName, namespaceURI) }
3509}
3510
3511/// Look up a namespace by prefix.
3512///
3513/// Returns a newly allocated string with the namespace URI, or NULL.
3514///
3515/// # UPSTREAM-PARITY
3516///
3517/// ```c
3518/// xmlChar *xmlTextReaderLookupNamespace(xmlTextReaderPtr reader, const xmlChar *prefix);
3519/// ```
3520///
3521/// # Safety
3522///
3523/// `reader` and `prefix` must be valid pointers or NULL.
3524#[no_mangle]
3525pub unsafe extern "C" fn xmlTextReaderLookupNamespace(
3526 reader: *mut XmlTextReader,
3527 prefix: *const xmlChar,
3528) -> *mut xmlChar {
3529 if reader.is_null() {
3530 return ptr::null_mut();
3531 }
3532 // SAFETY: reader and prefix are valid.
3533 unsafe { (*reader).LookupNamespace(prefix) }
3534}
3535
3536// ─────────────────────────────────────────────────────────────────────────────
3537// Parser properties
3538// ─────────────────────────────────────────────────────────────────────────────
3539
3540/// Get a parser property.
3541///
3542/// Returns the property value (0 or 1), or -1 on error.
3543///
3544/// # UPSTREAM-PARITY
3545///
3546/// ```c
3547/// int xmlTextReaderGetParserProp(xmlTextReaderPtr reader, int prop);
3548/// ```
3549///
3550/// # Safety
3551///
3552/// `reader` must be a valid pointer or NULL.
3553#[no_mangle]
3554pub unsafe extern "C" fn xmlTextReaderGetParserProp(
3555 reader: *mut XmlTextReader,
3556 prop: c_int,
3557) -> c_int {
3558 if reader.is_null() {
3559 return -1;
3560 }
3561 // SAFETY: reader is valid.
3562 unsafe { (*reader).GetParserProp(prop) }
3563}
3564
3565/// Set a parser property.
3566///
3567/// Returns 0 on success, -1 on error.
3568///
3569/// # UPSTREAM-PARITY
3570///
3571/// ```c
3572/// int xmlTextReaderSetParserProp(xmlTextReaderPtr reader, int prop, int value);
3573/// ```
3574///
3575/// # Safety
3576///
3577/// `reader` must be a valid pointer or NULL.
3578#[no_mangle]
3579pub unsafe extern "C" fn xmlTextReaderSetParserProp(
3580 reader: *mut XmlTextReader,
3581 prop: c_int,
3582 value: c_int,
3583) -> c_int {
3584 if reader.is_null() {
3585 return -1;
3586 }
3587 // SAFETY: reader is valid.
3588 unsafe { (*reader).SetParserProp(prop, value) }
3589}
3590
3591// ─────────────────────────────────────────────────────────────────────────────
3592// Lifecycle
3593// ─────────────────────────────────────────────────────────────────────────────
3594
3595/// Free a text reader.
3596///
3597/// # UPSTREAM-PARITY
3598///
3599/// ```c
3600/// void xmlFreeTextReader(xmlTextReaderPtr reader);
3601/// ```
3602///
3603/// # Safety
3604///
3605/// `reader` must be a valid pointer returned by `xmlNewTextReader` or one of the
3606/// `xmlReaderFor*` functions, or NULL (in which case this is a no-op).
3607#[no_mangle]
3608pub unsafe extern "C" fn xmlFreeTextReader(reader: *mut XmlTextReader) {
3609 if reader.is_null() {
3610 return;
3611 }
3612 // SAFETY: reader was created via Box::into_raw, so we reconstruct the Box
3613 // and let it drop, which calls the Drop impl.
3614 unsafe {
3615 let _ = Box::from_raw(reader);
3616 }
3617}
3618
3619/// Setup/reinitialize a reader with new input.
3620///
3621/// # UPSTREAM-PARITY
3622///
3623/// ```c
3624/// int xmlTextReaderSetup(xmlTextReaderPtr reader,
3625/// xmlParserInputBufferPtr input,
3626/// const char *URL, const char *encoding, int options);
3627/// ```
3628///
3629/// # Safety
3630///
3631/// - `reader` must be a valid pointer or NULL.
3632/// - `input` must be a valid `_xmlParserInputBuffer` pointer or NULL.
3633/// - `URL` and `encoding` must be valid C strings or NULL.
3634#[no_mangle]
3635pub unsafe extern "C" fn xmlTextReaderSetup(
3636 reader: *mut XmlTextReader,
3637 input: *mut _xmlParserInputBuffer,
3638 URL: *const c_char,
3639 encoding: *const c_char,
3640 options: c_int,
3641) -> c_int {
3642 if reader.is_null() {
3643 return -1;
3644 }
3645
3646 // SAFETY: reader is valid.
3647 let r = unsafe { &mut *reader };
3648
3649 // Reset the reader state.
3650 r.clear_cached_name();
3651 r.clear_cached_value();
3652
3653 r.events.clear();
3654 r.event_index = 0;
3655 r.state = ReadState::INITIALIZED;
3656 r.cur_node = ptr::null_mut();
3657 r.node_type = ReaderNodeType::NONE;
3658 r.depth = 0;
3659 r.attribute_count = -1;
3660 r.cur_attribute = -1;
3661 r.options = options;
3662 r.parsed = false;
3663 r.errors.clear();
3664 r.doc_incomplete = false;
3665 r.finalized = false;
3666 r.reparse_input = None;
3667
3668 // Update URL.
3669 if !r.URL.is_null() {
3670 // SAFETY: URL was allocated by xmlMalloc.
3671 unsafe { xmlFreeImpl(r.URL as *mut c_void) };
3672 r.URL = ptr::null_mut();
3673 }
3674 if !URL.is_null() {
3675 // SAFETY: URL is a valid C string.
3676 let url_str = unsafe { std::ffi::CStr::from_ptr(URL) };
3677 // SAFETY: bytes_to_xmlstr allocates via xmlMalloc.
3678 r.URL = unsafe { bytes_to_xmlstr(url_str.to_bytes()) };
3679 }
3680
3681 // Update encoding.
3682 if !r.encoding.is_null() {
3683 // SAFETY: encoding was allocated by xmlMalloc.
3684 unsafe { xmlFreeImpl(r.encoding as *mut c_void) };
3685 r.encoding = ptr::null_mut();
3686 }
3687 if !encoding.is_null() {
3688 // SAFETY: encoding is a valid C string.
3689 let enc_str = unsafe { std::ffi::CStr::from_ptr(encoding) };
3690 // SAFETY: bytes_to_xmlstr allocates via xmlMalloc.
3691 r.encoding = unsafe { bytes_to_xmlstr(enc_str.to_bytes()) };
3692 }
3693
3694 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderSetup): a NULL input means
3695 // "use the input the reader was created with" — PHP's XMLReader::XML() /
3696 // fromString() call xmlNewTextReader(inputbfr, uri) and then
3697 // xmlTextReaderSetup(reader, NULL, uri, encoding, options). The
3698 // candidate consumed that buffer in reader_from_input (the context is
3699 // already wired), so a NULL input keeps the existing context; a NEW
3700 // input replaces it (C consumers re-arming the reader).
3701 if input.is_null() {
3702 // UPSTREAM-PARITY: a NULL input promotes the source the reader was
3703 // created with (php XMLReader::XML()/fromString(): xmlNewTextReader
3704 // then xmlTextReaderSetup(reader, NULL, ...)). The candidate
3705 // consumed that source in reader_from_input, so the context built
3706 // there is kept. A reader that ALREADY parsed (its context was
3707 // consumed by parse_and_build_events) discards the old document and
3708 // resets — upstream xmlTextReaderSetup tears the previous parse
3709 // state down.
3710 if r.ctxt.is_null() && !r.doc.is_null() {
3711 // SAFETY: doc was allocated by the parser.
3712 unsafe { tree::free_doc(r.doc) };
3713 r.doc = ptr::null_mut();
3714 }
3715 return 0;
3716 }
3717
3718 // Free the old document.
3719 if !r.doc.is_null() {
3720 // SAFETY: doc was allocated by the parser.
3721 unsafe { tree::free_doc(r.doc) };
3722 r.doc = ptr::null_mut();
3723 }
3724
3725 // Free old parser context.
3726 if !r.ctxt.is_null() {
3727 // SAFETY: ctxt was created by create_parser_ctxt.
3728 unsafe { free_parser_ctxt(r.ctxt) };
3729 r.ctxt = ptr::null_mut();
3730 }
3731
3732 // Create new parser context and set up input.
3733 // SAFETY: create_parser_ctxt returns a valid context or NULL.
3734 let ctxt = unsafe { create_parser_ctxt() };
3735 if ctxt.is_null() {
3736 return -1;
3737 }
3738
3739 // Read all data from the input buffer. A memory buffer
3740 // (xmlParserInputBufferCreateMem) has no read callback — its content
3741 // lives in the input-buffer content stash (helpers.rs).
3742 let mut data = Vec::new();
3743 let mut tmp = [0u8; 4096];
3744
3745 // SAFETY: input is valid.
3746 let read_cb = unsafe { (*input).readcallback };
3747 let ioctx = unsafe { (*input).context };
3748
3749 if let Some(read) = read_cb {
3750 loop {
3751 // SAFETY: callbacks are valid.
3752 let n = unsafe { read(ioctx, tmp.as_mut_ptr() as *mut c_char, tmp.len() as c_int) };
3753 if n <= 0 {
3754 break;
3755 }
3756 data.extend_from_slice(&tmp[..n as usize]);
3757 }
3758 } else {
3759 data = crate::xml::parser::helpers::take_input_buf_content(input).unwrap_or_default();
3760 }
3761
3762 // Close the input.
3763 let close_cb = unsafe { (*input).closecallback };
3764 if let Some(close) = close_cb {
3765 // SAFETY: close callback is valid.
3766 unsafe { close(ioctx) };
3767 }
3768
3769 let input_buf = InputBuffer::from_memory(&data, None);
3770
3771 // SAFETY: ctxt and input_buf are valid.
3772 unsafe { setup_parser_input(ctxt, input_buf) };
3773 unsafe {
3774 (*ctxt).options = options;
3775 }
3776
3777 r.ctxt = ctxt;
3778
3779 0
3780}
3781
3782/// Get the current document from the reader.
3783///
3784/// Returns a pointer to the `_xmlDoc` or NULL.
3785///
3786/// # UPSTREAM-PARITY
3787///
3788/// ```c
3789/// xmlDocPtr xmlTextReaderCurrentDoc(xmlTextReaderPtr reader);
3790/// ```
3791///
3792/// # Safety
3793///
3794/// `reader` must be a valid pointer or NULL.
3795#[no_mangle]
3796pub unsafe extern "C" fn xmlTextReaderCurrentDoc(reader: *mut XmlTextReader) -> *mut _xmlDoc {
3797 if reader.is_null() {
3798 return ptr::null_mut();
3799 }
3800 // SAFETY: reader is valid. Upstream xmlTextReaderCurrentDoc sets
3801 // reader->preserve = 1 and returns ctxt->myDoc: the document ownership
3802 // moves to the caller (freed with xmlFreeDoc). Mirror that so the
3803 // reader's Drop does not free a doc the caller still owns (upstream
3804 // reader3.c pattern; Phase-12 EXTERNAL-CONSUMERS court).
3805 unsafe {
3806 (*reader).preserve = true;
3807 (*reader).CurrentDoc()
3808 }
3809}
3810
3811/// Close the reader, releasing the document and parser state.
3812///
3813/// # UPSTREAM-PARITY
3814///
3815/// Upstream `xmlTextReaderClose` (xmlreader.c): sets the mode to
3816/// XML_TEXTREADER_MODE_CLOSED, drops the current node, and tears down the
3817/// validation state. The reader object itself is freed separately with
3818/// `xmlFreeTextReader`.
3819///
3820/// ```c
3821/// int xmlTextReaderClose(xmlTextReaderPtr reader);
3822/// ```
3823///
3824/// Returns 0 on success, -1 if `reader` is NULL.
3825///
3826/// # Safety
3827///
3828/// `reader` must be a valid pointer or NULL.
3829#[no_mangle]
3830pub unsafe extern "C" fn xmlTextReaderClose(reader: *mut XmlTextReader) -> c_int {
3831 if reader.is_null() {
3832 return -1;
3833 }
3834 // SAFETY: reader is valid; close resets cursor state and marks the
3835 // reader closed, mirroring upstream's mode transition.
3836 unsafe {
3837 let r = &mut *reader;
3838 r.cur_node = ptr::null_mut();
3839 r.node_type = ReaderNodeType::NONE;
3840 r.clear_cached_name();
3841 r.clear_cached_value();
3842 r.state = ReadState::CLOSED;
3843 }
3844 0
3845}
3846
3847/// Return the current node of the reader.
3848///
3849/// # UPSTREAM-PARITY
3850///
3851/// ```c
3852/// xmlNodePtr xmlTextReaderCurrentNode(xmlTextReaderPtr reader);
3853/// ```
3854///
3855/// Returns the current node or NULL. The node is owned by the document;
3856/// the caller must not free it.
3857///
3858/// # Safety
3859///
3860/// `reader` must be a valid pointer or NULL.
3861#[no_mangle]
3862pub unsafe extern "C" fn xmlTextReaderCurrentNode(reader: *mut XmlTextReader) -> *mut _xmlNode {
3863 if reader.is_null() {
3864 return ptr::null_mut();
3865 }
3866 // SAFETY: reader is valid.
3867 unsafe { (*reader).cur_node }
3868}
3869
3870/// Expand entity references at the current position.
3871///
3872/// # UPSTREAM-PARITY
3873///
3874/// Upstream `xmlTextReaderExpand` (xmlreader.c) forces substitution of the
3875/// current entity reference so the node can be read in full. When the parser
3876/// ran with XML_PARSE_NOENT the entities are already substituted during
3877/// parsing; the function then simply returns the current node.
3878///
3879/// ```c
3880/// xmlNodePtr xmlTextReaderExpand(xmlTextReaderPtr reader);
3881/// ```
3882///
3883/// Returns the (expanded) current node, or NULL if the reader is NULL or
3884/// not positioned on a node.
3885///
3886/// # Safety
3887///
3888/// `reader` must be a valid pointer or NULL.
3889#[no_mangle]
3890pub unsafe extern "C" fn xmlTextReaderExpand(reader: *mut XmlTextReader) -> *mut _xmlNode {
3891 if reader.is_null() {
3892 return ptr::null_mut();
3893 }
3894 // SAFETY: reader is valid.
3895 unsafe { (*reader).cur_node }
3896}
3897
3898/// Return the parser line number of the current node.
3899///
3900/// # UPSTREAM-PARITY
3901///
3902/// Upstream `xmlTextReaderGetParserLineNumber` returns the input stream's
3903/// current line. The candidate records `line` per node during parsing, which
3904/// is equivalent for the read cursor.
3905///
3906/// ```c
3907/// int xmlTextReaderGetParserLineNumber(xmlTextReaderPtr reader);
3908/// ```
3909///
3910/// Returns the line number, or 0 when unavailable.
3911///
3912/// # Safety
3913///
3914/// `reader` must be a valid pointer or NULL.
3915#[no_mangle]
3916pub unsafe extern "C" fn xmlTextReaderGetParserLineNumber(reader: *mut XmlTextReader) -> c_int {
3917 if reader.is_null() {
3918 return 0;
3919 }
3920 // SAFETY: reader is valid; cur_node is owned by the doc.
3921 unsafe {
3922 let node = (*reader).cur_node;
3923 if node.is_null() {
3924 0
3925 } else {
3926 (*node).line as c_int
3927 }
3928 }
3929}
3930
3931/// Return the parser column number of the current node.
3932///
3933/// # UPSTREAM-PARITY
3934///
3935/// Upstream `xmlTextReaderGetParserColumnNumber` returns the input stream's
3936/// column. Columns are not tracked per-node in the candidate tree (upstream
3937/// exposes -1 when no column information is available either); return -1.
3938///
3939/// ```c
3940/// int xmlTextReaderGetParserColumnNumber(xmlTextReaderPtr reader);
3941/// ```
3942///
3943/// # Safety
3944///
3945/// `reader` must be a valid pointer or NULL.
3946#[no_mangle]
3947pub const unsafe extern "C" fn xmlTextReaderGetParserColumnNumber(
3948 reader: *mut XmlTextReader,
3949) -> c_int {
3950 if reader.is_null() {
3951 return -1;
3952 }
3953 -1
3954}
3955
3956/// Return the validation status of the reader.
3957///
3958/// # UPSTREAM-PARITY
3959///
3960/// Upstream `xmlTextReaderIsValid` (xmlreader.c): with a RELAX NG
3961/// validation context attached the RELAX NG outcome wins, then the XSD
3962/// outcome, then the DTD-parse validity (`ctxt->valid`); 0 when no
3963/// validation was performed or it failed, -1 for a NULL reader.
3964///
3965/// ```c
3966/// int xmlTextReaderIsValid(xmlTextReaderPtr reader);
3967/// ```
3968///
3969/// # Safety
3970///
3971/// `reader` must be a valid pointer or NULL.
3972#[no_mangle]
3973pub unsafe extern "C" fn xmlTextReaderIsValid(reader: *mut XmlTextReader) -> c_int {
3974 if reader.is_null() {
3975 return -1;
3976 }
3977 unsafe {
3978 let r = &*reader;
3979 if !r.rng.is_null() {
3980 if r.rng_result >= 0 {
3981 r.rng_result
3982 } else {
3983 // Validation not run yet (schema attached, first Read not
3984 // done): upstream's rngValidCtxt->valid starts at 0.
3985 0
3986 }
3987 } else if !r.schema.is_null() {
3988 if r.xsd_result >= 0 {
3989 r.xsd_result
3990 } else {
3991 0
3992 }
3993 } else if r.did_validate == 1 {
3994 r.was_valid
3995 } else {
3996 0
3997 }
3998 }
3999}
4000
4001/// Return the normalization status of the reader.
4002///
4003/// # UPSTREAM-PARITY
4004///
4005/// Upstream `xmlTextReaderNormalization` returns 1 when the reader performs
4006/// whitespace normalization (it always reports 1 unless the parser was
4007/// configured otherwise). The candidate normalizes attribute values per the
4008/// XML spec during parsing, so report 1.
4009///
4010/// ```c
4011/// int xmlTextReaderNormalization(xmlTextReaderPtr reader);
4012/// ```
4013///
4014/// # Safety
4015///
4016/// `reader` must be a valid pointer or NULL.
4017#[no_mangle]
4018pub const unsafe extern "C" fn xmlTextReaderNormalization(reader: *mut XmlTextReader) -> c_int {
4019 if reader.is_null() {
4020 return -1;
4021 }
4022 1
4023}
4024
4025/// Read the value of an attribute as a text node (attribute-value mode).
4026///
4027/// # UPSTREAM-PARITY
4028///
4029/// Upstream `xmlTextReaderReadAttributeValue` moves the reader so that the
4030/// value of the current attribute is available as a text node, returning 1
4031/// on success and 0 when already at the end. The candidate tree stores
4032/// attribute values directly on the attribute node, so the value is already
4033/// available via `xmlTextReaderValue`; report 1 when positioned on an
4034/// attribute with a value.
4035///
4036/// ```c
4037/// int xmlTextReaderReadAttributeValue(xmlTextReaderPtr reader);
4038/// ```
4039///
4040/// # Safety
4041///
4042/// `reader` must be a valid pointer or NULL.
4043#[no_mangle]
4044pub unsafe extern "C" fn xmlTextReaderReadAttributeValue(reader: *mut XmlTextReader) -> c_int {
4045 if reader.is_null() {
4046 return -1;
4047 }
4048 // SAFETY: reader is valid.
4049 unsafe {
4050 let r = &*reader;
4051 if r.node_type == ReaderNodeType::ATTRIBUTE && !r.cur_node.is_null() {
4052 1
4053 } else {
4054 0
4055 }
4056 }
4057}
4058
4059/// Read the content of the current node as a string.
4060///
4061/// # UPSTREAM-PARITY
4062///
4063/// Upstream `xmlTextReaderReadString` concatenates the text of the current
4064/// node's subtree (recursively) into one string. It behaves like
4065/// `xmlNodeGetContent` for the current node.
4066///
4067/// ```c
4068/// xmlChar *xmlTextReaderReadString(xmlTextReaderPtr reader);
4069/// ```
4070///
4071/// Returns a newly allocated string (free with `xmlFree`) or NULL.
4072///
4073/// # Safety
4074///
4075/// `reader` must be a valid pointer or NULL.
4076#[no_mangle]
4077pub unsafe extern "C" fn xmlTextReaderReadString(reader: *mut XmlTextReader) -> *mut xmlChar {
4078 if reader.is_null() {
4079 return ptr::null_mut();
4080 }
4081 // SAFETY: reader is valid; node owned by the document.
4082 unsafe {
4083 let node = (*reader).cur_node;
4084 if node.is_null() {
4085 return ptr::null_mut();
4086 }
4087 tree::node_get_content(node)
4088 }
4089}
4090
4091/// Read the inner XML of the current node as a string.
4092///
4093/// # UPSTREAM-PARITY
4094///
4095/// Upstream `xmlTextReaderReadInnerXml` serializes the children of the
4096/// current node. The candidate uses its serializer on the children list.
4097///
4098/// ```c
4099/// xmlChar *xmlTextReaderReadInnerXml(xmlTextReaderPtr reader);
4100/// ```
4101///
4102/// Returns a newly allocated string (free with `xmlFree`) or NULL.
4103///
4104/// # Safety
4105///
4106/// `reader` must be a valid pointer or NULL.
4107#[no_mangle]
4108pub unsafe extern "C" fn xmlTextReaderReadInnerXml(reader: *mut XmlTextReader) -> *mut xmlChar {
4109 if reader.is_null() {
4110 return ptr::null_mut();
4111 }
4112 // SAFETY: reader is valid; node owned by the document.
4113 unsafe {
4114 let node = (*reader).cur_node;
4115 if node.is_null() {
4116 return ptr::null_mut();
4117 }
4118 let buf = crate::xml::io::buf_create(-1);
4119 if buf.is_null() {
4120 return ptr::null_mut();
4121 }
4122 let mut child = (*node).children;
4123 while !child.is_null() {
4124 tree::serialize_node(child, buf, 0, 0);
4125 child = (*child).next;
4126 }
4127 let len = crate::xml::io::buf_length(buf) as usize;
4128 let content = crate::xml::io::buf_content(buf);
4129 if content.is_null() || len == 0 {
4130 crate::xml::io::buf_free(buf);
4131 return ptr::null_mut();
4132 }
4133 let out = xml_strdup(content);
4134 crate::xml::io::buf_free(buf);
4135 out
4136 }
4137}
4138
4139/// Read the outer XML of the current node as a string.
4140///
4141/// # UPSTREAM-PARITY
4142///
4143/// Upstream `xmlTextReaderReadOuterXml` serializes the current node itself.
4144///
4145/// ```c
4146/// xmlChar *xmlTextReaderReadOuterXml(xmlTextReaderPtr reader);
4147/// ```
4148///
4149/// Returns a newly allocated string (free with `xmlFree`) or NULL.
4150///
4151/// # Safety
4152///
4153/// `reader` must be a valid pointer or NULL.
4154#[no_mangle]
4155pub unsafe extern "C" fn xmlTextReaderReadOuterXml(reader: *mut XmlTextReader) -> *mut xmlChar {
4156 if reader.is_null() {
4157 return ptr::null_mut();
4158 }
4159 // SAFETY: reader is valid; node owned by the document.
4160 unsafe {
4161 let node = (*reader).cur_node;
4162 if node.is_null() {
4163 return ptr::null_mut();
4164 }
4165 let buf = crate::xml::io::buf_create(-1);
4166 if buf.is_null() {
4167 return ptr::null_mut();
4168 }
4169 tree::serialize_node(node, buf, 0, 0);
4170 let len = crate::xml::io::buf_length(buf) as usize;
4171 let content = crate::xml::io::buf_content(buf);
4172 if content.is_null() || len == 0 {
4173 crate::xml::io::buf_free(buf);
4174 return ptr::null_mut();
4175 }
4176 let out = xml_strdup(content);
4177 crate::xml::io::buf_free(buf);
4178 out
4179 }
4180}
4181
4182/// Return the standalone flag of the document being read.
4183///
4184/// # UPSTREAM-PARITY
4185///
4186/// Upstream `xmlTextReaderStandalone` returns the document's standalone
4187/// value (1 = standalone, 0 = not, -1 = no XML declaration / NULL reader).
4188///
4189/// ```c
4190/// int xmlTextReaderStandalone(xmlTextReaderPtr reader);
4191/// ```
4192///
4193/// # Safety
4194///
4195/// `reader` must be a valid pointer or NULL.
4196#[no_mangle]
4197pub unsafe extern "C" fn xmlTextReaderStandalone(reader: *mut XmlTextReader) -> c_int {
4198 if reader.is_null() {
4199 return -1;
4200 }
4201 // SAFETY: reader is valid; doc owned by the reader.
4202 unsafe {
4203 let doc = (*reader).doc;
4204 if doc.is_null() {
4205 return -1;
4206 }
4207 (*doc).standalone
4208 }
4209}
4210
4211/// Return the xml:lang of the current node.
4212///
4213/// # UPSTREAM-PARITY
4214///
4215/// Upstream `xmlTextReaderXmlLang` returns `xmlNodeGetLang(node)`: the
4216/// nearest `xml:lang` attribute on the node or an ancestor.
4217///
4218/// ```c
4219/// xmlChar *xmlTextReaderXmlLang(xmlTextReaderPtr reader);
4220/// ```
4221///
4222/// Returns a newly allocated string (free with `xmlFree`) or NULL.
4223///
4224/// # Safety
4225///
4226/// `reader` must be a valid pointer or NULL.
4227#[no_mangle]
4228pub unsafe extern "C" fn xmlTextReaderXmlLang(reader: *mut XmlTextReader) -> *mut xmlChar {
4229 if reader.is_null() {
4230 return ptr::null_mut();
4231 }
4232 // SAFETY: reader is valid; node owned by the document.
4233 unsafe {
4234 let mut node = (*reader).cur_node;
4235 while !node.is_null() {
4236 // walk the property list for xml:lang
4237 let mut prop = (*node).properties;
4238 while !prop.is_null() {
4239 if !(*prop).name.is_null() {
4240 let name = crate::xml::string::xmlstr_to_bytes((*prop).name);
4241 if name == b"lang" && !(*prop).ns.is_null() {
4242 let ns_href = crate::xml::string::xmlstr_to_bytes((*(*prop).ns).href);
4243 if ns_href == b"http://www.w3.org/XML/1998/namespace" {
4244 let v = (*prop).children;
4245 if !v.is_null() && !(*v).content.is_null() {
4246 return xml_strdup((*v).content);
4247 }
4248 }
4249 }
4250 }
4251 prop = (*prop).next;
4252 }
4253 node = (*node).parent;
4254 }
4255 ptr::null_mut()
4256 }
4257}
4258
4259// ═══════════════════════════════════════════════════════════════════════════════
4260// Tests
4261// ═══════════════════════════════════════════════════════════════════════════════
4262
4263#[cfg(test)]
4264mod tests {
4265 use super::*;
4266 use crate::abi::allocator::xmlFreeImpl;
4267 use core::ffi::c_void;
4268 use std::os::raw::c_char;
4269
4270 /// Helper: create a reader from a string.
4271 unsafe fn create_reader(xml: &str) -> *mut XmlTextReader {
4272 let bytes = xml.as_bytes();
4273 xmlReaderForMemory(
4274 bytes.as_ptr() as *const c_char,
4275 bytes.len() as c_int,
4276 ptr::null(),
4277 ptr::null(),
4278 0,
4279 )
4280 }
4281
4282 /// Helper: free a reader.
4283 unsafe fn free_reader(reader: *mut XmlTextReader) {
4284 if !reader.is_null() {
4285 xmlFreeTextReader(reader);
4286 }
4287 }
4288
4289 /// Helper: read through all nodes and collect their types and names.
4290 unsafe fn collect_nodes(reader: *mut XmlTextReader) -> Vec<(ReaderNodeType, String, i32)> {
4291 let mut result = Vec::new();
4292 loop {
4293 let ret = xmlTextReaderRead(reader);
4294 if ret <= 0 {
4295 break;
4296 }
4297 // SAFETY: reader is valid.
4298 let r = &*reader;
4299 let ntype = r.NodeType();
4300 let name = if r.name.is_null() {
4301 String::new()
4302 } else {
4303 xmlstr_to_string(r.name as *const xmlChar)
4304 };
4305 let depth = r.Depth();
4306 result.push((ntype, name, depth));
4307 }
4308 result
4309 }
4310
4311 // ─── Basic tests ───────────────────────────────────────────────────────
4312
4313 /// Creates a reader from a memory buffer and checks its initial state.
4314 ///
4315 /// # Safety
4316 ///
4317 /// - The reader is created from a static string literal that stays alive
4318 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4319 /// before it is dereferenced and is freed exactly once with
4320 /// `free_reader`.
4321 #[test]
4322 fn test_create_reader_from_memory() {
4323 unsafe {
4324 let reader = create_reader("<root/>");
4325 assert!(!reader.is_null());
4326 assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
4327 free_reader(reader);
4328 }
4329 }
4330
4331 /// Reads a simple document and verifies the event sequence.
4332 ///
4333 /// # Safety
4334 ///
4335 /// - The reader is created from a static string literal that stays alive
4336 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4337 /// before it is dereferenced and is freed exactly once with
4338 /// `free_reader`.
4339 #[test]
4340 fn test_read_simple_document() {
4341 unsafe {
4342 let reader = create_reader("<root><child>text</child></root>");
4343 assert!(!reader.is_null());
4344
4345 let nodes = collect_nodes(reader);
4346 // Expected sequence:
4347 // ELEMENT root (depth=0)
4348 // ELEMENT child (depth=1)
4349 // TEXT text (depth=2)
4350 // END_ELEMENT child (depth=1)
4351 // END_ELEMENT root (depth=0)
4352
4353 assert_eq!(nodes.len(), 5);
4354 assert_eq!(nodes[0], (ReaderNodeType::ELEMENT, "root".to_string(), 0));
4355 assert_eq!(nodes[1], (ReaderNodeType::ELEMENT, "child".to_string(), 1));
4356 // UPSTREAM-PARITY: text nodes report the fixed name "#text".
4357 assert_eq!(nodes[2], (ReaderNodeType::TEXT, "#text".to_string(), 2));
4358 assert_eq!(
4359 nodes[3],
4360 (ReaderNodeType::END_ELEMENT, "child".to_string(), 1)
4361 );
4362 assert_eq!(
4363 nodes[4],
4364 (ReaderNodeType::END_ELEMENT, "root".to_string(), 0)
4365 );
4366
4367 assert_eq!((*reader).ReadState(), ReadState::EOF);
4368 free_reader(reader);
4369 }
4370 }
4371
4372 /// SP-14.3.1-7 (fromStream_broken_stream): a document that is NOT
4373 /// complete when the input ends (an unterminated root) still delivers the
4374 /// completed prefix events — the root element and the comment — and the
4375 /// premature-EOF failure surfaces only on the read that runs past the
4376 /// last event, leaving the cursor on the last delivered node (upstream
4377 /// xmlTextReaderPushData's terminating xmlParseChunk).
4378 ///
4379 /// # Safety
4380 ///
4381 /// - The reader is created from a static string literal that stays alive
4382 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4383 /// before it is dereferenced and is freed exactly once with
4384 /// `free_reader`.
4385 #[test]
4386 fn test_read_incomplete_document_defers_eof_error() {
4387 unsafe {
4388 // Deliberately unterminated: <root><!--my comment--> (no </root>).
4389 let reader = create_reader("<root><!--my comment-->");
4390 assert!(!reader.is_null());
4391
4392 // read #1: the root element is delivered even though the document
4393 // is not complete.
4394 assert_eq!(xmlTextReaderRead(reader), 1);
4395 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4396 assert_eq!(xmlstr_to_bytes((*reader).Name()), b"root");
4397 assert_eq!((*reader).Depth(), 0);
4398
4399 // read #2: the comment.
4400 assert_eq!(xmlTextReaderRead(reader), 1);
4401 assert_eq!((*reader).NodeType(), ReaderNodeType::COMMENT);
4402 assert_eq!(xmlstr_to_bytes((*reader).Value()), b"my comment");
4403 assert_eq!((*reader).Depth(), 1);
4404
4405 // read #3: the parse needs more input than the source supplies;
4406 // the deferred EOF finalize reports the failure and the cursor
4407 // stays on the last delivered event (upstream
4408 // fromStream_broken_stream).
4409 assert_eq!(xmlTextReaderRead(reader), -1);
4410 assert_eq!((*reader).NodeType(), ReaderNodeType::COMMENT);
4411 assert_eq!((*reader).Depth(), 1);
4412
4413 // Later reads keep failing with the cursor frozen.
4414 assert_eq!(xmlTextReaderRead(reader), -1);
4415 assert_eq!((*reader).NodeType(), ReaderNodeType::COMMENT);
4416 assert_eq!((*reader).Depth(), 1);
4417
4418 free_reader(reader);
4419 }
4420 }
4421
4422 /// Verifies read state transitions from INITIALIZED to EOF.
4423 ///
4424 /// # Safety
4425 ///
4426 /// - The reader is created from a static string literal that stays alive
4427 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4428 /// before it is dereferenced and is freed exactly once with
4429 /// `free_reader`.
4430 #[test]
4431 fn test_read_state_transitions() {
4432 unsafe {
4433 let reader = create_reader("<root/>");
4434 assert!(!reader.is_null());
4435 assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
4436
4437 // First read.
4438 assert_eq!(xmlTextReaderRead(reader), 1);
4439 assert_eq!((*reader).ReadState(), ReadState::READING);
4440 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4441 assert_eq!((*reader).Depth(), 0);
4442
4443 // UPSTREAM-PARITY (oracle-verified 2.15.3): empty elements emit no
4444 // END_ELEMENT event — the second Read returns EOF directly.
4445 assert_eq!(xmlTextReaderRead(reader), 0);
4446 assert_eq!((*reader).ReadState(), ReadState::EOF);
4447
4448 free_reader(reader);
4449 }
4450 }
4451
4452 /// Verifies reader API entry points return error indicators for a NULL
4453 /// reader pointer.
4454 ///
4455 /// # Safety
4456 ///
4457 /// - NULL is passed only to reader API entry points that accept NULL and
4458 /// return error indicators without dereferencing the pointer.
4459 #[test]
4460 fn test_null_reader_returns_error() {
4461 unsafe {
4462 assert_eq!(xmlTextReaderRead(ptr::null_mut()), -1);
4463 assert_eq!(xmlTextReaderDepth(ptr::null_mut()), -1);
4464 assert_eq!(xmlTextReaderNodeType(ptr::null_mut()), -1);
4465 assert!(xmlTextReaderName(ptr::null_mut()).is_null());
4466 assert!(xmlTextReaderValue(ptr::null_mut()).is_null());
4467 assert_eq!(xmlTextReaderHasValue(ptr::null_mut()), 0);
4468 assert_eq!(xmlTextReaderIsEmptyElement(ptr::null_mut()), 0);
4469 assert_eq!(
4470 xmlTextReaderReadState(ptr::null_mut()),
4471 ReadState::ERROR as c_int
4472 );
4473 }
4474 }
4475
4476 /// Verifies freeing a NULL reader does not crash.
4477 ///
4478 /// # Safety
4479 ///
4480 /// - `xmlFreeTextReader` must tolerate a NULL pointer without
4481 /// dereferencing it.
4482 #[test]
4483 fn test_xmlFreeTextReader_null() {
4484 unsafe {
4485 // Should not crash.
4486 xmlFreeTextReader(ptr::null_mut());
4487 }
4488 }
4489
4490 /// Verifies `xmlTextReaderName` and `xmlTextReaderValue` results.
4491 ///
4492 /// # Safety
4493 ///
4494 /// - The `reader` pointer is asserted non-NULL before use and freed
4495 /// exactly once with `free_reader`.
4496 /// - `xmlTextReaderName` and `xmlTextReaderValue` return heap strings
4497 /// allocated via `xml_strdup`; each non-NULL result is freed exactly
4498 /// once with `xmlFreeImpl`.
4499 #[test]
4500 fn test_reader_name_and_value() {
4501 unsafe {
4502 let reader = create_reader("<root>hello</root>");
4503 assert!(!reader.is_null());
4504
4505 // Read root element.
4506 assert_eq!(xmlTextReaderRead(reader), 1);
4507 let name = xmlTextReaderName(reader);
4508 assert!(!name.is_null());
4509 assert_eq!(xmlstr_to_string(name), "root");
4510 xmlFreeImpl(name as *mut c_void);
4511
4512 assert_eq!(xmlTextReaderHasValue(reader), 0);
4513
4514 // Read text node.
4515 assert_eq!(xmlTextReaderRead(reader), 1);
4516 assert_eq!((*reader).NodeType(), ReaderNodeType::TEXT);
4517 assert_eq!((*reader).HasValue(), 1);
4518
4519 let val = xmlTextReaderValue(reader);
4520 assert!(!val.is_null());
4521 assert_eq!(xmlstr_to_string(val), "hello");
4522 xmlFreeImpl(val as *mut c_void);
4523
4524 free_reader(reader);
4525 }
4526 }
4527
4528 /// Verifies an empty element reports no END_ELEMENT event.
4529 ///
4530 /// # Safety
4531 ///
4532 /// - The reader is created from a static string literal that stays alive
4533 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4534 /// before it is dereferenced and is freed exactly once with
4535 /// `free_reader`.
4536 #[test]
4537 fn test_empty_element() {
4538 unsafe {
4539 let reader = create_reader("<empty/>");
4540 assert!(!reader.is_null());
4541
4542 assert_eq!(xmlTextReaderRead(reader), 1);
4543 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4544 assert_eq!((*reader).IsEmptyElement(), 1);
4545 assert_eq!((*reader).HasAttributes(), 0);
4546 assert_eq!((*reader).AttributeCount(), 0);
4547
4548 // UPSTREAM-PARITY (oracle-verified 2.15.3): empty elements emit
4549 // NO END_ELEMENT event; the next Read returns EOF.
4550 assert_eq!(xmlTextReaderRead(reader), 0);
4551 assert_eq!((*reader).ReadState(), ReadState::EOF);
4552
4553 free_reader(reader);
4554 }
4555 }
4556
4557 /// Verifies attribute counting on an element with two attributes.
4558 ///
4559 /// # Safety
4560 ///
4561 /// - The reader is created from a static string literal that stays alive
4562 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4563 /// before it is dereferenced and is freed exactly once with
4564 /// `free_reader`.
4565 #[test]
4566 fn test_element_with_attributes() {
4567 unsafe {
4568 let reader = create_reader(r#"<root a="1" b="2"/>"#);
4569 assert!(!reader.is_null());
4570
4571 assert_eq!(xmlTextReaderRead(reader), 1);
4572 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4573 assert_eq!((*reader).HasAttributes(), 1);
4574
4575 // We know the attribute count if we've built the events properly.
4576 // The count_attributes checks the element's properties list.
4577 let attrs = xmlTextReaderAttributeCount(reader);
4578 assert_eq!(attrs, 2);
4579
4580 free_reader(reader);
4581 }
4582 }
4583
4584 /// Verifies attribute navigation (first, next, by name, by index).
4585 ///
4586 /// # Safety
4587 ///
4588 /// - The reader is created from a static string literal that stays alive
4589 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4590 /// before it is dereferenced and is freed exactly once with
4591 /// `free_reader`.
4592 /// - The pointers returned by `xmlTextReaderConstName` and
4593 /// `xmlTextReaderConstValue` are borrowed from the reader and are not
4594 /// freed by the test.
4595 #[test]
4596 fn test_attribute_navigation() {
4597 unsafe {
4598 let reader = create_reader(r#"<root a="1" b="2"></root>"#);
4599 assert!(!reader.is_null());
4600
4601 // Position on root element.
4602 assert_eq!(xmlTextReaderRead(reader), 1);
4603 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4604
4605 // Move to first attribute.
4606 assert_eq!(xmlTextReaderMoveToFirstAttribute(reader), 1);
4607 assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
4608
4609 let name = xmlTextReaderConstName(reader);
4610 assert!(!name.is_null());
4611 assert_eq!(xmlstr_to_bytes(name), b"a");
4612
4613 let val = xmlTextReaderConstValue(reader);
4614 assert!(!val.is_null());
4615 assert_eq!(xmlstr_to_bytes(val), b"1");
4616
4617 // Move to next attribute.
4618 assert_eq!(xmlTextReaderMoveToNextAttribute(reader), 1);
4619 let name = xmlTextReaderConstName(reader);
4620 assert!(!name.is_null());
4621 assert_eq!(xmlstr_to_bytes(name), b"b");
4622 let val = xmlTextReaderConstValue(reader);
4623 assert!(!val.is_null());
4624 assert_eq!(xmlstr_to_bytes(val), b"2");
4625
4626 // No more attributes.
4627 assert_eq!(xmlTextReaderMoveToNextAttribute(reader), 0);
4628
4629 // Move back to element.
4630 assert_eq!(xmlTextReaderMoveToElement(reader), 1);
4631 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4632
4633 // Move to attribute by name.
4634 assert_eq!(
4635 xmlTextReaderMoveToAttribute(reader, b"a\0" as *const u8 as *const xmlChar),
4636 1
4637 );
4638 assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
4639
4640 // Move to attribute by index.
4641 assert_eq!(xmlTextReaderMoveToElement(reader), 1);
4642 assert_eq!(xmlTextReaderMoveToAttributeNo(reader, 1), 1);
4643 assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
4644
4645 free_reader(reader);
4646 }
4647 }
4648
4649 /// Verifies attribute lookup by name and by index.
4650 ///
4651 /// # Safety
4652 ///
4653 /// - The `reader` pointer is asserted non-NULL before use and freed
4654 /// exactly once with `free_reader`.
4655 /// - `xmlTextReaderGetAttribute` and `xmlTextReaderGetAttributeNo` return
4656 /// heap strings; each non-NULL result is freed exactly once with
4657 /// `xmlFreeImpl`.
4658 #[test]
4659 fn test_get_attribute() {
4660 unsafe {
4661 let reader = create_reader(r#"<root a="hello" b="world"/>"#);
4662 assert!(!reader.is_null());
4663
4664 assert_eq!(xmlTextReaderRead(reader), 1);
4665
4666 // Get attribute by name.
4667 let val = xmlTextReaderGetAttribute(reader, b"a\0" as *const u8 as *const xmlChar);
4668 assert!(!val.is_null());
4669 assert_eq!(xmlstr_to_bytes(val), b"hello");
4670 xmlFreeImpl(val as *mut c_void);
4671
4672 let val = xmlTextReaderGetAttribute(reader, b"b\0" as *const u8 as *const xmlChar);
4673 assert!(!val.is_null());
4674 assert_eq!(xmlstr_to_bytes(val), b"world");
4675 xmlFreeImpl(val as *mut c_void);
4676
4677 // Non-existent attribute.
4678 let val = xmlTextReaderGetAttribute(reader, b"c\0" as *const u8 as *const xmlChar);
4679 assert!(val.is_null());
4680
4681 // Get attribute by index.
4682 let val = xmlTextReaderGetAttributeNo(reader, 0);
4683 assert!(!val.is_null());
4684 assert_eq!(xmlstr_to_bytes(val), b"hello");
4685 xmlFreeImpl(val as *mut c_void);
4686
4687 let val = xmlTextReaderGetAttributeNo(reader, 1);
4688 assert!(!val.is_null());
4689 assert_eq!(xmlstr_to_bytes(val), b"world");
4690 xmlFreeImpl(val as *mut c_void);
4691
4692 let val = xmlTextReaderGetAttributeNo(reader, 2);
4693 assert!(val.is_null());
4694
4695 free_reader(reader);
4696 }
4697 }
4698
4699 /// Verifies depth tracking across nested elements.
4700 ///
4701 /// # Safety
4702 ///
4703 /// - The reader is created from a static string literal that stays alive
4704 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4705 /// before it is dereferenced and is freed exactly once with
4706 /// `free_reader`.
4707 #[test]
4708 fn test_depth_tracking() {
4709 unsafe {
4710 let reader = create_reader("<a><b><c/></b></a>");
4711 assert!(!reader.is_null());
4712
4713 let nodes = collect_nodes(reader);
4714 // UPSTREAM-PARITY (oracle-verified 2.15.3): empty elements emit no
4715 // END_ELEMENT event.
4716 // ELEMENT a (0), ELEMENT b (1), ELEMENT c (2),
4717 // END_ELEMENT b (1), END_ELEMENT a (0)
4718 assert_eq!(nodes.len(), 5);
4719 assert_eq!(nodes[0].2, 0); // a depth 0
4720 assert_eq!(nodes[1].2, 1); // b depth 1
4721 assert_eq!(nodes[2].2, 2); // c depth 2
4722 assert_eq!(nodes[3].2, 1); // END b depth 1
4723 assert_eq!(nodes[4].2, 0); // END a depth 0
4724
4725 free_reader(reader);
4726 }
4727 }
4728
4729 /// Verifies traversal of multiple sibling elements.
4730 ///
4731 /// # Safety
4732 ///
4733 /// - The reader is created from a static string literal that stays alive
4734 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4735 /// before it is dereferenced and is freed exactly once with
4736 /// `free_reader`.
4737 #[test]
4738 fn test_multiple_siblings() {
4739 unsafe {
4740 let reader = create_reader("<root><a>A</a><b>B</b><c>C</c></root>");
4741 assert!(!reader.is_null());
4742
4743 let nodes = collect_nodes(reader);
4744 // ELEMENT root(0), ELEMENT a(1), TEXT(2), END a(1),
4745 // ELEMENT b(1), TEXT(2), END b(1),
4746 // ELEMENT c(1), TEXT(2), END c(1),
4747 // END root(0)
4748 assert_eq!(nodes.len(), 11);
4749
4750 // Check the sibling elements.
4751 assert_eq!(nodes[1], (ReaderNodeType::ELEMENT, "a".to_string(), 1));
4752 assert_eq!(nodes[4], (ReaderNodeType::ELEMENT, "b".to_string(), 1));
4753 assert_eq!(nodes[7], (ReaderNodeType::ELEMENT, "c".to_string(), 1));
4754
4755 free_reader(reader);
4756 }
4757 }
4758
4759 /// Verifies `xmlTextReaderNext` semantics against the upstream streaming
4760 /// reader (xmlreader.c xmlTextReaderNext): from a NON-element node Next
4761 /// degrades to a plain Read — one step forward in document order, which
4762 /// may land on the parent's END_ELEMENT — and only an element START is
4763 /// skipped (subtree + END events) to the next sibling. On exhaustion the
4764 /// cursor is cleared (EOF).
4765 ///
4766 /// # Safety
4767 ///
4768 /// - The reader is created from a static string literal that stays alive
4769 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4770 /// before it is dereferenced and is freed exactly once with
4771 /// `free_reader`.
4772 /// - The `name` field read through `(*reader)` is borrowed from the
4773 /// reader and is not freed.
4774 #[test]
4775 fn test_next_skip_to_sibling() {
4776 unsafe {
4777 let reader = create_reader("<root><a>A</a><b>B</b><c>C</c></root>");
4778 assert!(!reader.is_null());
4779
4780 // Read to first node (root element).
4781 assert_eq!(xmlTextReaderRead(reader), 1);
4782 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4783
4784 // Read to a.
4785 assert_eq!(xmlTextReaderRead(reader), 1);
4786 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4787 assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "a");
4788
4789 // Read to text of a.
4790 assert_eq!(xmlTextReaderRead(reader), 1);
4791 assert_eq!((*reader).NodeType(), ReaderNodeType::TEXT);
4792
4793 // Next from a TEXT node is a plain Read: it lands on the END of
4794 // the parent element (upstream xmlTextReaderNext, oracle-verified
4795 // via XMLReader 010/next_basic).
4796 assert_eq!(xmlTextReaderNext(reader), 1);
4797 assert_eq!((*reader).NodeType(), ReaderNodeType::END_ELEMENT);
4798 assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "a");
4799
4800 // Next again from the END event — a single step to element b.
4801 assert_eq!(xmlTextReaderNext(reader), 1);
4802 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4803 assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "b");
4804
4805 // Next from the b element start skips its subtree to c.
4806 assert_eq!(xmlTextReaderNext(reader), 1);
4807 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4808 assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "c");
4809
4810 // Next again — the traversal is exhausted and the cursor clears.
4811 assert_eq!(xmlTextReaderNext(reader), 0);
4812 assert_eq!((*reader).NodeType(), ReaderNodeType::NONE);
4813
4814 free_reader(reader);
4815 }
4816 }
4817
4818 /// Verifies comment and processing-instruction nodes are reported.
4819 ///
4820 /// # Safety
4821 ///
4822 /// - The static NUL-terminated `xml` buffer stays valid for the
4823 /// `xmlReaderForMemory` call; the returned `reader` is asserted non-NULL
4824 /// before use and is freed exactly once with `free_reader`.
4825 #[test]
4826 fn test_comment_and_pi_nodes() {
4827 unsafe {
4828 let xml = b"<?pi target?><root><!-- comment -->text</root>\0";
4829 let reader = xmlReaderForMemory(
4830 xml.as_ptr() as *const c_char,
4831 (xml.len() - 1) as c_int,
4832 ptr::null(),
4833 ptr::null(),
4834 0,
4835 );
4836 assert!(!reader.is_null());
4837
4838 let nodes = collect_nodes(reader);
4839 // PI, ELEMENT root, COMMENT, TEXT, END_ELEMENT root
4840 // Note: PI appears as PROCESSING_INSTRUCTION node.
4841 assert!(!nodes.is_empty(), "no nodes collected: {:?}", nodes);
4842
4843 // Check PI.
4844 assert_eq!(
4845 nodes[0].0,
4846 ReaderNodeType::PROCESSING_INSTRUCTION,
4847 "expected PI at nodes[0], got {:?} name={}",
4848 nodes[0].0,
4849 nodes[0].1
4850 );
4851 assert_eq!(
4852 nodes[0].0,
4853 ReaderNodeType::PROCESSING_INSTRUCTION,
4854 "expected PI at nodes[0], got {:?} name={}",
4855 nodes[0].0,
4856 nodes[0].1
4857 );
4858
4859 // Check root element.
4860 let root_idx = nodes
4861 .iter()
4862 .position(|(t, n, _)| *t == ReaderNodeType::ELEMENT && n == "root");
4863 assert!(
4864 root_idx.is_some(),
4865 "no ELEMENT root found in nodes: {:?}",
4866 nodes
4867 .iter()
4868 .map(|(t, n, _)| format!("{:?}:{}", t, n))
4869 .collect::<Vec<_>>()
4870 );
4871
4872 // Check comment.
4873 let comment_idx = nodes
4874 .iter()
4875 .position(|(t, _, _)| *t == ReaderNodeType::COMMENT);
4876 assert!(comment_idx.is_some(), "no COMMENT found");
4877
4878 // Check text.
4879 let text_idx = nodes
4880 .iter()
4881 .position(|(t, _, _)| *t == ReaderNodeType::TEXT);
4882 assert!(text_idx.is_some(), "no TEXT found");
4883
4884 free_reader(reader);
4885 }
4886 }
4887
4888 /// Verifies `xmlTextReaderLocalName` returns the local name.
4889 ///
4890 /// # Safety
4891 ///
4892 /// - The `reader` pointer is asserted non-NULL before use and freed
4893 /// exactly once with `free_reader`.
4894 /// - `xmlTextReaderLocalName` returns a heap string that is freed exactly
4895 /// once with `xmlFreeImpl`.
4896 #[test]
4897 fn test_local_name() {
4898 unsafe {
4899 // We need a namespace-aware element. For now, test without namespace.
4900 let reader = create_reader("<root/>");
4901 assert!(!reader.is_null());
4902
4903 assert_eq!(xmlTextReaderRead(reader), 1);
4904 let local = xmlTextReaderLocalName(reader);
4905 assert!(!local.is_null());
4906 assert_eq!(xmlstr_to_bytes(local), b"root");
4907 xmlFreeImpl(local as *mut c_void);
4908
4909 free_reader(reader);
4910 }
4911 }
4912
4913 /// Verifies the base URI is NULL for memory-created readers.
4914 ///
4915 /// # Safety
4916 ///
4917 /// - The reader is created from a static string literal that stays alive
4918 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4919 /// before it is dereferenced and is freed exactly once with
4920 /// `free_reader`.
4921 #[test]
4922 fn test_base_uri() {
4923 unsafe {
4924 let reader = create_reader("<root/>");
4925 assert!(!reader.is_null());
4926
4927 assert_eq!(xmlTextReaderRead(reader), 1);
4928 // Base URI should be NULL for memory-created readers.
4929 let uri = xmlTextReaderBaseUri(reader);
4930 assert!(uri.is_null());
4931
4932 free_reader(reader);
4933 }
4934 }
4935
4936 /// Verifies namespace prefix lookup on the reader.
4937 ///
4938 /// # Safety
4939 ///
4940 /// - The `reader` pointer is asserted non-NULL before use and freed
4941 /// exactly once with `free_reader`.
4942 /// - `xmlTextReaderLookupNamespace` returns a heap string; the non-NULL
4943 /// result is freed exactly once with `xmlFreeImpl`.
4944 #[test]
4945 fn test_lookup_namespace() {
4946 unsafe {
4947 let reader = create_reader(r#"<root xmlns:ns="http://example.com"><ns:child/></root>"#);
4948 assert!(!reader.is_null());
4949
4950 // Read to root.
4951 assert_eq!(xmlTextReaderRead(reader), 1);
4952 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4953
4954 // Read to child (ns:child).
4955 assert_eq!(xmlTextReaderRead(reader), 1);
4956
4957 // Lookup the "ns" prefix.
4958 let uri = xmlTextReaderLookupNamespace(reader, b"ns\0" as *const u8 as *const xmlChar);
4959 assert!(!uri.is_null());
4960 assert_eq!(xmlstr_to_bytes(uri), b"http://example.com");
4961 xmlFreeImpl(uri as *mut c_void);
4962
4963 // Lookup default namespace (NULL prefix).
4964 let uri = xmlTextReaderLookupNamespace(reader, ptr::null());
4965 assert!(uri.is_null());
4966
4967 // Lookup non-existent prefix.
4968 let uri = xmlTextReaderLookupNamespace(
4969 reader,
4970 b"nonexistent\0" as *const u8 as *const xmlChar,
4971 );
4972 assert!(uri.is_null());
4973
4974 free_reader(reader);
4975 }
4976 }
4977
4978 /// Verifies parser property get/set round trips and error codes.
4979 ///
4980 /// # Safety
4981 ///
4982 /// - The reader is created from a static string literal that stays alive
4983 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4984 /// before it is dereferenced and is freed exactly once with
4985 /// `free_reader`.
4986 #[test]
4987 fn test_parser_properties() {
4988 unsafe {
4989 let reader = create_reader("<root/>");
4990 assert!(!reader.is_null());
4991
4992 // Get default properties.
4993 assert_eq!(xmlTextReaderGetParserProp(reader, 1), 0); // LOADDTD
4994 assert_eq!(xmlTextReaderGetParserProp(reader, 2), 0); // DEFAULTATTRS
4995 assert_eq!(xmlTextReaderGetParserProp(reader, 3), 0); // VALIDATE
4996 assert_eq!(xmlTextReaderGetParserProp(reader, 4), 0); // SUBST_ENTITIES
4997
4998 // Set and verify.
4999 assert_eq!(xmlTextReaderSetParserProp(reader, 1, 1), 0);
5000 assert_eq!(xmlTextReaderGetParserProp(reader, 1), 1);
5001
5002 assert_eq!(xmlTextReaderSetParserProp(reader, 4, 1), 0);
5003 assert_eq!(xmlTextReaderGetParserProp(reader, 4), 1);
5004
5005 // Invalid property.
5006 assert_eq!(xmlTextReaderGetParserProp(reader, 99), -1);
5007 assert_eq!(xmlTextReaderSetParserProp(reader, 99, 1), -1);
5008
5009 free_reader(reader);
5010 }
5011 }
5012
5013 /// Verifies the current document is available after the first read.
5014 ///
5015 /// # Safety
5016 ///
5017 /// - The reader is created from a static string literal that stays alive
5018 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5019 /// before it is dereferenced and is freed exactly once with
5020 /// `free_reader`.
5021 #[test]
5022 fn test_current_doc() {
5023 unsafe {
5024 let reader = create_reader("<root/>");
5025 assert!(!reader.is_null());
5026
5027 // Before reading, doc should be null.
5028 assert!((*reader).CurrentDoc().is_null());
5029
5030 // After reading, doc should be available.
5031 assert_eq!(xmlTextReaderRead(reader), 1);
5032 let doc = xmlTextReaderCurrentDoc(reader);
5033 assert!(!doc.is_null());
5034
5035 free_reader(reader);
5036 }
5037 }
5038
5039 /// Verifies a reader can be freed after reading to EOF.
5040 ///
5041 /// # Safety
5042 ///
5043 /// - The reader is created from a static string literal that stays alive
5044 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5045 /// before it is dereferenced and is freed exactly once with
5046 /// `free_reader`.
5047 #[test]
5048 fn test_free_reader_after_read() {
5049 unsafe {
5050 let reader = create_reader("<root><child/></root>");
5051 assert!(!reader.is_null());
5052
5053 // Read through the document.
5054 while xmlTextReaderRead(reader) > 0 {}
5055 assert_eq!((*reader).ReadState(), ReadState::EOF);
5056
5057 // Free should not crash.
5058 free_reader(reader);
5059 }
5060 }
5061
5062 /// Verifies a NULL buffer with a non-zero size is rejected.
5063 ///
5064 /// # Safety
5065 ///
5066 /// - A NULL buffer with a non-zero size must be rejected by
5067 /// `xmlReaderForMemory` without reading any memory.
5068 #[test]
5069 fn test_reader_for_memory_null_buffer() {
5070 unsafe {
5071 let reader = xmlReaderForMemory(ptr::null(), 10, ptr::null(), ptr::null(), 0);
5072 assert!(reader.is_null());
5073 }
5074 }
5075
5076 /// Verifies a zero size makes `xmlReaderForMemory` return NULL.
5077 ///
5078 /// # Safety
5079 ///
5080 /// - The static buffer is not read: a size of 0 must make
5081 /// `xmlReaderForMemory` return a NULL reader.
5082 #[test]
5083 fn test_reader_for_memory_empty_size() {
5084 unsafe {
5085 let data = b"<root/>";
5086 let reader = xmlReaderForMemory(
5087 data.as_ptr() as *const c_char,
5088 0,
5089 ptr::null(),
5090 ptr::null(),
5091 0,
5092 );
5093 assert!(reader.is_null());
5094 }
5095 }
5096
5097 /// Verifies a nonexistent file yields a NULL reader.
5098 ///
5099 /// # Safety
5100 ///
5101 /// - The static NUL-terminated filename stays valid for the
5102 /// `xmlReaderForFile` call; a nonexistent file must yield a NULL reader
5103 /// without reading.
5104 #[test]
5105 fn test_reader_for_file_not_found() {
5106 unsafe {
5107 let filename = b"/nonexistent/file.xml\0" as *const u8 as *const c_char;
5108 let reader = xmlReaderForFile(filename, ptr::null(), 0);
5109 assert!(reader.is_null());
5110 }
5111 }
5112
5113 /// Verifies const name and value pointers borrowed from the reader.
5114 ///
5115 /// # Safety
5116 ///
5117 /// - The `reader` pointer is asserted non-NULL before use and freed
5118 /// exactly once with `free_reader`.
5119 /// - The pointers returned by `xmlTextReaderConstName` and
5120 /// `xmlTextReaderConstValue` are borrowed from the reader and must not
5121 /// be freed by the caller.
5122 #[test]
5123 fn test_const_name_and_value() {
5124 unsafe {
5125 let reader = create_reader("<root>text</root>");
5126 assert!(!reader.is_null());
5127
5128 // Root element.
5129 assert_eq!(xmlTextReaderRead(reader), 1);
5130 let cname = xmlTextReaderConstName(reader);
5131 assert!(!cname.is_null());
5132 assert_eq!(xmlstr_to_bytes(cname), b"root");
5133
5134 // Text node.
5135 assert_eq!(xmlTextReaderRead(reader), 1);
5136 let cval = xmlTextReaderConstValue(reader);
5137 assert!(!cval.is_null());
5138 assert_eq!(xmlstr_to_bytes(cval), b"text");
5139
5140 free_reader(reader);
5141 }
5142 }
5143
5144 /// Reads a complex nested document and counts node types.
5145 ///
5146 /// # Safety
5147 ///
5148 /// - The reader is created from a static string literal that stays alive
5149 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5150 /// before it is dereferenced and is freed exactly once with
5151 /// `free_reader`.
5152 #[test]
5153 fn test_complex_nested_document() {
5154 unsafe {
5155 let xml = r#"<?xml version="1.0"?>
5156<library>
5157 <book id="1">
5158 <title>XML Fundamentals</title>
5159 <author>John Doe</author>
5160 </book>
5161 <book id="2">
5162 <title>XSLT Recipes</title>
5163 <author>Jane Smith</author>
5164 </book>
5165</library>"#;
5166
5167 let reader = create_reader(xml);
5168 assert!(!reader.is_null());
5169
5170 let mut element_count = 0;
5171 let mut end_element_count = 0;
5172 let mut text_count = 0;
5173 let mut pi_count = 0;
5174
5175 loop {
5176 let ret = xmlTextReaderRead(reader);
5177 if ret <= 0 {
5178 break;
5179 }
5180 match (*reader).NodeType() {
5181 ReaderNodeType::ELEMENT => element_count += 1,
5182 ReaderNodeType::END_ELEMENT => end_element_count += 1,
5183 ReaderNodeType::TEXT => text_count += 1,
5184 ReaderNodeType::PROCESSING_INSTRUCTION => pi_count += 1,
5185 _ => {}
5186 }
5187 }
5188
5189 // Elements: library, book(2), title(2), author(2) = 7
5190 assert_eq!(element_count, 7);
5191 // End elements: same count as elements
5192 assert_eq!(end_element_count, 7);
5193 // Text nodes: one per title and author = 4
5194 assert_eq!(text_count, 4);
5195 // UPSTREAM-PARITY: XML declaration (<?xml ...?>) is NOT stored as
5196 // a PI node in the tree. It is consumed by the parser and stored
5197 // in the document's version/encoding fields. Only <?pi ...?> nodes
5198 // (processing instructions) appear as XML_PI_NODE in the tree.
5199 assert_eq!(pi_count, 0);
5200
5201 free_reader(reader);
5202 }
5203 }
5204
5205 /// Verifies `xmlTextReaderSetup` reinitializes a used reader.
5206 ///
5207 /// # Safety
5208 ///
5209 /// - The `reader` pointer is asserted non-NULL before use and freed
5210 /// exactly once with `free_reader`.
5211 /// - `xmlTextReaderSetup` with a NULL input must reset the reader without
5212 /// dereferencing the NULL input.
5213 #[test]
5214 fn test_setup_reinitialize() {
5215 unsafe {
5216 let reader = create_reader("<root/>");
5217 assert!(!reader.is_null());
5218
5219 // Read through.
5220 assert_eq!(xmlTextReaderRead(reader), 1);
5221 assert_eq!((*reader).ReadState(), ReadState::READING);
5222
5223 // Setup with new input (simulate re-initialization).
5224 // For this test, we just verify the setup function exists and
5225 // handles a NULL input gracefully (resetting the reader).
5226 assert_eq!(
5227 xmlTextReaderSetup(reader, ptr::null_mut(), ptr::null(), ptr::null(), 0),
5228 0
5229 );
5230 assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
5231
5232 free_reader(reader);
5233 }
5234 }
5235
5236 /// Verifies attribute queries on non-element and attribute-less nodes.
5237 ///
5238 /// # Safety
5239 ///
5240 /// - The reader is created from a static string literal that stays alive
5241 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5242 /// before it is dereferenced and is freed exactly once with
5243 /// `free_reader`.
5244 #[test]
5245 fn test_has_attributes_on_non_element() {
5246 unsafe {
5247 let reader = create_reader("<root>text</root>");
5248 assert!(!reader.is_null());
5249
5250 // Position on text node.
5251 assert_eq!(xmlTextReaderRead(reader), 1); // root element
5252 assert_eq!((*reader).HasAttributes(), 0); // 0 attributes on root
5253 assert_eq!(xmlTextReaderRead(reader), 1); // text
5254 assert_eq!((*reader).HasAttributes(), 0);
5255
5256 free_reader(reader);
5257 }
5258 }
5259
5260 /// Verifies `xmlTextReaderPrev` fails after EOF.
5261 ///
5262 /// # Safety
5263 ///
5264 /// - The reader is created from a static string literal that stays alive
5265 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5266 /// before it is dereferenced and is freed exactly once with
5267 /// `free_reader`.
5268 #[test]
5269 fn test_prev_sibling() {
5270 unsafe {
5271 let reader = create_reader("<root><a/><b/><c/></root>");
5272 assert!(!reader.is_null());
5273
5274 // Read through the document.
5275 while xmlTextReaderRead(reader) > 0 {
5276 // Skip to END_ELEMENT root or beyond.
5277 }
5278
5279 // Can't go prev after EOF.
5280 assert_eq!(xmlTextReaderPrev(reader), -1);
5281
5282 free_reader(reader);
5283 }
5284 }
5285
5286 /// Verifies moving to an attribute index fails on an element without
5287 /// attributes.
5288 ///
5289 /// # Safety
5290 ///
5291 /// - The reader is created from a static string literal that stays alive
5292 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5293 /// before it is dereferenced and is freed exactly once with
5294 /// `free_reader`.
5295 #[test]
5296 fn test_move_to_attribute_no_not_on_element() {
5297 unsafe {
5298 let reader = create_reader("<root>text</root>");
5299 assert!(!reader.is_null());
5300
5301 assert_eq!(xmlTextReaderRead(reader), 1); // root
5302 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
5303
5304 // Move to non-existent attribute index.
5305 assert_eq!(xmlTextReaderMoveToAttributeNo(reader, 0), 0);
5306
5307 free_reader(reader);
5308 }
5309 }
5310
5311 /// Verifies namespaced attribute lookup with a NULL namespace URI.
5312 ///
5313 /// # Safety
5314 ///
5315 /// - The `reader` pointer is asserted non-NULL before use and freed
5316 /// exactly once with `free_reader`.
5317 /// - `xmlTextReaderGetAttributeNs` returns a heap string; the non-NULL
5318 /// result is freed exactly once with `xmlFreeImpl`.
5319 #[test]
5320 fn test_get_attribute_ns() {
5321 unsafe {
5322 let reader = create_reader(r#"<root a="1" b="2"/>"#);
5323 assert!(!reader.is_null());
5324
5325 assert_eq!(xmlTextReaderRead(reader), 1);
5326
5327 // Get attribute by local name only (namespaceURI is NULL).
5328 let val = xmlTextReaderGetAttributeNs(
5329 reader,
5330 b"a\0" as *const u8 as *const xmlChar,
5331 ptr::null(),
5332 );
5333 assert!(!val.is_null());
5334 assert_eq!(xmlstr_to_bytes(val), b"1");
5335 xmlFreeImpl(val as *mut c_void);
5336
5337 free_reader(reader);
5338 }
5339 }
5340
5341 /// Verifies mixed content (text and elements) event ordering.
5342 ///
5343 /// # Safety
5344 ///
5345 /// - The reader is created from a static string literal that stays alive
5346 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5347 /// before it is dereferenced and is freed exactly once with
5348 /// `free_reader`.
5349 #[test]
5350 fn test_mixed_content() {
5351 unsafe {
5352 let reader = create_reader("<root>before<child/>after</root>");
5353 assert!(!reader.is_null());
5354
5355 let nodes = collect_nodes(reader);
5356 // UPSTREAM-PARITY (oracle-verified 2.15.3): empty elements emit no
5357 // END_ELEMENT event.
5358 // ELEMENT root(0), TEXT "before"(1), ELEMENT child(1),
5359 // TEXT "after"(1), END_ELEMENT root(0)
5360 assert_eq!(nodes.len(), 5);
5361 assert_eq!(nodes[0], (ReaderNodeType::ELEMENT, "root".to_string(), 0));
5362 assert_eq!(nodes[1].0, ReaderNodeType::TEXT);
5363 assert_eq!(nodes[2], (ReaderNodeType::ELEMENT, "child".to_string(), 1));
5364 assert_eq!(nodes[3].0, ReaderNodeType::TEXT);
5365
5366 free_reader(reader);
5367 }
5368 }
5369
5370 /// Verifies malformed XML makes reading fail gracefully.
5371 ///
5372 /// # Safety
5373 ///
5374 /// - The static `data` buffer is valid for the 7-byte `xmlReaderForMemory`
5375 /// read; the returned `reader` is asserted non-NULL before being read
5376 /// and is freed exactly once with `free_reader`.
5377 #[test]
5378 fn test_error_handling_invalid_xml() {
5379 unsafe {
5380 // Malformed XML: `<root><` is a completed `<root>` start tag
5381 // followed by a lone `<` at end of input — an invalid element
5382 // name. UPSTREAM-PARITY (oracle-verified 2.15.3 xmlreader.c):
5383 // the first xmlTextReaderRead delivers the completed `<root>`
5384 // element node (returns 1); the second read runs past it and
5385 // surfaces the trailing "StartTag: invalid element name"
5386 // diagnostic (returns -1).
5387 let data = b"<root><\0" as *const u8 as *const c_char;
5388 let reader = xmlReaderForMemory(data, 7, ptr::null(), ptr::null(), 0);
5389 assert!(!reader.is_null());
5390
5391 // First read delivers the `<root>` element node.
5392 let ret = xmlTextReaderRead(reader);
5393 assert_eq!(ret, 1);
5394
5395 // Second read fails on the trailing invalid element name.
5396 let ret = xmlTextReaderRead(reader);
5397 assert_eq!(ret, -1);
5398
5399 free_reader(reader);
5400 }
5401 }
5402
5403 /// Verifies parser options are stored and honored by the reader.
5404 ///
5405 /// # Safety
5406 ///
5407 /// - The static NUL-terminated `data` buffer stays valid for the
5408 /// `xmlReaderForMemory` call; the returned `reader` is asserted non-NULL
5409 /// before dereferencing its `options` field and before
5410 /// `xmlTextReaderRead`, and is freed exactly once with `free_reader`.
5411 #[test]
5412 fn test_reader_with_options() {
5413 unsafe {
5414 let data = b"<root/>\0" as *const u8 as *const c_char;
5415 let reader = xmlReaderForMemory(
5416 data,
5417 7,
5418 ptr::null(),
5419 ptr::null(),
5420 XML_PARSE_NOENT | XML_PARSE_DTDLOAD,
5421 );
5422 assert!(!reader.is_null());
5423
5424 // Verify options were set.
5425 assert_eq!((*reader).options & XML_PARSE_NOENT, XML_PARSE_NOENT);
5426 assert_eq!((*reader).options & XML_PARSE_DTDLOAD, XML_PARSE_DTDLOAD);
5427
5428 assert_eq!(xmlTextReaderRead(reader), 1);
5429 free_reader(reader);
5430 }
5431 }
5432
5433 /// Verifies reading a document from an open file descriptor.
5434 ///
5435 /// # Safety
5436 ///
5437 /// - The descriptor returned by `libc::open` must be a valid open
5438 /// descriptor passed to `xmlReaderForFd`, which reads from it; the
5439 /// returned `reader` is asserted non-NULL before use, freed with
5440 /// `free_reader`, and the descriptor is closed afterwards.
5441 #[test]
5442 fn test_reader_for_fd() {
5443 unsafe {
5444 // Create a temp file and test xmlReaderForFd.
5445 let tmp_path = "/tmp/libxml_rs_test_reader_fd.xml";
5446 let tmp_cstr = std::ffi::CString::new(tmp_path).unwrap();
5447 let content = b"<root><data/></root>";
5448 let fd = libc::open(
5449 tmp_cstr.as_ptr(),
5450 libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC,
5451 0o644,
5452 );
5453 assert!(fd >= 0);
5454 libc::write(fd, content.as_ptr() as *const c_void, content.len());
5455 libc::close(fd);
5456
5457 // Open for reading.
5458 let fd = libc::open(tmp_cstr.as_ptr(), libc::O_RDONLY, 0);
5459 assert!(fd >= 0);
5460
5461 let reader = xmlReaderForFd(fd, ptr::null(), ptr::null(), 0);
5462 assert!(!reader.is_null());
5463
5464 let nodes = collect_nodes(reader);
5465 // UPSTREAM-PARITY (oracle-verified 2.15.3): `<data/>` is empty, so
5466 // it contributes no END_ELEMENT: root, data, END root.
5467 assert_eq!(nodes.len(), 3);
5468
5469 free_reader(reader);
5470 libc::close(fd);
5471 std::fs::remove_file(tmp_path).ok();
5472 }
5473 }
5474
5475 #[test]
5476 fn test_reader_for_io() {
5477 unsafe {
5478 extern "C" fn io_read(context: *mut c_void, buffer: *mut c_char, len: c_int) -> c_int {
5479 if context.is_null() || buffer.is_null() || len <= 0 {
5480 return -1;
5481 }
5482 // SAFETY: context points to an IoCtx struct.
5483 let ctx = unsafe { &mut *(context as *mut IoCtx) };
5484 if ctx.pos >= ctx.data.len() {
5485 return 0;
5486 }
5487 let remaining = ctx.data.len() - ctx.pos;
5488 let to_copy = if (remaining as c_int) < len {
5489 remaining
5490 } else {
5491 len as usize
5492 };
5493 // SAFETY: buffer has at least `len` bytes of space.
5494 unsafe {
5495 std::ptr::copy_nonoverlapping(
5496 ctx.data.as_ptr().add(ctx.pos),
5497 buffer as *mut u8,
5498 to_copy,
5499 );
5500 }
5501 ctx.pos += to_copy;
5502 to_copy as c_int
5503 }
5504
5505 extern "C" fn io_close(_context: *mut c_void) -> c_int {
5506 0
5507 }
5508
5509 struct IoCtx {
5510 data: &'static [u8],
5511 pos: usize,
5512 }
5513 let mut ctx = IoCtx {
5514 data: b"<root/>",
5515 pos: 0,
5516 };
5517
5518 let reader = xmlReaderForIO(
5519 Some(io_read),
5520 Some(io_close),
5521 &mut ctx as *mut IoCtx as *mut c_void,
5522 ptr::null(),
5523 ptr::null(),
5524 0,
5525 );
5526 assert!(!reader.is_null());
5527
5528 // Read through the document.
5529 assert_eq!(xmlTextReaderRead(reader), 1);
5530 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
5531 let cname = xmlTextReaderConstName(reader);
5532 assert!(!cname.is_null());
5533 assert_eq!(xmlstr_to_bytes(cname), b"root");
5534
5535 // UPSTREAM-PARITY (oracle-verified 2.15.3): `<root/>` is empty, so
5536 // there is no END_ELEMENT — the second Read returns EOF.
5537 assert_eq!(xmlTextReaderRead(reader), 0);
5538
5539 free_reader(reader);
5540 }
5541 }
5542
5543 /// xmlTextReaderPreservePattern: after a full traversal, the document
5544 /// contains only the pattern-matched nodes and their element ancestors
5545 /// (upstream NODE_IS_PRESERVED streaming prune; reader3.c — Phase-12
5546 /// EXTERNAL-CONSUMERS court).
5547 ///
5548 /// # Safety
5549 ///
5550 /// - The reader is created from a static string literal that stays alive
5551 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5552 /// and freed exactly once with `free_reader`; the returned doc is freed
5553 /// with `tree::free_doc` exactly once.
5554 #[test]
5555 fn test_preserve_pattern_prunes() {
5556 unsafe {
5557 let reader = create_reader("<doc><parent><drop/><keep/><drop/></parent></doc>");
5558 assert!(!reader.is_null());
5559 // register the pattern BEFORE the first Read, like reader3.c
5560 let pat = b"keep\0";
5561 assert!(
5562 xmlTextReaderPreservePattern(
5563 reader,
5564 pat.as_ptr() as *const xmlChar,
5565 ptr::null_mut(),
5566 ) >= 0
5567 );
5568 let mut ret = xmlTextReaderRead(reader);
5569 while ret == 1 {
5570 ret = xmlTextReaderRead(reader);
5571 }
5572 let doc = xmlTextReaderCurrentDoc(reader);
5573 assert!(!doc.is_null());
5574 free_reader(reader);
5575
5576 // the pruned doc keeps doc -> parent -> keep; drop nodes gone
5577 let root = tree::doc_get_root_element(doc);
5578 assert!(!root.is_null());
5579 assert_eq!(xmlstr_to_bytes((*root).name), b"doc");
5580 let mut children: Vec<*mut _xmlNode> = Vec::new();
5581 let mut c = (*root).children;
5582 while !c.is_null() {
5583 children.push(c);
5584 c = (*c).next;
5585 }
5586 // the doc's only child is the preserved ancestor <parent>
5587 assert_eq!(children.len(), 1);
5588 assert_eq!(xmlstr_to_bytes((*children[0]).name), b"parent");
5589 // <parent> keeps only the matched <keep/>; both <drop/> are gone
5590 let mut keep: Vec<*mut _xmlNode> = Vec::new();
5591 let mut c = (*children[0]).children;
5592 while !c.is_null() {
5593 keep.push(c);
5594 c = (*c).next;
5595 }
5596 assert_eq!(keep.len(), 1);
5597 assert_eq!(xmlstr_to_bytes((*keep[0]).name), b"keep");
5598 tree::free_doc(doc);
5599 }
5600 }
5601
5602 /// XML_PARSE_DTDVALID on a no-DTD document: the parser raises the
5603 /// no-DTD validity error and clears ctxt->valid (parse2.c — Phase-12
5604 /// EXTERNAL-CONSUMERS court).
5605 ///
5606 /// # Safety
5607 ///
5608 /// - The reader is created from a static string literal that stays alive
5609 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5610 /// and freed exactly once with `free_reader`.
5611 #[test]
5612 fn test_reader_dtdvalid_no_dtd() {
5613 unsafe {
5614 let bytes = b"<doc/>";
5615 let ctxt = create_parser_ctxt();
5616 assert!(!ctxt.is_null());
5617 let input = input_from_memory(bytes.as_ptr() as *const c_char, bytes.len() as c_int);
5618 setup_parser_input(ctxt, input);
5619 crate::abi::exports_parser::apply_options(ctxt, XML_PARSE_DTDVALID);
5620 assert_eq!(parse_document(ctxt), 0);
5621 assert_eq!(
5622 (*ctxt).valid,
5623 0,
5624 "no-DTD validating parse must clear ctxt->valid"
5625 );
5626 assert_eq!((*ctxt).errNo, XML_DTD_NO_DTD);
5627 free_parser_ctxt(ctxt);
5628 }
5629 }
5630}
5631
5632// ═══════════════════════════════════════════════════════════════════════════════
5633// 11.1-I reader closure — remaining xmlTextReader API (R-000136)
5634// ═══════════════════════════════════════════════════════════════════════════════
5635
5636/// Error severity (upstream `xmlParserSeverities`, reader.h).
5637pub const XML_PARSER_SEVERITY_VALIDITY_WARNING: c_int = 1;
5638pub const XML_PARSER_SEVERITY_VALIDITY_ERROR: c_int = 2;
5639pub const XML_PARSER_SEVERITY_WARNING: c_int = 3;
5640pub const XML_PARSER_SEVERITY_ERROR: c_int = 4;
5641
5642/// Opaque locator passed to the reader error handler (upstream
5643/// `xmlTextReaderLocator`).
5644#[derive(Debug)]
5645#[repr(C)]
5646pub struct XmlTextReaderLocator {
5647 pub reader: *mut XmlTextReader,
5648}
5649
5650/// Reader error callback (upstream `xmlTextReaderErrorFunc`).
5651pub type xmlTextReaderErrorFunc = unsafe extern "C" fn(
5652 arg: *mut c_void,
5653 msg: *const c_char,
5654 severity: c_int,
5655 locator: *mut XmlTextReaderLocator,
5656);
5657
5658/// `xmlTextReaderPtr xmlReaderForDoc(const xmlChar *cur, const char *URL,
5659/// const char *encoding, int options)` — reader over an in-memory XML string.
5660///
5661/// # SAFETY
5662///
5663/// - `cur` must be a valid NUL-terminated XML document string.
5664#[no_mangle]
5665pub unsafe extern "C" fn xmlReaderForDoc(
5666 cur: *const xmlChar,
5667 URL: *const c_char,
5668 encoding: *const c_char,
5669 options: c_int,
5670) -> *mut XmlTextReader {
5671 if cur.is_null() {
5672 return ptr::null_mut();
5673 }
5674 let len = unsafe { libc::strlen(cur as *const libc::c_char) } as c_int;
5675 unsafe { xmlReaderForMemory(cur as *const c_char, len, URL, encoding, options) }
5676}
5677
5678/// `xmlTextReaderPtr xmlNewTextReaderFilename(const char *URI)` — upstream
5679/// xmlreader.c: creates a reader over the file, no encoding/options
5680/// (R-000176: the candidate previously exported a 3-argument extension).
5681///
5682/// # SAFETY
5683///
5684/// - `URI` must point to a valid NUL-terminated string or NULL.
5685#[no_mangle]
5686pub unsafe extern "C" fn xmlNewTextReaderFilename(URI: *const c_char) -> *mut XmlTextReader {
5687 unsafe { xmlReaderForFile(URI, ptr::null(), 0) }
5688}
5689
5690/// Rebuild a reader in place (upstream `xmlReaderNew*` reuse contract).
5691///
5692/// Upstream reuses the caller's existing reader allocation, so a caller's
5693/// pointer remains valid across `xmlReaderNew*`. The candidate mirrors that by
5694/// moving the freshly built reader's contents into the caller's allocation and
5695/// releasing the temporary allocation without dropping the moved contents.
5696///
5697/// # SAFETY
5698///
5699/// - `reader` must be a valid, non-NULL reader pointer.
5700/// - `new_reader` must be a valid, non-NULL reader pointer distinct from `reader`.
5701unsafe fn reader_renew(reader: *mut XmlTextReader, new_reader: *mut XmlTextReader) {
5702 debug_assert!(!reader.is_null() && !new_reader.is_null() && reader != new_reader);
5703 unsafe {
5704 // Drop the old contents, then bitwise-move the new reader into the
5705 // caller's allocation. The temporary allocation is deallocated without
5706 // dropping (its contents now live at `reader`).
5707 core::ptr::drop_in_place(reader);
5708 core::ptr::copy_nonoverlapping(new_reader, reader, 1);
5709 let layout = std::alloc::Layout::new::<XmlTextReader>();
5710 std::alloc::dealloc(new_reader as *mut u8, layout);
5711 }
5712}
5713
5714/// `int xmlReaderNewDoc(xmlTextReaderPtr reader, const xmlChar *cur, const char *URL, const char *encoding, int options)`.
5715///
5716/// # SAFETY
5717///
5718/// - `reader` must be valid pointers (or NULL
5719/// where the upstream C contract allows), obtained from the
5720/// matching constructor/owner and not yet freed; the callee may
5721/// take or keep ownership exactly as the C API specifies.
5722///
5723/// - `cur`, `URL`, `encoding` must point to valid NUL-terminated
5724/// strings (or NULL where the C contract allows) for the lifetime
5725/// of the call.
5726///
5727/// The caller must not race this call with concurrent mutation of the
5728/// same objects from other threads (per-object state is not internally
5729/// synchronized). Violating any of the above is undefined behavior.
5730///
5731/// Exercised by the C-API differential courts
5732/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5733/// courts; those pass byte-for-byte against the upstream oracle.
5734#[no_mangle]
5735pub unsafe extern "C" fn xmlReaderNewDoc(
5736 reader: *mut XmlTextReader,
5737 cur: *const xmlChar,
5738 URL: *const c_char,
5739 encoding: *const c_char,
5740 options: c_int,
5741) -> c_int {
5742 // UPSTREAM-PARITY: the New* family rejects a NULL reader before any work
5743 // (xmlreader.c: `if (reader == NULL) return (-1);`). It never allocates.
5744 if reader.is_null() || cur.is_null() {
5745 return -1;
5746 }
5747 let r = unsafe { xmlReaderForDoc(cur, URL, encoding, options) };
5748 if r.is_null() {
5749 return -1;
5750 }
5751 unsafe { reader_renew(reader, r) };
5752 0
5753}
5754
5755/// `int xmlReaderNewFile(xmlTextReaderPtr reader, const char *filename, const char *encoding, int options)`.
5756///
5757/// # SAFETY
5758///
5759/// - `reader` must be valid pointers (or NULL
5760/// where the upstream C contract allows), obtained from the
5761/// matching constructor/owner and not yet freed; the callee may
5762/// take or keep ownership exactly as the C API specifies.
5763///
5764/// - `filename`, `encoding` must point to valid NUL-terminated
5765/// strings (or NULL where the C contract allows) for the lifetime
5766/// of the call.
5767///
5768/// The caller must not race this call with concurrent mutation of the
5769/// same objects from other threads (per-object state is not internally
5770/// synchronized). Violating any of the above is undefined behavior.
5771///
5772/// Exercised by the C-API differential courts
5773/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5774/// courts; those pass byte-for-byte against the upstream oracle.
5775#[no_mangle]
5776pub unsafe extern "C" fn xmlReaderNewFile(
5777 reader: *mut XmlTextReader,
5778 filename: *const c_char,
5779 encoding: *const c_char,
5780 options: c_int,
5781) -> c_int {
5782 if reader.is_null() {
5783 return -1;
5784 }
5785 let r = unsafe { xmlReaderForFile(filename, encoding, options) };
5786 if r.is_null() {
5787 return -1;
5788 }
5789 unsafe { reader_renew(reader, r) };
5790 0
5791}
5792
5793/// `int xmlReaderNewMemory(xmlTextReaderPtr reader, const char *buffer, int size, const char *URL, const char *encoding, int options)`.
5794///
5795/// # SAFETY
5796///
5797/// - `reader` must be valid pointers (or NULL
5798/// where the upstream C contract allows), obtained from the
5799/// matching constructor/owner and not yet freed; the callee may
5800/// take or keep ownership exactly as the C API specifies.
5801///
5802/// - `buffer`, `URL`, `encoding` must point to valid NUL-terminated
5803/// strings (or NULL where the C contract allows) for the lifetime
5804/// of the call.
5805///
5806/// The caller must not race this call with concurrent mutation of the
5807/// same objects from other threads (per-object state is not internally
5808/// synchronized). Violating any of the above is undefined behavior.
5809///
5810/// Exercised by the C-API differential courts
5811/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5812/// courts; those pass byte-for-byte against the upstream oracle.
5813#[no_mangle]
5814pub unsafe extern "C" fn xmlReaderNewMemory(
5815 reader: *mut XmlTextReader,
5816 buffer: *const c_char,
5817 size: c_int,
5818 URL: *const c_char,
5819 encoding: *const c_char,
5820 options: c_int,
5821) -> c_int {
5822 if reader.is_null() || buffer.is_null() {
5823 return -1;
5824 }
5825 let r = unsafe { xmlReaderForMemory(buffer, size, URL, encoding, options) };
5826 if r.is_null() {
5827 return -1;
5828 }
5829 unsafe { reader_renew(reader, r) };
5830 0
5831}
5832
5833/// `int xmlReaderNewFd(xmlTextReaderPtr reader, int fd, const char *URL, const char *encoding, int options)`.
5834///
5835/// # SAFETY
5836///
5837/// - `reader` must be valid pointers (or NULL
5838/// where the upstream C contract allows), obtained from the
5839/// matching constructor/owner and not yet freed; the callee may
5840/// take or keep ownership exactly as the C API specifies.
5841///
5842/// - `URL`, `encoding` must point to valid NUL-terminated
5843/// strings (or NULL where the C contract allows) for the lifetime
5844/// of the call.
5845///
5846/// The caller must not race this call with concurrent mutation of the
5847/// same objects from other threads (per-object state is not internally
5848/// synchronized). Violating any of the above is undefined behavior.
5849///
5850/// Exercised by the C-API differential courts
5851/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5852/// courts; those pass byte-for-byte against the upstream oracle.
5853#[no_mangle]
5854pub unsafe extern "C" fn xmlReaderNewFd(
5855 reader: *mut XmlTextReader,
5856 fd: c_int,
5857 URL: *const c_char,
5858 encoding: *const c_char,
5859 options: c_int,
5860) -> c_int {
5861 if reader.is_null() {
5862 return -1;
5863 }
5864 let r = unsafe { xmlReaderForFd(fd, URL, encoding, options) };
5865 if r.is_null() {
5866 return -1;
5867 }
5868 unsafe { reader_renew(reader, r) };
5869 0
5870}
5871
5872/// `int xmlReaderNewIO(xmlTextReaderPtr reader, xmlInputReadCallback ioread, xmlInputCloseCallback ioclose, void *ioctx, const char *URL, const char *encoding, int options)`.
5873///
5874/// # SAFETY
5875///
5876/// - `reader`, `ioctx` must be valid pointers (or NULL
5877/// where the upstream C contract allows), obtained from the
5878/// matching constructor/owner and not yet freed; the callee may
5879/// take or keep ownership exactly as the C API specifies.
5880///
5881/// - `URL`, `encoding` must point to valid NUL-terminated
5882/// strings (or NULL where the C contract allows) for the lifetime
5883/// of the call.
5884///
5885/// - `ioread`, `ioclose` must be a valid callback (or None);
5886/// the callback is invoked with the documented context pointer and
5887/// must itself uphold the same pointer invariants.
5888///
5889/// The caller must not race this call with concurrent mutation of the
5890/// same objects from other threads (per-object state is not internally
5891/// synchronized). Violating any of the above is undefined behavior.
5892///
5893/// Exercised by the C-API differential courts
5894/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5895/// courts; those pass byte-for-byte against the upstream oracle.
5896#[no_mangle]
5897pub unsafe extern "C" fn xmlReaderNewIO(
5898 reader: *mut XmlTextReader,
5899 ioread: Option<xmlInputReadCallback>,
5900 ioclose: Option<xmlInputCloseCallback>,
5901 ioctx: *mut c_void,
5902 URL: *const c_char,
5903 encoding: *const c_char,
5904 options: c_int,
5905) -> c_int {
5906 // UPSTREAM-PARITY: NULL reader or NULL read callback is rejected (-1).
5907 if reader.is_null() || ioread.is_none() {
5908 return -1;
5909 }
5910 let r = unsafe { xmlReaderForIO(ioread, ioclose, ioctx, URL, encoding, options) };
5911 if r.is_null() {
5912 return -1;
5913 }
5914 unsafe { reader_renew(reader, r) };
5915 0
5916}
5917
5918/// `xmlTextReaderPtr xmlReaderWalker(xmlDocPtr doc)` — reader walking an
5919/// existing document tree.
5920///
5921/// # SAFETY
5922///
5923/// - `doc` must be a valid document.
5924#[no_mangle]
5925pub unsafe extern "C" fn xmlReaderWalker(doc: *mut _xmlDoc) -> *mut XmlTextReader {
5926 if doc.is_null() {
5927 return ptr::null_mut();
5928 }
5929 let mut reader = XmlTextReader::new(ptr::null_mut(), None, None);
5930 reader.doc = doc;
5931 reader.parsed = true;
5932 reader.owns_doc = false; // walker borrows the caller's document
5933 reader.state = ReadState::READING;
5934 reader.build_events();
5935 Box::into_raw(Box::new(reader))
5936}
5937
5938/// `int xmlReaderNewWalker(xmlTextReaderPtr reader, xmlDocPtr doc)`.
5939///
5940/// # SAFETY
5941///
5942/// - `reader`, `doc` must be valid pointers (or NULL
5943/// where the upstream C contract allows), obtained from the
5944/// matching constructor/owner and not yet freed; the callee may
5945/// take or keep ownership exactly as the C API specifies.
5946///
5947/// The caller must not race this call with concurrent mutation of the
5948/// same objects from other threads (per-object state is not internally
5949/// synchronized). Violating any of the above is undefined behavior.
5950///
5951/// Exercised by the C-API differential courts
5952/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5953/// courts; those pass byte-for-byte against the upstream oracle.
5954#[no_mangle]
5955pub unsafe extern "C" fn xmlReaderNewWalker(
5956 reader: *mut XmlTextReader,
5957 doc: *mut _xmlDoc,
5958) -> c_int {
5959 // UPSTREAM-PARITY: NULL reader or NULL doc is rejected (-1).
5960 if reader.is_null() || doc.is_null() {
5961 return -1;
5962 }
5963 let r = unsafe { xmlReaderWalker(doc) };
5964 if r.is_null() {
5965 return -1;
5966 }
5967 unsafe { reader_renew(reader, r) };
5968 0
5969}
5970
5971/// `long xmlTextReaderByteConsumed(xmlTextReaderPtr reader)`.
5972///
5973/// Returns the total bytes consumed from the input (0 when unavailable —
5974/// the candidate parses the full input up front; documented divergence).
5975///
5976/// # SAFETY
5977///
5978/// - `reader` must be valid pointers (or NULL
5979/// where the upstream C contract allows), obtained from the
5980/// matching constructor/owner and not yet freed; the callee may
5981/// take or keep ownership exactly as the C API specifies.
5982///
5983/// The caller must not race this call with concurrent mutation of the
5984/// same objects from other threads (per-object state is not internally
5985/// synchronized). Violating any of the above is undefined behavior.
5986///
5987/// Exercised by the C-API differential courts
5988/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5989/// courts; those pass byte-for-byte against the upstream oracle.
5990#[no_mangle]
5991pub const unsafe extern "C" fn xmlTextReaderByteConsumed(reader: *mut XmlTextReader) -> c_long {
5992 if reader.is_null() {
5993 return -1;
5994 }
5995 0
5996}
5997
5998/// `const xmlChar *xmlTextReaderConstBaseUri(xmlTextReaderPtr reader)` — the
5999/// base URI, valid until the reader is freed (no copy).
6000///
6001/// # SAFETY
6002///
6003/// - `reader` must be valid pointers (or NULL
6004/// where the upstream C contract allows), obtained from the
6005/// matching constructor/owner and not yet freed; the callee may
6006/// take or keep ownership exactly as the C API specifies.
6007///
6008/// The caller must not race this call with concurrent mutation of the
6009/// same objects from other threads (per-object state is not internally
6010/// synchronized). Violating any of the above is undefined behavior.
6011///
6012/// Exercised by the C-API differential courts
6013/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6014/// courts; those pass byte-for-byte against the upstream oracle.
6015#[no_mangle]
6016pub unsafe extern "C" fn xmlTextReaderConstBaseUri(reader: *mut XmlTextReader) -> *const xmlChar {
6017 if reader.is_null() {
6018 return ptr::null();
6019 }
6020 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderConstBaseUri): the base URI
6021 // is exposed only once a node is current (upstream `reader->node == NULL`
6022 // returns NULL). Pre-first-Read and after EOF the PHP property must read
6023 // as the empty string, not the reader's setup URL.
6024 let r = unsafe { &*reader };
6025 if r.cur_node.is_null() {
6026 return ptr::null();
6027 }
6028 r.URL
6029}
6030
6031/// `const xmlChar *xmlTextReaderConstEncoding(xmlTextReaderPtr reader)`.
6032///
6033/// # SAFETY
6034///
6035/// - `reader` must be valid pointers (or NULL
6036/// where the upstream C contract allows), obtained from the
6037/// matching constructor/owner and not yet freed; the callee may
6038/// take or keep ownership exactly as the C API specifies.
6039///
6040/// The caller must not race this call with concurrent mutation of the
6041/// same objects from other threads (per-object state is not internally
6042/// synchronized). Violating any of the above is undefined behavior.
6043///
6044/// Exercised by the C-API differential courts
6045/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6046/// courts; those pass byte-for-byte against the upstream oracle.
6047#[no_mangle]
6048pub unsafe extern "C" fn xmlTextReaderConstEncoding(reader: *mut XmlTextReader) -> *const xmlChar {
6049 if reader.is_null() {
6050 return ptr::null();
6051 }
6052 let r = unsafe { &*reader };
6053 if !r.encoding.is_null() {
6054 return r.encoding;
6055 }
6056 if !r.doc.is_null() {
6057 return unsafe { (*r.doc).encoding };
6058 }
6059 ptr::null()
6060}
6061
6062/// `const xmlChar *xmlTextReaderConstLocalName(xmlTextReaderPtr reader)`.
6063///
6064/// UPSTREAM-PARITY: at an attribute position this is the attribute's local
6065/// name (or "xmlns"/the prefix for a namespace declaration); at an element
6066/// position the tree's local name.
6067///
6068/// # SAFETY
6069///
6070/// - `reader` must be valid pointers (or NULL
6071/// where the upstream C contract allows), obtained from the
6072/// matching constructor/owner and not yet freed; the callee may
6073/// take or keep ownership exactly as the C API specifies.
6074///
6075/// The caller must not race this call with concurrent mutation of the
6076/// same objects from other threads (per-object state is not internally
6077/// synchronized). Violating any of the above is undefined behavior.
6078///
6079/// Exercised by the C-API differential courts
6080/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6081/// courts; those pass byte-for-byte against the upstream oracle.
6082#[no_mangle]
6083pub unsafe extern "C" fn xmlTextReaderConstLocalName(reader: *mut XmlTextReader) -> *const xmlChar {
6084 if reader.is_null() {
6085 return ptr::null();
6086 }
6087 let r = unsafe { &*reader };
6088 if r.cur_node.is_null() {
6089 return ptr::null();
6090 }
6091 // Attribute position: the attribute's local name (upstream node->name for
6092 // XML_ATTRIBUTE_NODE; "xmlns"/prefix for a namespace declaration).
6093 if r.cur_attribute >= 0 {
6094 let target = unsafe { r.attr_at(r.cur_node, r.cur_attribute) };
6095 return match target {
6096 AttrTarget::Ns(ns) => {
6097 if ns.is_null() {
6098 ptr::null()
6099 } else if unsafe { (*ns).prefix }.is_null() {
6100 c"xmlns".as_ptr() as *const xmlChar
6101 } else {
6102 unsafe { (*ns).prefix }
6103 }
6104 }
6105 AttrTarget::Prop(p) => {
6106 if p.is_null() || unsafe { (*p).name }.is_null() {
6107 ptr::null()
6108 } else {
6109 unsafe { (*p).name }
6110 }
6111 }
6112 AttrTarget::None => ptr::null(),
6113 };
6114 }
6115 // Element position: the tree's local name (upstream node->name).
6116 let etype = unsafe { (*r.cur_node).type_ };
6117 if etype == XML_ELEMENT_NODE as c_int || etype == XML_ATTRIBUTE_NODE as c_int {
6118 unsafe { (*r.cur_node).name }
6119 } else {
6120 ptr::null()
6121 }
6122}
6123
6124/// `const xmlChar *xmlTextReaderConstNamespaceUri(xmlTextReaderPtr reader)`.
6125///
6126/// UPSTREAM-PARITY: at an attribute position the namespace comes from the
6127/// attribute (or namespace declaration) itself; elsewhere from the node.
6128///
6129/// # SAFETY
6130///
6131/// - `reader` must be valid pointers (or NULL
6132/// where the upstream C contract allows), obtained from the
6133/// matching constructor/owner and not yet freed; the callee may
6134/// take or keep ownership exactly as the C API specifies.
6135///
6136/// The caller must not race this call with concurrent mutation of the
6137/// same objects from other threads (per-object state is not internally
6138/// synchronized). Violating any of the above is undefined behavior.
6139///
6140/// Exercised by the C-API differential courts
6141/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6142/// courts; those pass byte-for-byte against the upstream oracle.
6143#[no_mangle]
6144pub unsafe extern "C" fn xmlTextReaderConstNamespaceUri(
6145 reader: *mut XmlTextReader,
6146) -> *const xmlChar {
6147 if reader.is_null() {
6148 return ptr::null();
6149 }
6150 let r = unsafe { &*reader };
6151 if r.cur_node.is_null() {
6152 return ptr::null();
6153 }
6154 // Attribute position: resolve the current attribute's namespace.
6155 if r.cur_attribute >= 0 {
6156 let target = unsafe { r.attr_at(r.cur_node, r.cur_attribute) };
6157 return match target {
6158 AttrTarget::Ns(_ns) => {
6159 // UPSTREAM-PARITY (xmlTextReaderConstNamespaceUri): a
6160 // namespace declaration reports the xmlns namespace URI,
6161 // not the declared URI.
6162 c"http://www.w3.org/2000/xmlns/".as_ptr() as *const xmlChar
6163 }
6164 AttrTarget::Prop(p) => {
6165 if p.is_null() || unsafe { (*p).ns }.is_null() {
6166 ptr::null()
6167 } else {
6168 unsafe { (*(*p).ns).href }
6169 }
6170 }
6171 AttrTarget::None => ptr::null(),
6172 };
6173 }
6174 let ns = unsafe { (*r.cur_node).ns };
6175 if ns.is_null() || unsafe { (*ns).href }.is_null() {
6176 ptr::null()
6177 } else {
6178 unsafe { (*ns).href }
6179 }
6180}
6181
6182/// `const xmlChar *xmlTextReaderConstPrefix(xmlTextReaderPtr reader)`.
6183///
6184/// UPSTREAM-PARITY: at an attribute position the prefix comes from the
6185/// attribute; for a namespace declaration the prefix is reported as "xmlns"
6186/// (and NULL for the default declaration) — an upstream quirk reproduced here.
6187///
6188/// # SAFETY
6189///
6190/// - `reader` must be valid pointers (or NULL
6191/// where the upstream C contract allows), obtained from the
6192/// matching constructor/owner and not yet freed; the callee may
6193/// take or keep ownership exactly as the C API specifies.
6194///
6195/// The caller must not race this call with concurrent mutation of the
6196/// same objects from other threads (per-object state is not internally
6197/// synchronized). Violating any of the above is undefined behavior.
6198///
6199/// Exercised by the C-API differential courts
6200/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6201/// courts; those pass byte-for-byte against the upstream oracle.
6202#[no_mangle]
6203pub unsafe extern "C" fn xmlTextReaderConstPrefix(reader: *mut XmlTextReader) -> *const xmlChar {
6204 if reader.is_null() {
6205 return ptr::null();
6206 }
6207 let r = unsafe { &*reader };
6208 if r.cur_node.is_null() {
6209 return ptr::null();
6210 }
6211 // Attribute position: resolve the current attribute's namespace.
6212 if r.cur_attribute >= 0 {
6213 let target = unsafe { r.attr_at(r.cur_node, r.cur_attribute) };
6214 return match target {
6215 AttrTarget::Ns(ns) => {
6216 if ns.is_null() || unsafe { (*ns).prefix }.is_null() {
6217 ptr::null()
6218 } else {
6219 c"xmlns".as_ptr() as *const xmlChar
6220 }
6221 }
6222 AttrTarget::Prop(p) => {
6223 if p.is_null() || unsafe { (*p).ns }.is_null() {
6224 ptr::null()
6225 } else {
6226 unsafe { (*(*p).ns).prefix }
6227 }
6228 }
6229 AttrTarget::None => ptr::null(),
6230 };
6231 }
6232 let ns = unsafe { (*r.cur_node).ns };
6233 if ns.is_null() || unsafe { (*ns).prefix }.is_null() {
6234 ptr::null()
6235 } else {
6236 unsafe { (*ns).prefix }
6237 }
6238}
6239
6240/// `const xmlChar *xmlTextReaderConstString(xmlTextReaderPtr reader, const xmlChar *str)`
6241/// — the reader's dictionary-internalized copy of `str`; the candidate
6242/// returns `str` unchanged (dictionary interning is an internal detail).
6243///
6244/// # SAFETY
6245///
6246/// - `_reader` must be valid pointers (or NULL
6247/// where the upstream C contract allows), obtained from the
6248/// matching constructor/owner and not yet freed; the callee may
6249/// take or keep ownership exactly as the C API specifies.
6250///
6251/// - `str` must point to valid NUL-terminated
6252/// strings (or NULL where the C contract allows) for the lifetime
6253/// of the call.
6254///
6255/// The caller must not race this call with concurrent mutation of the
6256/// same objects from other threads (per-object state is not internally
6257/// synchronized). Violating any of the above is undefined behavior.
6258///
6259/// Exercised by the C-API differential courts
6260/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6261/// courts; those pass byte-for-byte against the upstream oracle.
6262#[no_mangle]
6263pub const unsafe extern "C" fn xmlTextReaderConstString(
6264 _reader: *mut XmlTextReader,
6265 str: *const xmlChar,
6266) -> *const xmlChar {
6267 str
6268}
6269
6270/// `const xmlChar *xmlTextReaderConstXmlLang(xmlTextReaderPtr reader)`.
6271///
6272/// # SAFETY
6273///
6274/// - `reader` must be valid pointers (or NULL
6275/// where the upstream C contract allows), obtained from the
6276/// matching constructor/owner and not yet freed; the callee may
6277/// take or keep ownership exactly as the C API specifies.
6278///
6279/// The caller must not race this call with concurrent mutation of the
6280/// same objects from other threads (per-object state is not internally
6281/// synchronized). Violating any of the above is undefined behavior.
6282///
6283/// Exercised by the C-API differential courts
6284/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6285/// courts; those pass byte-for-byte against the upstream oracle.
6286#[no_mangle]
6287pub unsafe extern "C" fn xmlTextReaderConstXmlLang(reader: *mut XmlTextReader) -> *const xmlChar {
6288 if reader.is_null() {
6289 return ptr::null();
6290 }
6291 let r = unsafe { &*reader };
6292 let mut node = r.cur_node;
6293 while !node.is_null() {
6294 let mut prop = unsafe { (*node).properties };
6295 while !prop.is_null() {
6296 let p = unsafe { &*prop };
6297 if !p.name.is_null()
6298 && unsafe { *p.name } == b'x'
6299 && unsafe { *p.name.add(1) } == b'm'
6300 && unsafe { *p.name.add(2) } == b'l'
6301 && unsafe { *p.name.add(3) } == b':'
6302 && unsafe { *p.name.add(4) } == b'l'
6303 && unsafe { *p.name.add(5) } == b'a'
6304 && unsafe { *p.name.add(6) } == b'n'
6305 && unsafe { *p.name.add(7) } == b'g'
6306 && unsafe { *p.name.add(8) } == 0
6307 {
6308 if !p.children.is_null() {
6309 let txt = p.children;
6310 if unsafe { (*txt).type_ }
6311 == crate::abi::types::xmlElementType::XML_TEXT_NODE as c_int
6312 {
6313 return unsafe { (*txt).content };
6314 }
6315 }
6316 return ptr::null();
6317 }
6318 prop = p.next;
6319 }
6320 node = unsafe { (*node).parent };
6321 }
6322 ptr::null()
6323}
6324
6325/// `const xmlChar *xmlTextReaderConstXmlVersion(xmlTextReaderPtr reader)`.
6326///
6327/// # SAFETY
6328///
6329/// - `reader` must be valid pointers (or NULL
6330/// where the upstream C contract allows), obtained from the
6331/// matching constructor/owner and not yet freed; the callee may
6332/// take or keep ownership exactly as the C API specifies.
6333///
6334/// The caller must not race this call with concurrent mutation of the
6335/// same objects from other threads (per-object state is not internally
6336/// synchronized). Violating any of the above is undefined behavior.
6337///
6338/// Exercised by the C-API differential courts
6339/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6340/// courts; those pass byte-for-byte against the upstream oracle.
6341#[no_mangle]
6342pub unsafe extern "C" fn xmlTextReaderConstXmlVersion(
6343 reader: *mut XmlTextReader,
6344) -> *const xmlChar {
6345 if reader.is_null() {
6346 return ptr::null();
6347 }
6348 let r = unsafe { &*reader };
6349 if r.doc.is_null() {
6350 return ptr::null();
6351 }
6352 unsafe { (*r.doc).version }
6353}
6354
6355/// `int xmlTextReaderQuoteChar(xmlTextReaderPtr reader)`.
6356///
6357/// UPSTREAM-PARITY: libxml2 2.13/2.15 returns `'"'` unconditionally for any
6358/// non-NULL reader (the implementation is a placeholder that does not inspect
6359/// the attribute; see the `/* TODO maybe lookup the attribute value */` comment
6360/// in xmlreader.c). The candidate reproduces that historical behavior exactly.
6361///
6362/// # SAFETY
6363///
6364/// - `reader` must be valid pointers (or NULL
6365/// where the upstream C contract allows), obtained from the
6366/// matching constructor/owner and not yet freed; the callee may
6367/// take or keep ownership exactly as the C API specifies.
6368///
6369/// The caller must not race this call with concurrent mutation of the
6370/// same objects from other threads (per-object state is not internally
6371/// synchronized). Violating any of the above is undefined behavior.
6372///
6373/// Exercised by the C-API differential courts
6374/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6375/// courts; those pass byte-for-byte against the upstream oracle.
6376#[no_mangle]
6377pub const unsafe extern "C" fn xmlTextReaderQuoteChar(reader: *mut XmlTextReader) -> c_int {
6378 if reader.is_null() {
6379 return -1;
6380 }
6381 b'"' as c_int
6382}
6383
6384/// `int xmlTextReaderIsDefault(xmlTextReaderPtr reader)` — whether the current
6385/// attribute came from the DTD default. The candidate returns 0 for a valid
6386/// reader (DTD default attribute expansion is not annotated; documented
6387/// divergence), -1 for a NULL reader (upstream contract).
6388///
6389/// # SAFETY
6390///
6391/// - `reader` must be valid pointers (or NULL
6392/// where the upstream C contract allows), obtained from the
6393/// matching constructor/owner and not yet freed; the callee may
6394/// take or keep ownership exactly as the C API specifies.
6395///
6396/// The caller must not race this call with concurrent mutation of the
6397/// same objects from other threads (per-object state is not internally
6398/// synchronized). Violating any of the above is undefined behavior.
6399///
6400/// Exercised by the C-API differential courts
6401/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6402/// courts; those pass byte-for-byte against the upstream oracle.
6403#[no_mangle]
6404pub const unsafe extern "C" fn xmlTextReaderIsDefault(reader: *mut XmlTextReader) -> c_int {
6405 if reader.is_null() {
6406 return -1;
6407 }
6408 0
6409}
6410
6411/// `int xmlTextReaderIsNamespaceDecl(xmlTextReaderPtr reader)` — whether the
6412/// current attribute position is a namespace declaration.
6413///
6414/// # SAFETY
6415///
6416/// - `reader` must be valid pointers (or NULL
6417/// where the upstream C contract allows), obtained from the
6418/// matching constructor/owner and not yet freed; the callee may
6419/// take or keep ownership exactly as the C API specifies.
6420///
6421/// The caller must not race this call with concurrent mutation of the
6422/// same objects from other threads (per-object state is not internally
6423/// synchronized). Violating any of the above is undefined behavior.
6424///
6425/// Exercised by the C-API differential courts
6426/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6427/// courts; those pass byte-for-byte against the upstream oracle.
6428#[no_mangle]
6429pub unsafe extern "C" fn xmlTextReaderIsNamespaceDecl(reader: *mut XmlTextReader) -> c_int {
6430 if reader.is_null() {
6431 return -1;
6432 }
6433 let r = unsafe { &*reader };
6434 if r.cur_node.is_null() {
6435 return -1;
6436 }
6437 r.cur_attr_is_ns as c_int
6438}
6439
6440/// `int xmlTextReaderMoveToAttributeNs(xmlTextReaderPtr reader, const xmlChar *localName, const xmlChar *namespaceURI)`.
6441///
6442/// UPSTREAM-PARITY (xmlreader.c, 2.15): NULL reader/localName/namespaceURI
6443/// returns -1; a NULL `namespaceURI` is NOT treated as "no namespace" — the
6444/// caller must pass the actual URI. The `http://www.w3.org/2000/xmlns/`
6445/// namespace searches namespace declarations (matching the default `xmlns`
6446/// declaration or a prefix), everything else searches only namespace-qualified
6447/// properties (`prop->ns != NULL`).
6448///
6449/// # SAFETY
6450///
6451/// - `localName`/`namespaceURI` must be valid strings (non-NULL).
6452#[no_mangle]
6453pub unsafe extern "C" fn xmlTextReaderMoveToAttributeNs(
6454 reader: *mut XmlTextReader,
6455 localName: *const xmlChar,
6456 namespaceURI: *const xmlChar,
6457) -> c_int {
6458 if reader.is_null() || localName.is_null() || namespaceURI.is_null() {
6459 return -1;
6460 }
6461 let r = unsafe { &mut *reader };
6462 let node = r.cur_node;
6463 if node.is_null() {
6464 return -1;
6465 }
6466 if unsafe { (*node).type_ } != XML_ELEMENT_NODE as c_int {
6467 return 0;
6468 }
6469
6470 const XMLNS_URI: &[u8] = b"http://www.w3.org/2000/xmlns/\0";
6471 if libc::strcmp(
6472 namespaceURI as *const libc::c_char,
6473 XMLNS_URI.as_ptr() as *const libc::c_char,
6474 ) == 0
6475 {
6476 // Namespace-declaration search: localName "xmlns" addresses the
6477 // default declaration, any other localName is a prefix.
6478 let is_default = libc::strcmp(
6479 localName as *const libc::c_char,
6480 c"xmlns".as_ptr() as *const libc::c_char,
6481 ) == 0;
6482 let mut ns = unsafe { (*node).nsDef };
6483 let mut index = 0;
6484 while !ns.is_null() {
6485 let n = unsafe { &*ns };
6486 let prefix_match = if is_default {
6487 n.prefix.is_null()
6488 } else {
6489 !n.prefix.is_null()
6490 && libc::strcmp(
6491 n.prefix as *const libc::c_char,
6492 localName as *const libc::c_char,
6493 ) == 0
6494 };
6495 if prefix_match {
6496 r.cur_attribute = index;
6497 r.node_type = ReaderNodeType::ATTRIBUTE;
6498 r.cache_attribute_info(AttrTarget::Ns(ns));
6499 return 1;
6500 }
6501 index += 1;
6502 ns = unsafe { (*ns).next };
6503 }
6504 return 0;
6505 }
6506
6507 // Property search: only namespace-qualified attributes are matchable.
6508 let mut prop = unsafe { (*node).properties };
6509 let mut index = 0;
6510 let mut ns_count = 0;
6511 let mut ns = unsafe { (*node).nsDef };
6512 while !ns.is_null() {
6513 ns_count += 1;
6514 ns = unsafe { (*ns).next };
6515 }
6516 while !prop.is_null() {
6517 let p = unsafe { &*prop };
6518 if !p.name.is_null()
6519 && !p.ns.is_null()
6520 && !(*p.ns).href.is_null()
6521 && libc::strcmp(
6522 p.name as *const libc::c_char,
6523 localName as *const libc::c_char,
6524 ) == 0
6525 && libc::strcmp(
6526 (*p.ns).href as *const libc::c_char,
6527 namespaceURI as *const libc::c_char,
6528 ) == 0
6529 {
6530 r.cur_attribute = ns_count + index;
6531 r.node_type = ReaderNodeType::ATTRIBUTE;
6532 r.cache_attribute_info(AttrTarget::Prop(prop));
6533 return 1;
6534 }
6535 index += 1;
6536 prop = unsafe { (*prop).next };
6537 }
6538 0
6539}
6540
6541/// `xmlNodePtr xmlTextReaderPreserve(xmlTextReaderPtr reader)` — the current
6542/// node (the candidate's reader owns the whole tree, so no separate
6543/// preservation step is needed).
6544///
6545/// # SAFETY
6546///
6547/// - `reader` must be valid pointers (or NULL
6548/// where the upstream C contract allows), obtained from the
6549/// matching constructor/owner and not yet freed; the callee may
6550/// take or keep ownership exactly as the C API specifies.
6551///
6552/// The caller must not race this call with concurrent mutation of the
6553/// same objects from other threads (per-object state is not internally
6554/// synchronized). Violating any of the above is undefined behavior.
6555///
6556/// Exercised by the C-API differential courts
6557/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6558/// courts; those pass byte-for-byte against the upstream oracle.
6559#[no_mangle]
6560pub unsafe extern "C" fn xmlTextReaderPreserve(reader: *mut XmlTextReader) -> *mut _xmlNode {
6561 if reader.is_null() {
6562 return ptr::null_mut();
6563 }
6564 unsafe { (*reader).cur_node }
6565}
6566
6567/// `int xmlTextReaderPreservePattern(xmlTextReaderPtr reader, const xmlChar *pattern, const xmlChar **namespaces)`.
6568///
6569/// Compiles the XPath-subset pattern (upstream `xmlPatterncompile`) and
6570/// registers it like upstream `reader->patternTab`; after the parse,
6571/// matched nodes and their element ancestors are kept and every other node
6572/// is unlinked and freed (upstream's NODE_IS_PRESERVED streaming prune,
6573/// applied post-parse by the candidate's whole-tree reader). Returns the
6574/// index of the registered pattern, or -1 on error (upstream xmlreader.c
6575/// xmlTextReaderPreservePattern).
6576///
6577/// # SAFETY
6578///
6579/// - `reader` must be valid pointers (or NULL
6580/// where the upstream C contract allows), obtained from the
6581/// matching constructor/owner and not yet freed; the callee may
6582/// take or keep ownership exactly as the C API specifies.
6583///
6584/// - `pattern` must point to a valid NUL-terminated string;
6585/// `namespaces` must be NULL or a NULL-terminated array of
6586/// NUL-terminated strings, both live for the duration of the call.
6587///
6588/// The caller must not race this call with concurrent mutation of the
6589/// same objects from other threads (per-object state is not internally
6590/// synchronized). Violating any of the above is undefined behavior.
6591///
6592/// Exercised by the Phase-12 EXTERNAL-CONSUMERS court (reader3.c).
6593#[no_mangle]
6594pub unsafe extern "C" fn xmlTextReaderPreservePattern(
6595 reader: *mut XmlTextReader,
6596 pattern: *const xmlChar,
6597 namespaces: *mut *const xmlChar,
6598) -> c_int {
6599 if reader.is_null() || pattern.is_null() {
6600 return -1;
6601 }
6602 let comp = unsafe {
6603 crate::abi::exports_automata::xmlPatterncompile(
6604 pattern,
6605 ptr::null_mut(),
6606 0,
6607 namespaces as *const *const xmlChar,
6608 )
6609 };
6610 if comp.is_null() {
6611 return -1;
6612 }
6613 // SAFETY: reader is valid (checked); pattern_tab owns the compiled
6614 // pattern and frees it in Drop.
6615 let idx = unsafe { (*reader).pattern_tab.len() };
6616 unsafe { (*reader).pattern_tab.push(comp) };
6617 idx as c_int
6618}
6619
6620/// `int xmlTextReaderSetErrorHandler(xmlTextReaderPtr reader, xmlTextReaderErrorFunc f, void *arg)`.
6621///
6622/// # SAFETY
6623///
6624/// - `f` must be a valid callback or NULL.
6625#[no_mangle]
6626pub unsafe extern "C" fn xmlTextReaderSetErrorHandler(
6627 reader: *mut XmlTextReader,
6628 f: Option<xmlTextReaderErrorFunc>,
6629 arg: *mut c_void,
6630) {
6631 if reader.is_null() {
6632 return;
6633 }
6634 unsafe {
6635 (*reader).error_handler = f;
6636 (*reader).error_arg = arg;
6637 }
6638}
6639
6640/// `void xmlTextReaderGetErrorHandler(xmlTextReaderPtr reader, xmlTextReaderErrorFunc *f, void **arg)`.
6641///
6642/// # SAFETY
6643///
6644/// - `f`/`arg` must be valid out-pointers or NULL.
6645#[no_mangle]
6646pub unsafe extern "C" fn xmlTextReaderGetErrorHandler(
6647 reader: *mut XmlTextReader,
6648 f: *mut Option<xmlTextReaderErrorFunc>,
6649 arg: *mut *mut c_void,
6650) {
6651 if reader.is_null() {
6652 return;
6653 }
6654 unsafe {
6655 if !f.is_null() {
6656 *f = (*reader).error_handler;
6657 }
6658 if !arg.is_null() {
6659 *arg = (*reader).error_arg;
6660 }
6661 }
6662}
6663
6664/// `void xmlTextReaderSetStructuredErrorHandler(xmlTextReaderPtr reader, xmlStructuredErrorFunc f, void *arg)`.
6665///
6666/// # SAFETY
6667///
6668/// - `reader`, `arg` must be valid pointers (or NULL
6669/// where the upstream C contract allows), obtained from the
6670/// matching constructor/owner and not yet freed; the callee may
6671/// take or keep ownership exactly as the C API specifies.
6672///
6673/// - `f` must be a valid callback (or None);
6674/// the callback is invoked with the documented context pointer and
6675/// must itself uphold the same pointer invariants.
6676///
6677/// The caller must not race this call with concurrent mutation of the
6678/// same objects from other threads (per-object state is not internally
6679/// synchronized). Violating any of the above is undefined behavior.
6680///
6681/// Exercised by the C-API differential courts
6682/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6683/// courts; those pass byte-for-byte against the upstream oracle.
6684#[no_mangle]
6685pub unsafe extern "C" fn xmlTextReaderSetStructuredErrorHandler(
6686 reader: *mut XmlTextReader,
6687 f: Option<crate::abi::callbacks::xmlStructuredErrorFunc>,
6688 arg: *mut c_void,
6689) {
6690 if reader.is_null() {
6691 return;
6692 }
6693 unsafe {
6694 (*reader).structured_handler = f;
6695 (*reader).structured_arg = arg;
6696 }
6697}
6698
6699/// `void xmlTextReaderSetResourceLoader(xmlTextReaderPtr reader,
6700/// xmlResourceLoader loader, void *data)` — install a custom resource
6701/// loader; stored on the reader and forwarded to its parser context
6702/// (upstream xmlreader.c).
6703///
6704/// # SAFETY
6705///
6706/// - `reader`, `data` must be valid pointers (or NULL
6707/// where the upstream C contract allows), obtained from the
6708/// matching constructor/owner and not yet freed; the callee may
6709/// take or keep ownership exactly as the C API specifies.
6710///
6711/// - `loader` must be a valid callback (or None);
6712/// the callback is invoked with the documented context pointer and
6713/// must itself uphold the same pointer invariants.
6714///
6715/// The caller must not race this call with concurrent mutation of the
6716/// same objects from other threads (per-object state is not internally
6717/// synchronized). Violating any of the above is undefined behavior.
6718///
6719/// Exercised by the C-API differential courts
6720/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6721/// courts; those pass byte-for-byte against the upstream oracle.
6722#[no_mangle]
6723pub unsafe extern "C" fn xmlTextReaderSetResourceLoader(
6724 reader: *mut XmlTextReader,
6725 loader: Option<crate::abi::callbacks::xmlResourceLoader>,
6726 data: *mut c_void,
6727) {
6728 if reader.is_null() {
6729 return;
6730 }
6731 unsafe {
6732 if !(*reader).ctxt.is_null() {
6733 crate::abi::exports_parserint::xmlCtxtSetResourceLoader((*reader).ctxt, loader, data);
6734 }
6735 }
6736}
6737
6738/// `const xmlError *xmlTextReaderGetLastError(xmlTextReaderPtr reader)` —
6739/// pointer to the reader's embedded `_xmlError` (upstream returns
6740/// `&reader->ctxt->lastError`, which is always present while the reader
6741/// exists; valid until the next error is collected).
6742///
6743/// # SAFETY
6744///
6745/// - `reader` must be valid pointers (or NULL
6746/// where the upstream C contract allows), obtained from the
6747/// matching constructor/owner and not yet freed; the callee may
6748/// take or keep ownership exactly as the C API specifies.
6749///
6750/// The caller must not race this call with concurrent mutation of the
6751/// same objects from other threads (per-object state is not internally
6752/// synchronized). Violating any of the above is undefined behavior.
6753///
6754/// Exercised by the C-API differential courts
6755/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6756/// courts; those pass byte-for-byte against the upstream oracle.
6757#[no_mangle]
6758pub unsafe extern "C" fn xmlTextReaderGetLastError(
6759 reader: *mut XmlTextReader,
6760) -> *const crate::abi::structs::_xmlError {
6761 if reader.is_null() {
6762 return ptr::null();
6763 }
6764 let r = unsafe { &mut *reader };
6765 // Sync the embedded struct from the most recent collected error, if any.
6766 // With no errors the struct stays zeroed (message NULL) — matching the
6767 // oracle, which still returns a non-NULL pointer here.
6768 if let Some(msg) = r.errors.last() {
6769 unsafe {
6770 // Message is a fresh NUL-terminated xmlMalloc copy owned by the
6771 // reader (freed on replacement and on drop).
6772 let bytes = msg.as_bytes();
6773 let m = libc::malloc(bytes.len() + 1) as *mut xmlChar;
6774 if !m.is_null() {
6775 libc::memcpy(
6776 m as *mut libc::c_void,
6777 bytes.as_ptr() as *const libc::c_void,
6778 bytes.len(),
6779 );
6780 *m.add(bytes.len()) = 0;
6781 if !r.last_err.message.is_null() {
6782 libc::free(r.last_err.message as *mut libc::c_void);
6783 }
6784 (*reader).last_err.message = m as *mut c_char;
6785 (*reader).last_err.domain = crate::abi::types::XML_FROM_PARSER as c_int;
6786 (*reader).last_err.level = crate::abi::types::xmlErrorLevel::XML_ERR_ERROR as c_int;
6787 (*reader).last_err.code = crate::abi::types::XML_ERR_INTERNAL_ERROR as c_int;
6788 }
6789 }
6790 }
6791 &(*reader).last_err as *const crate::abi::structs::_xmlError
6792}
6793
6794/// `xmlChar *xmlTextReaderLocatorBaseURI(xmlTextReaderLocatorPtr locator)`.
6795///
6796/// # SAFETY
6797///
6798/// - `locator` must be valid or NULL.
6799#[no_mangle]
6800pub unsafe extern "C" fn xmlTextReaderLocatorBaseURI(
6801 locator: *mut XmlTextReaderLocator,
6802) -> *mut xmlChar {
6803 if locator.is_null() {
6804 return ptr::null_mut();
6805 }
6806 unsafe {
6807 let r = (*locator).reader;
6808 if r.is_null() {
6809 return ptr::null_mut();
6810 }
6811 xml_strdup((*r).URL)
6812 }
6813}
6814
6815/// `int xmlTextReaderLocatorLineNumber(xmlTextReaderLocatorPtr locator)`.
6816///
6817/// # SAFETY
6818///
6819/// - `locator` must be valid pointers (or NULL
6820/// where the upstream C contract allows), obtained from the
6821/// matching constructor/owner and not yet freed; the callee may
6822/// take or keep ownership exactly as the C API specifies.
6823///
6824/// The caller must not race this call with concurrent mutation of the
6825/// same objects from other threads (per-object state is not internally
6826/// synchronized). Violating any of the above is undefined behavior.
6827///
6828/// Exercised by the C-API differential courts
6829/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6830/// courts; those pass byte-for-byte against the upstream oracle.
6831#[no_mangle]
6832pub unsafe extern "C" fn xmlTextReaderLocatorLineNumber(
6833 locator: *mut XmlTextReaderLocator,
6834) -> c_int {
6835 if locator.is_null() {
6836 return -1;
6837 }
6838 unsafe {
6839 let r = (*locator).reader;
6840 if r.is_null() {
6841 return -1;
6842 }
6843 let node = (*r).cur_node;
6844 if node.is_null() {
6845 return -1;
6846 }
6847 (*node).line as c_int
6848 }
6849}
6850
6851/// `xmlParserInputBufferPtr xmlTextReaderGetRemainder(xmlTextReaderPtr reader)`.
6852///
6853/// Returns NULL — the candidate reads the whole input up front (documented
6854/// divergence: no unconsumed input remains).
6855///
6856/// # SAFETY
6857///
6858/// - `_reader` must be valid pointers (or NULL
6859/// where the upstream C contract allows), obtained from the
6860/// matching constructor/owner and not yet freed; the callee may
6861/// take or keep ownership exactly as the C API specifies.
6862///
6863/// The caller must not race this call with concurrent mutation of the
6864/// same objects from other threads (per-object state is not internally
6865/// synchronized). Violating any of the above is undefined behavior.
6866///
6867/// Exercised by the C-API differential courts
6868/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6869/// courts; those pass byte-for-byte against the upstream oracle.
6870#[no_mangle]
6871pub const unsafe extern "C" fn xmlTextReaderGetRemainder(
6872 _reader: *mut XmlTextReader,
6873) -> *mut crate::abi::structs::_xmlParserInputBuffer {
6874 ptr::null_mut()
6875}
6876
6877/// `void xmlTextReaderSetMaxAmplification(xmlTextReaderPtr reader, unsigned maxAmpl)`.
6878///
6879/// # SAFETY
6880///
6881/// - `reader` must be valid pointers (or NULL
6882/// where the upstream C contract allows), obtained from the
6883/// matching constructor/owner and not yet freed; the callee may
6884/// take or keep ownership exactly as the C API specifies.
6885///
6886/// The caller must not race this call with concurrent mutation of the
6887/// same objects from other threads (per-object state is not internally
6888/// synchronized). Violating any of the above is undefined behavior.
6889///
6890/// Exercised by the C-API differential courts
6891/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6892/// courts; those pass byte-for-byte against the upstream oracle.
6893#[no_mangle]
6894pub unsafe extern "C" fn xmlTextReaderSetMaxAmplification(
6895 reader: *mut XmlTextReader,
6896 maxAmpl: c_uint,
6897) {
6898 if reader.is_null() {
6899 return;
6900 }
6901 unsafe { (*reader).max_amplification = maxAmpl as c_int };
6902}
6903
6904/// `int xmlTextReaderSchemaValidate(xmlTextReaderPtr reader, const char *xsd)` —
6905/// parse `xsd` and validate the reader's document as it is processed.
6906///
6907/// UPSTREAM-PARITY (xmlreader.c xmlTextReaderSchemaValidateInternal):
6908/// activation is only possible before the first Read(); the schema is
6909/// compiled NOW (a compile failure returns -1, which php reports as
6910/// "Schema contains errors") and the document is validated at read time —
6911/// validity diagnostics surface on the reader's error channel while
6912/// Read() streams, not as this call's return value.
6913///
6914/// # SAFETY
6915///
6916/// - `xsd` must be a valid path or NULL.
6917#[no_mangle]
6918pub unsafe extern "C" fn xmlTextReaderSchemaValidate(
6919 reader: *mut XmlTextReader,
6920 xsd: *const c_char,
6921) -> c_int {
6922 if reader.is_null() {
6923 return -1;
6924 }
6925 let r = unsafe { &mut *reader };
6926 if r.parsed {
6927 // Upstream: only XML_TEXTREADER_MODE_INITIAL readers can activate.
6928 return -1;
6929 }
6930 if xsd.is_null() {
6931 // Deactivate validation.
6932 if r.schema_owned && !r.schema.is_null() {
6933 // SAFETY: schema was compiled by xmlSchemaParse.
6934 unsafe { crate::xml::schemas::xmlSchemaFree(r.schema) };
6935 }
6936 r.schema = ptr::null_mut();
6937 r.schema_owned = false;
6938 r.xsd_result = -1;
6939 return 0;
6940 }
6941 // Compile the schema now. I/O and well-formedness diagnostics of the
6942 // schema document are raised by the parser machinery inside this call
6943 // (php prefixes them with XMLReader::setSchema()).
6944 let pctxt = crate::xml::schemas::xmlSchemaNewParserCtxt(xsd);
6945 if pctxt.is_null() {
6946 return -1;
6947 }
6948 let schema = crate::xml::schemas::xmlSchemaParse(pctxt);
6949 crate::xml::schemas::xmlSchemaFreeParserCtxt(pctxt);
6950 if schema.is_null() {
6951 return -1;
6952 }
6953 // Drop a previously attached reader-owned schema.
6954 if r.schema_owned && !r.schema.is_null() {
6955 // SAFETY: schema was compiled by xmlSchemaParse.
6956 unsafe { crate::xml::schemas::xmlSchemaFree(r.schema) };
6957 }
6958 r.schema = schema;
6959 r.schema_owned = true;
6960 r.xsd_result = -1;
6961 0
6962}
6963
6964/// `int xmlTextReaderSchemaValidateCtxt(xmlTextReaderPtr reader, xmlSchemaValidCtxtPtr ctxt, int options)`.
6965///
6966/// # SAFETY
6967///
6968/// - `reader`, `ctxt` must be valid pointers (or NULL
6969/// where the upstream C contract allows), obtained from the
6970/// matching constructor/owner and not yet freed; the callee may
6971/// take or keep ownership exactly as the C API specifies.
6972///
6973/// The caller must not race this call with concurrent mutation of the
6974/// same objects from other threads (per-object state is not internally
6975/// synchronized). Violating any of the above is undefined behavior.
6976///
6977/// Exercised by the C-API differential courts
6978/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6979/// courts; those pass byte-for-byte against the upstream oracle.
6980#[no_mangle]
6981pub unsafe extern "C" fn xmlTextReaderSchemaValidateCtxt(
6982 reader: *mut XmlTextReader,
6983 ctxt: *mut c_void,
6984 _options: c_int,
6985) -> c_int {
6986 if reader.is_null() || ctxt.is_null() {
6987 return -1;
6988 }
6989 if unsafe { (*reader).doc }.is_null() && !unsafe { (*reader).parsed } {
6990 unsafe { (*reader).Read() };
6991 }
6992 crate::xml::schemas::xmlSchemaValidateDoc(ctxt, unsafe { (*reader).doc })
6993}
6994
6995/// `int xmlTextReaderSetSchema(xmlTextReaderPtr reader, xmlSchemaPtr schema)`.
6996///
6997/// # SAFETY
6998///
6999/// - `reader`, `schema` must be valid pointers (or NULL
7000/// where the upstream C contract allows), obtained from the
7001/// matching constructor/owner and not yet freed; the callee may
7002/// take or keep ownership exactly as the C API specifies.
7003///
7004/// The caller must not race this call with concurrent mutation of the
7005/// same objects from other threads (per-object state is not internally
7006/// synchronized). Violating any of the above is undefined behavior.
7007///
7008/// Exercised by the C-API differential courts
7009/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
7010/// courts; those pass byte-for-byte against the upstream oracle.
7011#[no_mangle]
7012pub unsafe extern "C" fn xmlTextReaderSetSchema(
7013 reader: *mut XmlTextReader,
7014 schema: *mut c_void,
7015) -> c_int {
7016 if reader.is_null() {
7017 return -1;
7018 }
7019 unsafe {
7020 let r = &mut *reader;
7021 // Drop a previously attached reader-owned schema (a caller-owned one
7022 // is released by its owner; upstream never frees the passed-in
7023 // schema).
7024 if r.schema_owned && !r.schema.is_null() && r.schema != schema {
7025 crate::xml::schemas::xmlSchemaFree(r.schema);
7026 }
7027 r.schema = schema;
7028 r.schema_owned = false;
7029 r.xsd_result = -1;
7030 }
7031 0
7032}
7033
7034/// `int xmlTextReaderRelaxNGValidate(xmlTextReaderPtr reader, const char *rng)`.
7035///
7036/// # SAFETY
7037///
7038/// - `reader` must be valid pointers (or NULL
7039/// where the upstream C contract allows), obtained from the
7040/// matching constructor/owner and not yet freed; the callee may
7041/// take or keep ownership exactly as the C API specifies.
7042///
7043/// - `rng` must point to valid NUL-terminated
7044/// strings (or NULL where the C contract allows) for the lifetime
7045/// of the call.
7046///
7047/// The caller must not race this call with concurrent mutation of the
7048/// same objects from other threads (per-object state is not internally
7049/// synchronized). Violating any of the above is undefined behavior.
7050///
7051/// Exercised by the C-API differential courts
7052/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
7053/// courts; those pass byte-for-byte against the upstream oracle.
7054#[no_mangle]
7055pub unsafe extern "C" fn xmlTextReaderRelaxNGValidate(
7056 reader: *mut XmlTextReader,
7057 rng: *const c_char,
7058) -> c_int {
7059 if reader.is_null() {
7060 return -1;
7061 }
7062 let r = unsafe { &mut *reader };
7063 if r.parsed {
7064 // Upstream: only XML_TEXTREADER_MODE_INITIAL readers can activate.
7065 return -1;
7066 }
7067 if rng.is_null() {
7068 // Deactivate validation.
7069 if r.rng_owned && !r.rng.is_null() {
7070 // SAFETY: rng was compiled by xmlRelaxNGParse.
7071 unsafe { crate::xml::relaxng::xmlRelaxNGFree(r.rng) };
7072 }
7073 r.rng = ptr::null_mut();
7074 r.rng_owned = false;
7075 r.rng_result = -1;
7076 return 0;
7077 }
7078 // Compile the schema now (I/O diagnostics of the schema resource are
7079 // raised inside this call); validation itself is deferred to read time.
7080 let pctxt = crate::xml::relaxng::xmlRelaxNGNewParserCtxt(rng);
7081 if pctxt.is_null() {
7082 return -1;
7083 }
7084 let schema = crate::xml::relaxng::xmlRelaxNGParse(pctxt);
7085 crate::xml::relaxng::xmlRelaxNGFreeParserCtxt(pctxt);
7086 if schema.is_null() {
7087 return -1;
7088 }
7089 if r.rng_owned && !r.rng.is_null() {
7090 // SAFETY: rng was compiled by xmlRelaxNGParse.
7091 unsafe { crate::xml::relaxng::xmlRelaxNGFree(r.rng) };
7092 }
7093 r.rng = schema;
7094 r.rng_owned = true;
7095 r.rng_result = -1;
7096 0
7097}
7098
7099/// `int xmlTextReaderRelaxNGValidateCtxt(xmlTextReaderPtr reader, xmlRelaxNGValidCtxtPtr ctxt, int options)`.
7100///
7101/// # SAFETY
7102///
7103/// - `reader`, `ctxt` must be valid pointers (or NULL
7104/// where the upstream C contract allows), obtained from the
7105/// matching constructor/owner and not yet freed; the callee may
7106/// take or keep ownership exactly as the C API specifies.
7107///
7108/// The caller must not race this call with concurrent mutation of the
7109/// same objects from other threads (per-object state is not internally
7110/// synchronized). Violating any of the above is undefined behavior.
7111///
7112/// Exercised by the C-API differential courts
7113/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
7114/// courts; those pass byte-for-byte against the upstream oracle.
7115#[no_mangle]
7116pub unsafe extern "C" fn xmlTextReaderRelaxNGValidateCtxt(
7117 reader: *mut XmlTextReader,
7118 ctxt: *mut c_void,
7119 _options: c_int,
7120) -> c_int {
7121 if reader.is_null() || ctxt.is_null() {
7122 return -1;
7123 }
7124 if unsafe { (*reader).doc }.is_null() && !unsafe { (*reader).parsed } {
7125 unsafe { (*reader).Read() };
7126 }
7127 crate::xml::relaxng::xmlRelaxNGValidateDoc(ctxt, unsafe { (*reader).doc })
7128}
7129
7130/// `int xmlTextReaderRelaxNGSetSchema(xmlTextReaderPtr reader, xmlRelaxNGPtr schema)`.
7131///
7132/// # SAFETY
7133///
7134/// - `reader`, `schema` must be valid pointers (or NULL
7135/// where the upstream C contract allows), obtained from the
7136/// matching constructor/owner and not yet freed; the callee may
7137/// take or keep ownership exactly as the C API specifies.
7138///
7139/// The caller must not race this call with concurrent mutation of the
7140/// same objects from other threads (per-object state is not internally
7141/// synchronized). Violating any of the above is undefined behavior.
7142///
7143/// Exercised by the C-API differential courts
7144/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
7145/// courts; those pass byte-for-byte against the upstream oracle.
7146#[no_mangle]
7147pub unsafe extern "C" fn xmlTextReaderRelaxNGSetSchema(
7148 reader: *mut XmlTextReader,
7149 schema: *mut c_void,
7150) -> c_int {
7151 if reader.is_null() {
7152 return -1;
7153 }
7154 unsafe {
7155 let r = &mut *reader;
7156 // Drop a previously attached reader-owned schema (a caller-owned one
7157 // — php keeps it in intern->schema — is released by its owner).
7158 if r.rng_owned && !r.rng.is_null() && r.rng != schema {
7159 crate::xml::relaxng::xmlRelaxNGFree(r.rng);
7160 }
7161 r.rng = schema;
7162 r.rng_owned = false;
7163 r.rng_result = -1;
7164 }
7165 0
7166}