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 = match unsafe { crate::abi::exports_parser::open_filename_routed(filename) } {
2640 crate::abi::exports_parser::RoutedFileOpen::Loaded(input) => input,
2641 crate::abi::exports_parser::RoutedFileOpen::Failed => {
2642 // SAFETY: ctxt is valid.
2643 unsafe { free_parser_ctxt(ctxt) };
2644 return ptr::null_mut();
2645 }
2646 crate::abi::exports_parser::RoutedFileOpen::Builtin => {
2647 match unsafe { input_from_file(filename) } {
2648 Ok(input) => input,
2649 Err(_) => {
2650 // SAFETY: ctxt is valid.
2651 unsafe { free_parser_ctxt(ctxt) };
2652 return ptr::null_mut();
2653 }
2654 }
2655 }
2656 };
2657
2658 // SAFETY: ctxt and input are valid.
2659 unsafe { setup_parser_input(ctxt, input) };
2660 unsafe {
2661 (*ctxt).options = options;
2662 }
2663
2664 let enc_bytes = if encoding.is_null() {
2665 None
2666 } else {
2667 // SAFETY: encoding is a valid C string.
2668 unsafe {
2669 let cstr = std::ffi::CStr::from_ptr(encoding);
2670 Some(cstr.to_bytes().to_vec())
2671 }
2672 };
2673
2674 let mut reader = XmlTextReader::new(ctxt, None, enc_bytes.as_deref());
2675 reader.options = options;
2676 Box::into_raw(Box::new(reader))
2677}
2678
2679/// Create a text reader from memory.
2680///
2681/// # UPSTREAM-PARITY
2682///
2683/// ```c
2684/// xmlTextReaderPtr xmlReaderForMemory(const char *buffer, int size,
2685/// const char *URL, const char *encoding, int options);
2686/// ```
2687///
2688/// # Safety
2689///
2690/// - `buffer` must be a valid pointer with at least `size` readable bytes.
2691/// - `URL` and `encoding` must be valid C strings or NULL.
2692#[no_mangle]
2693pub unsafe extern "C" fn xmlReaderForMemory(
2694 buffer: *const c_char,
2695 size: c_int,
2696 URL: *const c_char,
2697 encoding: *const c_char,
2698 options: c_int,
2699) -> *mut XmlTextReader {
2700 if buffer.is_null() || size <= 0 {
2701 return ptr::null_mut();
2702 }
2703
2704 // SAFETY: create_parser_ctxt returns a valid context or NULL.
2705 let ctxt = unsafe { create_parser_ctxt() };
2706 if ctxt.is_null() {
2707 return ptr::null_mut();
2708 }
2709
2710 // SAFETY: input_from_memory copies the data; buffer and size are valid.
2711 // UPSTREAM-PARITY: the URL is recorded as the input's filename.
2712 let input = unsafe { input_from_memory_named(buffer, size, URL) };
2713
2714 // SAFETY: ctxt and input are valid.
2715 unsafe { setup_parser_input(ctxt, input) };
2716 unsafe {
2717 (*ctxt).options = options;
2718 }
2719
2720 let url_bytes = if URL.is_null() {
2721 None
2722 } else {
2723 // SAFETY: URL is a valid C string.
2724 unsafe {
2725 let cstr = std::ffi::CStr::from_ptr(URL);
2726 Some(cstr.to_bytes().to_vec())
2727 }
2728 };
2729
2730 let enc_bytes = if encoding.is_null() {
2731 None
2732 } else {
2733 // SAFETY: encoding is a valid C string.
2734 unsafe {
2735 let cstr = std::ffi::CStr::from_ptr(encoding);
2736 Some(cstr.to_bytes().to_vec())
2737 }
2738 };
2739
2740 let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
2741 reader.options = options;
2742 Box::into_raw(Box::new(reader))
2743}
2744
2745/// Create a text reader from a file descriptor.
2746///
2747/// # UPSTREAM-PARITY
2748///
2749/// ```c
2750/// xmlTextReaderPtr xmlReaderForFd(int fd, const char *URL,
2751/// const char *encoding, int options);
2752/// ```
2753///
2754/// # Safety
2755///
2756/// - `fd` must be a valid open file descriptor.
2757/// - `URL` and `encoding` must be valid C strings or NULL.
2758#[no_mangle]
2759pub unsafe extern "C" fn xmlReaderForFd(
2760 fd: c_int,
2761 URL: *const c_char,
2762 encoding: *const c_char,
2763 options: c_int,
2764) -> *mut XmlTextReader {
2765 // SAFETY: create_parser_ctxt returns a valid context or NULL.
2766 let ctxt = unsafe { create_parser_ctxt() };
2767 if ctxt.is_null() {
2768 return ptr::null_mut();
2769 }
2770
2771 // Read all data from the fd.
2772 let mut buf = Vec::new();
2773 let mut tmp = [0u8; 4096];
2774 loop {
2775 // SAFETY: fd must be a valid open file descriptor.
2776 let n = unsafe { libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len()) };
2777 if n <= 0 {
2778 break;
2779 }
2780 buf.extend_from_slice(&tmp[..n as usize]);
2781 }
2782
2783 // SAFETY: input_from_memory copies the buffer contents.
2784 let input = unsafe { input_from_memory(buf.as_ptr() as *const c_char, buf.len() as c_int) };
2785
2786 // SAFETY: ctxt and input are valid.
2787 unsafe { setup_parser_input(ctxt, input) };
2788 unsafe {
2789 (*ctxt).options = options;
2790 }
2791
2792 let url_bytes = if URL.is_null() {
2793 None
2794 } else {
2795 // SAFETY: URL is a valid C string.
2796 unsafe {
2797 let cstr = std::ffi::CStr::from_ptr(URL);
2798 Some(cstr.to_bytes().to_vec())
2799 }
2800 };
2801
2802 let enc_bytes = if encoding.is_null() {
2803 None
2804 } else {
2805 // SAFETY: encoding is a valid C string.
2806 unsafe {
2807 let cstr = std::ffi::CStr::from_ptr(encoding);
2808 Some(cstr.to_bytes().to_vec())
2809 }
2810 };
2811
2812 let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
2813 reader.options = options;
2814 Box::into_raw(Box::new(reader))
2815}
2816
2817/// Create a text reader from I/O callbacks.
2818///
2819/// # UPSTREAM-PARITY
2820///
2821/// ```c
2822/// xmlTextReaderPtr xmlReaderForIO(xmlInputReadCallback ioread, xmlInputCloseCallback ioclose,
2823/// void *ioctx, const char *URL,
2824/// const char *encoding, int options);
2825/// ```
2826///
2827/// # Safety
2828///
2829/// - `ioread` and `ioclose` must be valid function pointers or None.
2830/// - `ioctx` must be a valid context pointer for the callbacks.
2831/// - `URL` and `encoding` must be valid C strings or NULL.
2832#[no_mangle]
2833pub unsafe extern "C" fn xmlReaderForIO(
2834 ioread: Option<xmlInputReadCallback>,
2835 ioclose: Option<xmlInputCloseCallback>,
2836 ioctx: *mut c_void,
2837 URL: *const c_char,
2838 encoding: *const c_char,
2839 options: c_int,
2840) -> *mut XmlTextReader {
2841 // SAFETY: create_parser_ctxt returns a valid context or NULL.
2842 let ctxt = unsafe { create_parser_ctxt() };
2843 if ctxt.is_null() {
2844 return ptr::null_mut();
2845 }
2846
2847 // SAFETY: input_from_io reads all data via callbacks.
2848 let input = unsafe { input_from_io(ioread, ioclose, ioctx) };
2849
2850 // SAFETY: ctxt and input are valid.
2851 unsafe { setup_parser_input(ctxt, input) };
2852 unsafe {
2853 (*ctxt).options = options;
2854 }
2855
2856 let url_bytes = if URL.is_null() {
2857 None
2858 } else {
2859 // SAFETY: URL is a valid C string.
2860 unsafe {
2861 let cstr = std::ffi::CStr::from_ptr(URL);
2862 Some(cstr.to_bytes().to_vec())
2863 }
2864 };
2865
2866 let enc_bytes = if encoding.is_null() {
2867 None
2868 } else {
2869 // SAFETY: encoding is a valid C string.
2870 unsafe {
2871 let cstr = std::ffi::CStr::from_ptr(encoding);
2872 Some(cstr.to_bytes().to_vec())
2873 }
2874 };
2875
2876 let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
2877 reader.options = options;
2878 Box::into_raw(Box::new(reader))
2879}
2880
2881// ─────────────────────────────────────────────────────────────────────────────
2882// Navigation functions
2883// ─────────────────────────────────────────────────────────────────────────────
2884
2885/// Advance the reader to the next node in document order.
2886///
2887/// Returns 1 on success, 0 if EOF, -1 on error.
2888///
2889/// # UPSTREAM-PARITY
2890///
2891/// ```c
2892/// int xmlTextReaderRead(xmlTextReaderPtr reader);
2893/// ```
2894///
2895/// # Safety
2896///
2897/// `reader` must be a valid pointer returned by `xmlNewTextReader` or one of the
2898/// `xmlReaderFor*` functions, or NULL (in which case -1 is returned).
2899#[no_mangle]
2900pub unsafe extern "C" fn xmlTextReaderRead(reader: *mut XmlTextReader) -> c_int {
2901 if reader.is_null() {
2902 return -1;
2903 }
2904 // SAFETY: reader is valid.
2905 unsafe { (*reader).Read() }
2906}
2907
2908/// Skip to the next sibling of the current node.
2909///
2910/// Returns 1 on success, 0 if no more siblings, -1 on error.
2911///
2912/// # UPSTREAM-PARITY
2913///
2914/// ```c
2915/// int xmlTextReaderNext(xmlTextReaderPtr reader);
2916/// ```
2917///
2918/// # Safety
2919///
2920/// `reader` must be a valid pointer or NULL.
2921#[no_mangle]
2922pub unsafe extern "C" fn xmlTextReaderNext(reader: *mut XmlTextReader) -> c_int {
2923 if reader.is_null() {
2924 return -1;
2925 }
2926 // SAFETY: reader is valid.
2927 unsafe { (*reader).Next() }
2928}
2929
2930/// Skip to the next sibling (same as xmlTextReaderNext).
2931///
2932/// # UPSTREAM-PARITY
2933///
2934/// ```c
2935/// int xmlTextReaderNextSibling(xmlTextReaderPtr reader);
2936/// ```
2937///
2938/// # Safety
2939///
2940/// `reader` must be a valid pointer or NULL.
2941#[no_mangle]
2942pub unsafe extern "C" fn xmlTextReaderNextSibling(reader: *mut XmlTextReader) -> c_int {
2943 if reader.is_null() {
2944 return -1;
2945 }
2946 // SAFETY: reader is valid.
2947 unsafe { (*reader).Next() }
2948}
2949
2950/// Skip to the previous sibling of the current node.
2951///
2952/// Returns 1 on success, 0 if no previous sibling, -1 on error.
2953///
2954/// # UPSTREAM-PARITY
2955///
2956/// ```c
2957/// int xmlTextReaderPrev(xmlTextReaderPtr reader);
2958/// ```
2959///
2960/// # Safety
2961///
2962/// `reader` must be a valid pointer or NULL.
2963#[no_mangle]
2964pub unsafe extern "C" fn xmlTextReaderPrev(reader: *mut XmlTextReader) -> c_int {
2965 if reader.is_null() {
2966 return -1;
2967 }
2968 // SAFETY: reader is valid.
2969 unsafe { (*reader).Prev() }
2970}
2971
2972/// Move the reader back to the parent element (from an attribute).
2973///
2974/// Returns 1 on success, 0 if not on an attribute, -1 on error.
2975///
2976/// # UPSTREAM-PARITY
2977///
2978/// ```c
2979/// int xmlTextReaderMoveToElement(xmlTextReaderPtr reader);
2980/// ```
2981///
2982/// # Safety
2983///
2984/// `reader` must be a valid pointer or NULL.
2985#[no_mangle]
2986pub unsafe extern "C" fn xmlTextReaderMoveToElement(reader: *mut XmlTextReader) -> c_int {
2987 if reader.is_null() {
2988 return -1;
2989 }
2990 // SAFETY: reader is valid.
2991 unsafe { (*reader).MoveToElement() }
2992}
2993
2994/// Move to an attribute by name.
2995///
2996/// Returns 1 on success, 0 if not found, -1 on error.
2997///
2998/// # UPSTREAM-PARITY
2999///
3000/// ```c
3001/// int xmlTextReaderMoveToAttribute(xmlTextReaderPtr reader, const xmlChar *name);
3002/// ```
3003///
3004/// # Safety
3005///
3006/// `reader` must be a valid pointer or NULL. `name` must be a valid C string or NULL.
3007#[no_mangle]
3008pub unsafe extern "C" fn xmlTextReaderMoveToAttribute(
3009 reader: *mut XmlTextReader,
3010 name: *const xmlChar,
3011) -> c_int {
3012 if reader.is_null() || name.is_null() {
3013 return -1;
3014 }
3015 // SAFETY: reader and name are valid.
3016 unsafe { (*reader).MoveToAttribute(name) }
3017}
3018
3019/// Move to an attribute by index.
3020///
3021/// Returns 1 on success, 0 if not found, -1 on error.
3022///
3023/// # UPSTREAM-PARITY
3024///
3025/// ```c
3026/// int xmlTextReaderMoveToAttributeNo(xmlTextReaderPtr reader, int index);
3027/// ```
3028///
3029/// # Safety
3030///
3031/// `reader` must be a valid pointer or NULL.
3032#[no_mangle]
3033pub unsafe extern "C" fn xmlTextReaderMoveToAttributeNo(
3034 reader: *mut XmlTextReader,
3035 index: c_int,
3036) -> c_int {
3037 if reader.is_null() {
3038 return -1;
3039 }
3040 // SAFETY: reader is valid.
3041 unsafe { (*reader).MoveToAttributeNo(index) }
3042}
3043
3044/// Move to the first attribute of the current element.
3045///
3046/// Returns 1 on success, 0 if no attributes, -1 on error.
3047///
3048/// # UPSTREAM-PARITY
3049///
3050/// ```c
3051/// int xmlTextReaderMoveToFirstAttribute(xmlTextReaderPtr reader);
3052/// ```
3053///
3054/// # Safety
3055///
3056/// `reader` must be a valid pointer or NULL.
3057#[no_mangle]
3058pub unsafe extern "C" fn xmlTextReaderMoveToFirstAttribute(reader: *mut XmlTextReader) -> c_int {
3059 if reader.is_null() {
3060 return -1;
3061 }
3062 // SAFETY: reader is valid.
3063 unsafe { (*reader).MoveToFirstAttribute() }
3064}
3065
3066/// Move to the next attribute.
3067///
3068/// Returns 1 on success, 0 if no more attributes, -1 on error.
3069///
3070/// # UPSTREAM-PARITY
3071///
3072/// ```c
3073/// int xmlTextReaderMoveToNextAttribute(xmlTextReaderPtr reader);
3074/// ```
3075///
3076/// # Safety
3077///
3078/// `reader` must be a valid pointer or NULL.
3079#[no_mangle]
3080pub unsafe extern "C" fn xmlTextReaderMoveToNextAttribute(reader: *mut XmlTextReader) -> c_int {
3081 if reader.is_null() {
3082 return -1;
3083 }
3084 // SAFETY: reader is valid.
3085 unsafe { (*reader).MoveToNextAttribute() }
3086}
3087
3088// ─────────────────────────────────────────────────────────────────────────────
3089// Information methods
3090// ─────────────────────────────────────────────────────────────────────────────
3091
3092/// Get the attribute count of the current element.
3093///
3094/// Returns the number of attributes, or -1 if not on an element.
3095///
3096/// # UPSTREAM-PARITY
3097///
3098/// ```c
3099/// int xmlTextReaderAttributeCount(xmlTextReaderPtr reader);
3100/// ```
3101///
3102/// # Safety
3103///
3104/// `reader` must be a valid pointer or NULL.
3105#[no_mangle]
3106pub unsafe extern "C" fn xmlTextReaderAttributeCount(reader: *mut XmlTextReader) -> c_int {
3107 if reader.is_null() {
3108 return -1;
3109 }
3110 // SAFETY: reader is valid.
3111 unsafe { (*reader).AttributeCount() }
3112}
3113
3114/// Get the depth of the current node.
3115///
3116/// Returns the depth (0 for root element), or -1 on error.
3117///
3118/// # UPSTREAM-PARITY
3119///
3120/// ```c
3121/// int xmlTextReaderDepth(xmlTextReaderPtr reader);
3122/// ```
3123///
3124/// # Safety
3125///
3126/// `reader` must be a valid pointer or NULL.
3127#[no_mangle]
3128pub unsafe extern "C" fn xmlTextReaderDepth(reader: *mut XmlTextReader) -> c_int {
3129 if reader.is_null() {
3130 return -1;
3131 }
3132 // SAFETY: reader is valid.
3133 unsafe { (*reader).Depth() }
3134}
3135
3136/// Get the node type of the current node.
3137///
3138/// Returns one of the `xmlReaderTypes` constants, or -1 on error.
3139///
3140/// # UPSTREAM-PARITY
3141///
3142/// ```c
3143/// int xmlTextReaderNodeType(xmlTextReaderPtr reader);
3144/// ```
3145///
3146/// # Safety
3147///
3148/// `reader` must be a valid pointer or NULL.
3149#[no_mangle]
3150pub unsafe extern "C" fn xmlTextReaderNodeType(reader: *mut XmlTextReader) -> c_int {
3151 if reader.is_null() {
3152 return -1;
3153 }
3154 // SAFETY: reader is valid.
3155 unsafe { (*reader).NodeType() as c_int }
3156}
3157
3158/// Get the name of the current node.
3159///
3160/// Returns a newly allocated string (caller must free with `xmlFree`),
3161/// or NULL if there is no name.
3162///
3163/// # UPSTREAM-PARITY
3164///
3165/// ```c
3166/// xmlChar *xmlTextReaderName(xmlTextReaderPtr reader);
3167/// ```
3168///
3169/// # Safety
3170///
3171/// `reader` must be a valid pointer or NULL.
3172#[no_mangle]
3173pub unsafe extern "C" fn xmlTextReaderName(reader: *mut XmlTextReader) -> *mut xmlChar {
3174 if reader.is_null() {
3175 return ptr::null_mut();
3176 }
3177 // SAFETY: reader is valid.
3178 unsafe { (*reader).Name() }
3179}
3180
3181/// Get the value of the current node.
3182///
3183/// Returns a newly allocated string (caller must free with `xmlFree`),
3184/// or NULL if there is no value.
3185///
3186/// # UPSTREAM-PARITY
3187///
3188/// ```c
3189/// xmlChar *xmlTextReaderValue(xmlTextReaderPtr reader);
3190/// ```
3191///
3192/// # Safety
3193///
3194/// `reader` must be a valid pointer or NULL.
3195#[no_mangle]
3196pub unsafe extern "C" fn xmlTextReaderValue(reader: *mut XmlTextReader) -> *mut xmlChar {
3197 if reader.is_null() {
3198 return ptr::null_mut();
3199 }
3200 // SAFETY: reader is valid.
3201 unsafe { (*reader).Value() }
3202}
3203
3204/// Get a constant pointer to the name (no copy).
3205///
3206/// The returned pointer is valid only while the reader is alive and positioned
3207/// on the same node.
3208///
3209/// # UPSTREAM-PARITY
3210///
3211/// ```c
3212/// const xmlChar *xmlTextReaderConstName(xmlTextReaderPtr reader);
3213/// ```
3214///
3215/// # Safety
3216///
3217/// `reader` must be a valid pointer or NULL.
3218#[no_mangle]
3219pub unsafe extern "C" fn xmlTextReaderConstName(reader: *mut XmlTextReader) -> *const xmlChar {
3220 if reader.is_null() {
3221 return ptr::null();
3222 }
3223 // SAFETY: reader is valid.
3224 unsafe { (*reader).ConstName() }
3225}
3226
3227/// Get a constant pointer to the value (no copy).
3228///
3229/// The returned pointer is valid only while the reader is alive and positioned
3230/// on the same node.
3231///
3232/// # UPSTREAM-PARITY
3233///
3234/// ```c
3235/// const xmlChar *xmlTextReaderConstValue(xmlTextReaderPtr reader);
3236/// ```
3237///
3238/// # Safety
3239///
3240/// `reader` must be a valid pointer or NULL.
3241#[no_mangle]
3242pub unsafe extern "C" fn xmlTextReaderConstValue(reader: *mut XmlTextReader) -> *const xmlChar {
3243 if reader.is_null() {
3244 return ptr::null();
3245 }
3246 // SAFETY: reader is valid.
3247 unsafe { (*reader).ConstValue() }
3248}
3249
3250/// Get the base URI of the current node.
3251///
3252/// Returns a newly allocated string (caller must free with `xmlFree`),
3253/// or NULL if not available.
3254///
3255/// # UPSTREAM-PARITY
3256///
3257/// ```c
3258/// xmlChar *xmlTextReaderBaseUri(xmlTextReaderPtr reader);
3259/// ```
3260///
3261/// # Safety
3262///
3263/// `reader` must be a valid pointer or NULL.
3264#[no_mangle]
3265pub unsafe extern "C" fn xmlTextReaderBaseUri(reader: *mut XmlTextReader) -> *mut xmlChar {
3266 if reader.is_null() {
3267 return ptr::null_mut();
3268 }
3269 // SAFETY: reader is valid.
3270 unsafe { (*reader).BaseUri() }
3271}
3272
3273/// Get the local name of the current node.
3274///
3275/// Returns a newly allocated string, or NULL.
3276///
3277/// # UPSTREAM-PARITY
3278///
3279/// ```c
3280/// xmlChar *xmlTextReaderLocalName(xmlTextReaderPtr reader);
3281/// ```
3282///
3283/// # Safety
3284///
3285/// `reader` must be a valid pointer or NULL.
3286#[no_mangle]
3287pub unsafe extern "C" fn xmlTextReaderLocalName(reader: *mut XmlTextReader) -> *mut xmlChar {
3288 if reader.is_null() {
3289 return ptr::null_mut();
3290 }
3291 // SAFETY: reader is valid.
3292 unsafe { (*reader).LocalName() }
3293}
3294
3295/// Get the namespace URI of the current node.
3296///
3297/// Returns a newly allocated string, or NULL.
3298///
3299/// # UPSTREAM-PARITY
3300///
3301/// ```c
3302/// xmlChar *xmlTextReaderNamespaceUri(xmlTextReaderPtr reader);
3303/// ```
3304///
3305/// # Safety
3306///
3307/// `reader` must be a valid pointer or NULL.
3308#[no_mangle]
3309pub unsafe extern "C" fn xmlTextReaderNamespaceUri(reader: *mut XmlTextReader) -> *mut xmlChar {
3310 if reader.is_null() {
3311 return ptr::null_mut();
3312 }
3313 // SAFETY: reader is valid.
3314 unsafe { (*reader).NamespaceUri() }
3315}
3316
3317/// Get the prefix of the current node.
3318///
3319/// Returns a newly allocated string, or NULL.
3320///
3321/// # UPSTREAM-PARITY
3322///
3323/// ```c
3324/// xmlChar *xmlTextReaderPrefix(xmlTextReaderPtr reader);
3325/// ```
3326///
3327/// # Safety
3328///
3329/// `reader` must be a valid pointer or NULL.
3330#[no_mangle]
3331pub unsafe extern "C" fn xmlTextReaderPrefix(reader: *mut XmlTextReader) -> *mut xmlChar {
3332 if reader.is_null() {
3333 return ptr::null_mut();
3334 }
3335 // SAFETY: reader is valid.
3336 unsafe { (*reader).Prefix() }
3337}
3338
3339/// Check if the current node has a value.
3340///
3341/// Returns 1 if the node has a value, 0 otherwise.
3342///
3343/// # UPSTREAM-PARITY
3344///
3345/// ```c
3346/// int xmlTextReaderHasValue(xmlTextReaderPtr reader);
3347/// ```
3348///
3349/// # Safety
3350///
3351/// `reader` must be a valid pointer or NULL.
3352#[no_mangle]
3353pub unsafe extern "C" fn xmlTextReaderHasValue(reader: *mut XmlTextReader) -> c_int {
3354 if reader.is_null() {
3355 return 0;
3356 }
3357 // SAFETY: reader is valid.
3358 unsafe { (*reader).HasValue() }
3359}
3360
3361/// Check if the current node has attributes.
3362///
3363/// Returns 1 if the node has attributes, 0 otherwise.
3364///
3365/// # UPSTREAM-PARITY
3366///
3367/// ```c
3368/// int xmlTextReaderHasAttributes(xmlTextReaderPtr reader);
3369/// ```
3370///
3371/// # Safety
3372///
3373/// `reader` must be a valid pointer or NULL.
3374#[no_mangle]
3375pub unsafe extern "C" fn xmlTextReaderHasAttributes(reader: *mut XmlTextReader) -> c_int {
3376 if reader.is_null() {
3377 return 0;
3378 }
3379 // SAFETY: reader is valid.
3380 unsafe { (*reader).HasAttributes() }
3381}
3382
3383/// Check if the current element is an empty element (no children).
3384///
3385/// Returns 1 if empty, 0 otherwise.
3386///
3387/// # UPSTREAM-PARITY
3388///
3389/// ```c
3390/// int xmlTextReaderIsEmptyElement(xmlTextReaderPtr reader);
3391/// ```
3392///
3393/// # Safety
3394///
3395/// `reader` must be a valid pointer or NULL.
3396#[no_mangle]
3397pub unsafe extern "C" fn xmlTextReaderIsEmptyElement(reader: *mut XmlTextReader) -> c_int {
3398 if reader.is_null() {
3399 return 0;
3400 }
3401 // SAFETY: reader is valid.
3402 unsafe { (*reader).IsEmptyElement() }
3403}
3404
3405/// Get the read state.
3406///
3407/// Returns one of the `xmlTextReaderReadState` constants.
3408///
3409/// # UPSTREAM-PARITY
3410///
3411/// ```c
3412/// int xmlTextReaderReadState(xmlTextReaderPtr reader);
3413/// ```
3414///
3415/// # Safety
3416///
3417/// `reader` must be a valid pointer or NULL.
3418#[no_mangle]
3419pub unsafe extern "C" fn xmlTextReaderReadState(reader: *mut XmlTextReader) -> c_int {
3420 if reader.is_null() {
3421 return ReadState::ERROR as c_int;
3422 }
3423 // SAFETY: reader is valid.
3424 unsafe { (*reader).ReadState() as c_int }
3425}
3426
3427// ─────────────────────────────────────────────────────────────────────────────
3428// Attribute access
3429// ─────────────────────────────────────────────────────────────────────────────
3430
3431/// Get an attribute value by name.
3432///
3433/// Returns a newly allocated string, or NULL.
3434///
3435/// # UPSTREAM-PARITY
3436///
3437/// ```c
3438/// xmlChar *xmlTextReaderGetAttribute(xmlTextReaderPtr reader, const xmlChar *name);
3439/// ```
3440///
3441/// # Safety
3442///
3443/// `reader` and `name` must be valid pointers or NULL.
3444#[no_mangle]
3445pub unsafe extern "C" fn xmlTextReaderGetAttribute(
3446 reader: *mut XmlTextReader,
3447 name: *const xmlChar,
3448) -> *mut xmlChar {
3449 if reader.is_null() || name.is_null() {
3450 return ptr::null_mut();
3451 }
3452 // SAFETY: reader and name are valid.
3453 unsafe { (*reader).GetAttribute(name) }
3454}
3455
3456/// Get an attribute value by index.
3457///
3458/// Returns a newly allocated string, or NULL.
3459///
3460/// # UPSTREAM-PARITY
3461///
3462/// ```c
3463/// xmlChar *xmlTextReaderGetAttributeNo(xmlTextReaderPtr reader, int index);
3464/// ```
3465///
3466/// # Safety
3467///
3468/// `reader` must be a valid pointer or NULL.
3469#[no_mangle]
3470pub unsafe extern "C" fn xmlTextReaderGetAttributeNo(
3471 reader: *mut XmlTextReader,
3472 index: c_int,
3473) -> *mut xmlChar {
3474 if reader.is_null() {
3475 return ptr::null_mut();
3476 }
3477 // SAFETY: reader is valid.
3478 unsafe { (*reader).GetAttributeNo(index) }
3479}
3480
3481/// Get an attribute value by local name and namespace URI.
3482///
3483/// Returns a newly allocated string, or NULL.
3484///
3485/// # UPSTREAM-PARITY
3486///
3487/// ```c
3488/// xmlChar *xmlTextReaderGetAttributeNs(xmlTextReaderPtr reader,
3489/// const xmlChar *localName,
3490/// const xmlChar *namespaceURI);
3491/// ```
3492///
3493/// # Safety
3494///
3495/// `reader`, `localName`, and `namespaceURI` must be valid pointers or NULL.
3496#[no_mangle]
3497pub unsafe extern "C" fn xmlTextReaderGetAttributeNs(
3498 reader: *mut XmlTextReader,
3499 localName: *const xmlChar,
3500 namespaceURI: *const xmlChar,
3501) -> *mut xmlChar {
3502 if reader.is_null() || localName.is_null() {
3503 return ptr::null_mut();
3504 }
3505 // SAFETY: reader, localName, and namespaceURI are valid.
3506 unsafe { (*reader).GetAttributeNs(localName, namespaceURI) }
3507}
3508
3509/// Look up a namespace by prefix.
3510///
3511/// Returns a newly allocated string with the namespace URI, or NULL.
3512///
3513/// # UPSTREAM-PARITY
3514///
3515/// ```c
3516/// xmlChar *xmlTextReaderLookupNamespace(xmlTextReaderPtr reader, const xmlChar *prefix);
3517/// ```
3518///
3519/// # Safety
3520///
3521/// `reader` and `prefix` must be valid pointers or NULL.
3522#[no_mangle]
3523pub unsafe extern "C" fn xmlTextReaderLookupNamespace(
3524 reader: *mut XmlTextReader,
3525 prefix: *const xmlChar,
3526) -> *mut xmlChar {
3527 if reader.is_null() {
3528 return ptr::null_mut();
3529 }
3530 // SAFETY: reader and prefix are valid.
3531 unsafe { (*reader).LookupNamespace(prefix) }
3532}
3533
3534// ─────────────────────────────────────────────────────────────────────────────
3535// Parser properties
3536// ─────────────────────────────────────────────────────────────────────────────
3537
3538/// Get a parser property.
3539///
3540/// Returns the property value (0 or 1), or -1 on error.
3541///
3542/// # UPSTREAM-PARITY
3543///
3544/// ```c
3545/// int xmlTextReaderGetParserProp(xmlTextReaderPtr reader, int prop);
3546/// ```
3547///
3548/// # Safety
3549///
3550/// `reader` must be a valid pointer or NULL.
3551#[no_mangle]
3552pub unsafe extern "C" fn xmlTextReaderGetParserProp(
3553 reader: *mut XmlTextReader,
3554 prop: c_int,
3555) -> c_int {
3556 if reader.is_null() {
3557 return -1;
3558 }
3559 // SAFETY: reader is valid.
3560 unsafe { (*reader).GetParserProp(prop) }
3561}
3562
3563/// Set a parser property.
3564///
3565/// Returns 0 on success, -1 on error.
3566///
3567/// # UPSTREAM-PARITY
3568///
3569/// ```c
3570/// int xmlTextReaderSetParserProp(xmlTextReaderPtr reader, int prop, int value);
3571/// ```
3572///
3573/// # Safety
3574///
3575/// `reader` must be a valid pointer or NULL.
3576#[no_mangle]
3577pub unsafe extern "C" fn xmlTextReaderSetParserProp(
3578 reader: *mut XmlTextReader,
3579 prop: c_int,
3580 value: c_int,
3581) -> c_int {
3582 if reader.is_null() {
3583 return -1;
3584 }
3585 // SAFETY: reader is valid.
3586 unsafe { (*reader).SetParserProp(prop, value) }
3587}
3588
3589// ─────────────────────────────────────────────────────────────────────────────
3590// Lifecycle
3591// ─────────────────────────────────────────────────────────────────────────────
3592
3593/// Free a text reader.
3594///
3595/// # UPSTREAM-PARITY
3596///
3597/// ```c
3598/// void xmlFreeTextReader(xmlTextReaderPtr reader);
3599/// ```
3600///
3601/// # Safety
3602///
3603/// `reader` must be a valid pointer returned by `xmlNewTextReader` or one of the
3604/// `xmlReaderFor*` functions, or NULL (in which case this is a no-op).
3605#[no_mangle]
3606pub unsafe extern "C" fn xmlFreeTextReader(reader: *mut XmlTextReader) {
3607 if reader.is_null() {
3608 return;
3609 }
3610 // SAFETY: reader was created via Box::into_raw, so we reconstruct the Box
3611 // and let it drop, which calls the Drop impl.
3612 unsafe {
3613 let _ = Box::from_raw(reader);
3614 }
3615}
3616
3617/// Setup/reinitialize a reader with new input.
3618///
3619/// # UPSTREAM-PARITY
3620///
3621/// ```c
3622/// int xmlTextReaderSetup(xmlTextReaderPtr reader,
3623/// xmlParserInputBufferPtr input,
3624/// const char *URL, const char *encoding, int options);
3625/// ```
3626///
3627/// # Safety
3628///
3629/// - `reader` must be a valid pointer or NULL.
3630/// - `input` must be a valid `_xmlParserInputBuffer` pointer or NULL.
3631/// - `URL` and `encoding` must be valid C strings or NULL.
3632#[no_mangle]
3633pub unsafe extern "C" fn xmlTextReaderSetup(
3634 reader: *mut XmlTextReader,
3635 input: *mut _xmlParserInputBuffer,
3636 URL: *const c_char,
3637 encoding: *const c_char,
3638 options: c_int,
3639) -> c_int {
3640 if reader.is_null() {
3641 return -1;
3642 }
3643
3644 // SAFETY: reader is valid.
3645 let r = unsafe { &mut *reader };
3646
3647 // Reset the reader state.
3648 r.clear_cached_name();
3649 r.clear_cached_value();
3650
3651 r.events.clear();
3652 r.event_index = 0;
3653 r.state = ReadState::INITIALIZED;
3654 r.cur_node = ptr::null_mut();
3655 r.node_type = ReaderNodeType::NONE;
3656 r.depth = 0;
3657 r.attribute_count = -1;
3658 r.cur_attribute = -1;
3659 r.options = options;
3660 r.parsed = false;
3661 r.errors.clear();
3662 r.doc_incomplete = false;
3663 r.finalized = false;
3664 r.reparse_input = None;
3665
3666 // Update URL.
3667 if !r.URL.is_null() {
3668 // SAFETY: URL was allocated by xmlMalloc.
3669 unsafe { xmlFreeImpl(r.URL as *mut c_void) };
3670 r.URL = ptr::null_mut();
3671 }
3672 if !URL.is_null() {
3673 // SAFETY: URL is a valid C string.
3674 let url_str = unsafe { std::ffi::CStr::from_ptr(URL) };
3675 // SAFETY: bytes_to_xmlstr allocates via xmlMalloc.
3676 r.URL = unsafe { bytes_to_xmlstr(url_str.to_bytes()) };
3677 }
3678
3679 // Update encoding.
3680 if !r.encoding.is_null() {
3681 // SAFETY: encoding was allocated by xmlMalloc.
3682 unsafe { xmlFreeImpl(r.encoding as *mut c_void) };
3683 r.encoding = ptr::null_mut();
3684 }
3685 if !encoding.is_null() {
3686 // SAFETY: encoding is a valid C string.
3687 let enc_str = unsafe { std::ffi::CStr::from_ptr(encoding) };
3688 // SAFETY: bytes_to_xmlstr allocates via xmlMalloc.
3689 r.encoding = unsafe { bytes_to_xmlstr(enc_str.to_bytes()) };
3690 }
3691
3692 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderSetup): a NULL input means
3693 // "use the input the reader was created with" — PHP's XMLReader::XML() /
3694 // fromString() call xmlNewTextReader(inputbfr, uri) and then
3695 // xmlTextReaderSetup(reader, NULL, uri, encoding, options). The
3696 // candidate consumed that buffer in reader_from_input (the context is
3697 // already wired), so a NULL input keeps the existing context; a NEW
3698 // input replaces it (C consumers re-arming the reader).
3699 if input.is_null() {
3700 // UPSTREAM-PARITY: a NULL input promotes the source the reader was
3701 // created with (php XMLReader::XML()/fromString(): xmlNewTextReader
3702 // then xmlTextReaderSetup(reader, NULL, ...)). The candidate
3703 // consumed that source in reader_from_input, so the context built
3704 // there is kept. A reader that ALREADY parsed (its context was
3705 // consumed by parse_and_build_events) discards the old document and
3706 // resets — upstream xmlTextReaderSetup tears the previous parse
3707 // state down.
3708 if r.ctxt.is_null() && !r.doc.is_null() {
3709 // SAFETY: doc was allocated by the parser.
3710 unsafe { tree::free_doc(r.doc) };
3711 r.doc = ptr::null_mut();
3712 }
3713 return 0;
3714 }
3715
3716 // Free the old document.
3717 if !r.doc.is_null() {
3718 // SAFETY: doc was allocated by the parser.
3719 unsafe { tree::free_doc(r.doc) };
3720 r.doc = ptr::null_mut();
3721 }
3722
3723 // Free old parser context.
3724 if !r.ctxt.is_null() {
3725 // SAFETY: ctxt was created by create_parser_ctxt.
3726 unsafe { free_parser_ctxt(r.ctxt) };
3727 r.ctxt = ptr::null_mut();
3728 }
3729
3730 // Create new parser context and set up input.
3731 // SAFETY: create_parser_ctxt returns a valid context or NULL.
3732 let ctxt = unsafe { create_parser_ctxt() };
3733 if ctxt.is_null() {
3734 return -1;
3735 }
3736
3737 // Read all data from the input buffer. A memory buffer
3738 // (xmlParserInputBufferCreateMem) has no read callback — its content
3739 // lives in the input-buffer content stash (helpers.rs).
3740 let mut data = Vec::new();
3741 let mut tmp = [0u8; 4096];
3742
3743 // SAFETY: input is valid.
3744 let read_cb = unsafe { (*input).readcallback };
3745 let ioctx = unsafe { (*input).context };
3746
3747 if let Some(read) = read_cb {
3748 loop {
3749 // SAFETY: callbacks are valid.
3750 let n = unsafe { read(ioctx, tmp.as_mut_ptr() as *mut c_char, tmp.len() as c_int) };
3751 if n <= 0 {
3752 break;
3753 }
3754 data.extend_from_slice(&tmp[..n as usize]);
3755 }
3756 } else {
3757 data = crate::xml::parser::helpers::take_input_buf_content(input).unwrap_or_default();
3758 }
3759
3760 // Close the input.
3761 let close_cb = unsafe { (*input).closecallback };
3762 if let Some(close) = close_cb {
3763 // SAFETY: close callback is valid.
3764 unsafe { close(ioctx) };
3765 }
3766
3767 let input_buf = InputBuffer::from_memory(&data, None);
3768
3769 // SAFETY: ctxt and input_buf are valid.
3770 unsafe { setup_parser_input(ctxt, input_buf) };
3771 unsafe {
3772 (*ctxt).options = options;
3773 }
3774
3775 r.ctxt = ctxt;
3776
3777 0
3778}
3779
3780/// Get the current document from the reader.
3781///
3782/// Returns a pointer to the `_xmlDoc` or NULL.
3783///
3784/// # UPSTREAM-PARITY
3785///
3786/// ```c
3787/// xmlDocPtr xmlTextReaderCurrentDoc(xmlTextReaderPtr reader);
3788/// ```
3789///
3790/// # Safety
3791///
3792/// `reader` must be a valid pointer or NULL.
3793#[no_mangle]
3794pub unsafe extern "C" fn xmlTextReaderCurrentDoc(reader: *mut XmlTextReader) -> *mut _xmlDoc {
3795 if reader.is_null() {
3796 return ptr::null_mut();
3797 }
3798 // SAFETY: reader is valid. Upstream xmlTextReaderCurrentDoc sets
3799 // reader->preserve = 1 and returns ctxt->myDoc: the document ownership
3800 // moves to the caller (freed with xmlFreeDoc). Mirror that so the
3801 // reader's Drop does not free a doc the caller still owns (upstream
3802 // reader3.c pattern; Phase-12 EXTERNAL-CONSUMERS court).
3803 unsafe {
3804 (*reader).preserve = true;
3805 (*reader).CurrentDoc()
3806 }
3807}
3808
3809/// Close the reader, releasing the document and parser state.
3810///
3811/// # UPSTREAM-PARITY
3812///
3813/// Upstream `xmlTextReaderClose` (xmlreader.c): sets the mode to
3814/// XML_TEXTREADER_MODE_CLOSED, drops the current node, and tears down the
3815/// validation state. The reader object itself is freed separately with
3816/// `xmlFreeTextReader`.
3817///
3818/// ```c
3819/// int xmlTextReaderClose(xmlTextReaderPtr reader);
3820/// ```
3821///
3822/// Returns 0 on success, -1 if `reader` is NULL.
3823///
3824/// # Safety
3825///
3826/// `reader` must be a valid pointer or NULL.
3827#[no_mangle]
3828pub unsafe extern "C" fn xmlTextReaderClose(reader: *mut XmlTextReader) -> c_int {
3829 if reader.is_null() {
3830 return -1;
3831 }
3832 // SAFETY: reader is valid; close resets cursor state and marks the
3833 // reader closed, mirroring upstream's mode transition.
3834 unsafe {
3835 let r = &mut *reader;
3836 r.cur_node = ptr::null_mut();
3837 r.node_type = ReaderNodeType::NONE;
3838 r.clear_cached_name();
3839 r.clear_cached_value();
3840 r.state = ReadState::CLOSED;
3841 }
3842 0
3843}
3844
3845/// Return the current node of the reader.
3846///
3847/// # UPSTREAM-PARITY
3848///
3849/// ```c
3850/// xmlNodePtr xmlTextReaderCurrentNode(xmlTextReaderPtr reader);
3851/// ```
3852///
3853/// Returns the current node or NULL. The node is owned by the document;
3854/// the caller must not free it.
3855///
3856/// # Safety
3857///
3858/// `reader` must be a valid pointer or NULL.
3859#[no_mangle]
3860pub unsafe extern "C" fn xmlTextReaderCurrentNode(reader: *mut XmlTextReader) -> *mut _xmlNode {
3861 if reader.is_null() {
3862 return ptr::null_mut();
3863 }
3864 // SAFETY: reader is valid.
3865 unsafe { (*reader).cur_node }
3866}
3867
3868/// Expand entity references at the current position.
3869///
3870/// # UPSTREAM-PARITY
3871///
3872/// Upstream `xmlTextReaderExpand` (xmlreader.c) forces substitution of the
3873/// current entity reference so the node can be read in full. When the parser
3874/// ran with XML_PARSE_NOENT the entities are already substituted during
3875/// parsing; the function then simply returns the current node.
3876///
3877/// ```c
3878/// xmlNodePtr xmlTextReaderExpand(xmlTextReaderPtr reader);
3879/// ```
3880///
3881/// Returns the (expanded) current node, or NULL if the reader is NULL or
3882/// not positioned on a node.
3883///
3884/// # Safety
3885///
3886/// `reader` must be a valid pointer or NULL.
3887#[no_mangle]
3888pub unsafe extern "C" fn xmlTextReaderExpand(reader: *mut XmlTextReader) -> *mut _xmlNode {
3889 if reader.is_null() {
3890 return ptr::null_mut();
3891 }
3892 // SAFETY: reader is valid.
3893 unsafe { (*reader).cur_node }
3894}
3895
3896/// Return the parser line number of the current node.
3897///
3898/// # UPSTREAM-PARITY
3899///
3900/// Upstream `xmlTextReaderGetParserLineNumber` returns the input stream's
3901/// current line. The candidate records `line` per node during parsing, which
3902/// is equivalent for the read cursor.
3903///
3904/// ```c
3905/// int xmlTextReaderGetParserLineNumber(xmlTextReaderPtr reader);
3906/// ```
3907///
3908/// Returns the line number, or 0 when unavailable.
3909///
3910/// # Safety
3911///
3912/// `reader` must be a valid pointer or NULL.
3913#[no_mangle]
3914pub unsafe extern "C" fn xmlTextReaderGetParserLineNumber(reader: *mut XmlTextReader) -> c_int {
3915 if reader.is_null() {
3916 return 0;
3917 }
3918 // SAFETY: reader is valid; cur_node is owned by the doc.
3919 unsafe {
3920 let node = (*reader).cur_node;
3921 if node.is_null() {
3922 0
3923 } else {
3924 (*node).line as c_int
3925 }
3926 }
3927}
3928
3929/// Return the parser column number of the current node.
3930///
3931/// # UPSTREAM-PARITY
3932///
3933/// Upstream `xmlTextReaderGetParserColumnNumber` returns the input stream's
3934/// column. Columns are not tracked per-node in the candidate tree (upstream
3935/// exposes -1 when no column information is available either); return -1.
3936///
3937/// ```c
3938/// int xmlTextReaderGetParserColumnNumber(xmlTextReaderPtr reader);
3939/// ```
3940///
3941/// # Safety
3942///
3943/// `reader` must be a valid pointer or NULL.
3944#[no_mangle]
3945pub const unsafe extern "C" fn xmlTextReaderGetParserColumnNumber(
3946 reader: *mut XmlTextReader,
3947) -> c_int {
3948 if reader.is_null() {
3949 return -1;
3950 }
3951 -1
3952}
3953
3954/// Return the validation status of the reader.
3955///
3956/// # UPSTREAM-PARITY
3957///
3958/// Upstream `xmlTextReaderIsValid` (xmlreader.c): with a RELAX NG
3959/// validation context attached the RELAX NG outcome wins, then the XSD
3960/// outcome, then the DTD-parse validity (`ctxt->valid`); 0 when no
3961/// validation was performed or it failed, -1 for a NULL reader.
3962///
3963/// ```c
3964/// int xmlTextReaderIsValid(xmlTextReaderPtr reader);
3965/// ```
3966///
3967/// # Safety
3968///
3969/// `reader` must be a valid pointer or NULL.
3970#[no_mangle]
3971pub unsafe extern "C" fn xmlTextReaderIsValid(reader: *mut XmlTextReader) -> c_int {
3972 if reader.is_null() {
3973 return -1;
3974 }
3975 unsafe {
3976 let r = &*reader;
3977 if !r.rng.is_null() {
3978 if r.rng_result >= 0 {
3979 r.rng_result
3980 } else {
3981 // Validation not run yet (schema attached, first Read not
3982 // done): upstream's rngValidCtxt->valid starts at 0.
3983 0
3984 }
3985 } else if !r.schema.is_null() {
3986 if r.xsd_result >= 0 {
3987 r.xsd_result
3988 } else {
3989 0
3990 }
3991 } else if r.did_validate == 1 {
3992 r.was_valid
3993 } else {
3994 0
3995 }
3996 }
3997}
3998
3999/// Return the normalization status of the reader.
4000///
4001/// # UPSTREAM-PARITY
4002///
4003/// Upstream `xmlTextReaderNormalization` returns 1 when the reader performs
4004/// whitespace normalization (it always reports 1 unless the parser was
4005/// configured otherwise). The candidate normalizes attribute values per the
4006/// XML spec during parsing, so report 1.
4007///
4008/// ```c
4009/// int xmlTextReaderNormalization(xmlTextReaderPtr reader);
4010/// ```
4011///
4012/// # Safety
4013///
4014/// `reader` must be a valid pointer or NULL.
4015#[no_mangle]
4016pub const unsafe extern "C" fn xmlTextReaderNormalization(reader: *mut XmlTextReader) -> c_int {
4017 if reader.is_null() {
4018 return -1;
4019 }
4020 1
4021}
4022
4023/// Read the value of an attribute as a text node (attribute-value mode).
4024///
4025/// # UPSTREAM-PARITY
4026///
4027/// Upstream `xmlTextReaderReadAttributeValue` moves the reader so that the
4028/// value of the current attribute is available as a text node, returning 1
4029/// on success and 0 when already at the end. The candidate tree stores
4030/// attribute values directly on the attribute node, so the value is already
4031/// available via `xmlTextReaderValue`; report 1 when positioned on an
4032/// attribute with a value.
4033///
4034/// ```c
4035/// int xmlTextReaderReadAttributeValue(xmlTextReaderPtr reader);
4036/// ```
4037///
4038/// # Safety
4039///
4040/// `reader` must be a valid pointer or NULL.
4041#[no_mangle]
4042pub unsafe extern "C" fn xmlTextReaderReadAttributeValue(reader: *mut XmlTextReader) -> c_int {
4043 if reader.is_null() {
4044 return -1;
4045 }
4046 // SAFETY: reader is valid.
4047 unsafe {
4048 let r = &*reader;
4049 if r.node_type == ReaderNodeType::ATTRIBUTE && !r.cur_node.is_null() {
4050 1
4051 } else {
4052 0
4053 }
4054 }
4055}
4056
4057/// Read the content of the current node as a string.
4058///
4059/// # UPSTREAM-PARITY
4060///
4061/// Upstream `xmlTextReaderReadString` concatenates the text of the current
4062/// node's subtree (recursively) into one string. It behaves like
4063/// `xmlNodeGetContent` for the current node.
4064///
4065/// ```c
4066/// xmlChar *xmlTextReaderReadString(xmlTextReaderPtr reader);
4067/// ```
4068///
4069/// Returns a newly allocated string (free with `xmlFree`) or NULL.
4070///
4071/// # Safety
4072///
4073/// `reader` must be a valid pointer or NULL.
4074#[no_mangle]
4075pub unsafe extern "C" fn xmlTextReaderReadString(reader: *mut XmlTextReader) -> *mut xmlChar {
4076 if reader.is_null() {
4077 return ptr::null_mut();
4078 }
4079 // SAFETY: reader is valid; node owned by the document.
4080 unsafe {
4081 let node = (*reader).cur_node;
4082 if node.is_null() {
4083 return ptr::null_mut();
4084 }
4085 tree::node_get_content(node)
4086 }
4087}
4088
4089/// Read the inner XML of the current node as a string.
4090///
4091/// # UPSTREAM-PARITY
4092///
4093/// Upstream `xmlTextReaderReadInnerXml` serializes the children of the
4094/// current node. The candidate uses its serializer on the children list.
4095///
4096/// ```c
4097/// xmlChar *xmlTextReaderReadInnerXml(xmlTextReaderPtr reader);
4098/// ```
4099///
4100/// Returns a newly allocated string (free with `xmlFree`) or NULL.
4101///
4102/// # Safety
4103///
4104/// `reader` must be a valid pointer or NULL.
4105#[no_mangle]
4106pub unsafe extern "C" fn xmlTextReaderReadInnerXml(reader: *mut XmlTextReader) -> *mut xmlChar {
4107 if reader.is_null() {
4108 return ptr::null_mut();
4109 }
4110 // SAFETY: reader is valid; node owned by the document.
4111 unsafe {
4112 let node = (*reader).cur_node;
4113 if node.is_null() {
4114 return ptr::null_mut();
4115 }
4116 let buf = crate::xml::io::buf_create(-1);
4117 if buf.is_null() {
4118 return ptr::null_mut();
4119 }
4120 let mut child = (*node).children;
4121 while !child.is_null() {
4122 tree::serialize_node(child, buf, 0, 0);
4123 child = (*child).next;
4124 }
4125 let len = crate::xml::io::buf_length(buf) as usize;
4126 let content = crate::xml::io::buf_content(buf);
4127 if content.is_null() || len == 0 {
4128 crate::xml::io::buf_free(buf);
4129 return ptr::null_mut();
4130 }
4131 let out = xml_strdup(content);
4132 crate::xml::io::buf_free(buf);
4133 out
4134 }
4135}
4136
4137/// Read the outer XML of the current node as a string.
4138///
4139/// # UPSTREAM-PARITY
4140///
4141/// Upstream `xmlTextReaderReadOuterXml` serializes the current node itself.
4142///
4143/// ```c
4144/// xmlChar *xmlTextReaderReadOuterXml(xmlTextReaderPtr reader);
4145/// ```
4146///
4147/// Returns a newly allocated string (free with `xmlFree`) or NULL.
4148///
4149/// # Safety
4150///
4151/// `reader` must be a valid pointer or NULL.
4152#[no_mangle]
4153pub unsafe extern "C" fn xmlTextReaderReadOuterXml(reader: *mut XmlTextReader) -> *mut xmlChar {
4154 if reader.is_null() {
4155 return ptr::null_mut();
4156 }
4157 // SAFETY: reader is valid; node owned by the document.
4158 unsafe {
4159 let node = (*reader).cur_node;
4160 if node.is_null() {
4161 return ptr::null_mut();
4162 }
4163 let buf = crate::xml::io::buf_create(-1);
4164 if buf.is_null() {
4165 return ptr::null_mut();
4166 }
4167 tree::serialize_node(node, buf, 0, 0);
4168 let len = crate::xml::io::buf_length(buf) as usize;
4169 let content = crate::xml::io::buf_content(buf);
4170 if content.is_null() || len == 0 {
4171 crate::xml::io::buf_free(buf);
4172 return ptr::null_mut();
4173 }
4174 let out = xml_strdup(content);
4175 crate::xml::io::buf_free(buf);
4176 out
4177 }
4178}
4179
4180/// Return the standalone flag of the document being read.
4181///
4182/// # UPSTREAM-PARITY
4183///
4184/// Upstream `xmlTextReaderStandalone` returns the document's standalone
4185/// value (1 = standalone, 0 = not, -1 = no XML declaration / NULL reader).
4186///
4187/// ```c
4188/// int xmlTextReaderStandalone(xmlTextReaderPtr reader);
4189/// ```
4190///
4191/// # Safety
4192///
4193/// `reader` must be a valid pointer or NULL.
4194#[no_mangle]
4195pub unsafe extern "C" fn xmlTextReaderStandalone(reader: *mut XmlTextReader) -> c_int {
4196 if reader.is_null() {
4197 return -1;
4198 }
4199 // SAFETY: reader is valid; doc owned by the reader.
4200 unsafe {
4201 let doc = (*reader).doc;
4202 if doc.is_null() {
4203 return -1;
4204 }
4205 (*doc).standalone
4206 }
4207}
4208
4209/// Return the xml:lang of the current node.
4210///
4211/// # UPSTREAM-PARITY
4212///
4213/// Upstream `xmlTextReaderXmlLang` returns `xmlNodeGetLang(node)`: the
4214/// nearest `xml:lang` attribute on the node or an ancestor.
4215///
4216/// ```c
4217/// xmlChar *xmlTextReaderXmlLang(xmlTextReaderPtr reader);
4218/// ```
4219///
4220/// Returns a newly allocated string (free with `xmlFree`) or NULL.
4221///
4222/// # Safety
4223///
4224/// `reader` must be a valid pointer or NULL.
4225#[no_mangle]
4226pub unsafe extern "C" fn xmlTextReaderXmlLang(reader: *mut XmlTextReader) -> *mut xmlChar {
4227 if reader.is_null() {
4228 return ptr::null_mut();
4229 }
4230 // SAFETY: reader is valid; node owned by the document.
4231 unsafe {
4232 let mut node = (*reader).cur_node;
4233 while !node.is_null() {
4234 // walk the property list for xml:lang
4235 let mut prop = (*node).properties;
4236 while !prop.is_null() {
4237 if !(*prop).name.is_null() {
4238 let name = crate::xml::string::xmlstr_to_bytes((*prop).name);
4239 if name == b"lang" && !(*prop).ns.is_null() {
4240 let ns_href = crate::xml::string::xmlstr_to_bytes((*(*prop).ns).href);
4241 if ns_href == b"http://www.w3.org/XML/1998/namespace" {
4242 let v = (*prop).children;
4243 if !v.is_null() && !(*v).content.is_null() {
4244 return xml_strdup((*v).content);
4245 }
4246 }
4247 }
4248 }
4249 prop = (*prop).next;
4250 }
4251 node = (*node).parent;
4252 }
4253 ptr::null_mut()
4254 }
4255}
4256
4257// ═══════════════════════════════════════════════════════════════════════════════
4258// Tests
4259// ═══════════════════════════════════════════════════════════════════════════════
4260
4261#[cfg(test)]
4262mod tests {
4263 use super::*;
4264 use crate::abi::allocator::xmlFreeImpl;
4265 use core::ffi::c_void;
4266 use std::os::raw::c_char;
4267
4268 /// Helper: create a reader from a string.
4269 unsafe fn create_reader(xml: &str) -> *mut XmlTextReader {
4270 let bytes = xml.as_bytes();
4271 xmlReaderForMemory(
4272 bytes.as_ptr() as *const c_char,
4273 bytes.len() as c_int,
4274 ptr::null(),
4275 ptr::null(),
4276 0,
4277 )
4278 }
4279
4280 /// Helper: free a reader.
4281 unsafe fn free_reader(reader: *mut XmlTextReader) {
4282 if !reader.is_null() {
4283 xmlFreeTextReader(reader);
4284 }
4285 }
4286
4287 /// Helper: read through all nodes and collect their types and names.
4288 unsafe fn collect_nodes(reader: *mut XmlTextReader) -> Vec<(ReaderNodeType, String, i32)> {
4289 let mut result = Vec::new();
4290 loop {
4291 let ret = xmlTextReaderRead(reader);
4292 if ret <= 0 {
4293 break;
4294 }
4295 // SAFETY: reader is valid.
4296 let r = &*reader;
4297 let ntype = r.NodeType();
4298 let name = if r.name.is_null() {
4299 String::new()
4300 } else {
4301 xmlstr_to_string(r.name as *const xmlChar)
4302 };
4303 let depth = r.Depth();
4304 result.push((ntype, name, depth));
4305 }
4306 result
4307 }
4308
4309 // ─── Basic tests ───────────────────────────────────────────────────────
4310
4311 /// Creates a reader from a memory buffer and checks its initial state.
4312 ///
4313 /// # Safety
4314 ///
4315 /// - The reader is created from a static string literal that stays alive
4316 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4317 /// before it is dereferenced and is freed exactly once with
4318 /// `free_reader`.
4319 #[test]
4320 fn test_create_reader_from_memory() {
4321 unsafe {
4322 let reader = create_reader("<root/>");
4323 assert!(!reader.is_null());
4324 assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
4325 free_reader(reader);
4326 }
4327 }
4328
4329 /// Reads a simple document and verifies the event sequence.
4330 ///
4331 /// # Safety
4332 ///
4333 /// - The reader is created from a static string literal that stays alive
4334 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4335 /// before it is dereferenced and is freed exactly once with
4336 /// `free_reader`.
4337 #[test]
4338 fn test_read_simple_document() {
4339 unsafe {
4340 let reader = create_reader("<root><child>text</child></root>");
4341 assert!(!reader.is_null());
4342
4343 let nodes = collect_nodes(reader);
4344 // Expected sequence:
4345 // ELEMENT root (depth=0)
4346 // ELEMENT child (depth=1)
4347 // TEXT text (depth=2)
4348 // END_ELEMENT child (depth=1)
4349 // END_ELEMENT root (depth=0)
4350
4351 assert_eq!(nodes.len(), 5);
4352 assert_eq!(nodes[0], (ReaderNodeType::ELEMENT, "root".to_string(), 0));
4353 assert_eq!(nodes[1], (ReaderNodeType::ELEMENT, "child".to_string(), 1));
4354 // UPSTREAM-PARITY: text nodes report the fixed name "#text".
4355 assert_eq!(nodes[2], (ReaderNodeType::TEXT, "#text".to_string(), 2));
4356 assert_eq!(
4357 nodes[3],
4358 (ReaderNodeType::END_ELEMENT, "child".to_string(), 1)
4359 );
4360 assert_eq!(
4361 nodes[4],
4362 (ReaderNodeType::END_ELEMENT, "root".to_string(), 0)
4363 );
4364
4365 assert_eq!((*reader).ReadState(), ReadState::EOF);
4366 free_reader(reader);
4367 }
4368 }
4369
4370 /// SP-14.3.1-7 (fromStream_broken_stream): a document that is NOT
4371 /// complete when the input ends (an unterminated root) still delivers the
4372 /// completed prefix events — the root element and the comment — and the
4373 /// premature-EOF failure surfaces only on the read that runs past the
4374 /// last event, leaving the cursor on the last delivered node (upstream
4375 /// xmlTextReaderPushData's terminating xmlParseChunk).
4376 ///
4377 /// # Safety
4378 ///
4379 /// - The reader is created from a static string literal that stays alive
4380 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4381 /// before it is dereferenced and is freed exactly once with
4382 /// `free_reader`.
4383 #[test]
4384 fn test_read_incomplete_document_defers_eof_error() {
4385 unsafe {
4386 // Deliberately unterminated: <root><!--my comment--> (no </root>).
4387 let reader = create_reader("<root><!--my comment-->");
4388 assert!(!reader.is_null());
4389
4390 // read #1: the root element is delivered even though the document
4391 // is not complete.
4392 assert_eq!(xmlTextReaderRead(reader), 1);
4393 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4394 assert_eq!(xmlstr_to_bytes((*reader).Name()), b"root");
4395 assert_eq!((*reader).Depth(), 0);
4396
4397 // read #2: the comment.
4398 assert_eq!(xmlTextReaderRead(reader), 1);
4399 assert_eq!((*reader).NodeType(), ReaderNodeType::COMMENT);
4400 assert_eq!(xmlstr_to_bytes((*reader).Value()), b"my comment");
4401 assert_eq!((*reader).Depth(), 1);
4402
4403 // read #3: the parse needs more input than the source supplies;
4404 // the deferred EOF finalize reports the failure and the cursor
4405 // stays on the last delivered event (upstream
4406 // fromStream_broken_stream).
4407 assert_eq!(xmlTextReaderRead(reader), -1);
4408 assert_eq!((*reader).NodeType(), ReaderNodeType::COMMENT);
4409 assert_eq!((*reader).Depth(), 1);
4410
4411 // Later reads keep failing with the cursor frozen.
4412 assert_eq!(xmlTextReaderRead(reader), -1);
4413 assert_eq!((*reader).NodeType(), ReaderNodeType::COMMENT);
4414 assert_eq!((*reader).Depth(), 1);
4415
4416 free_reader(reader);
4417 }
4418 }
4419
4420 /// Verifies read state transitions from INITIALIZED to EOF.
4421 ///
4422 /// # Safety
4423 ///
4424 /// - The reader is created from a static string literal that stays alive
4425 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4426 /// before it is dereferenced and is freed exactly once with
4427 /// `free_reader`.
4428 #[test]
4429 fn test_read_state_transitions() {
4430 unsafe {
4431 let reader = create_reader("<root/>");
4432 assert!(!reader.is_null());
4433 assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
4434
4435 // First read.
4436 assert_eq!(xmlTextReaderRead(reader), 1);
4437 assert_eq!((*reader).ReadState(), ReadState::READING);
4438 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4439 assert_eq!((*reader).Depth(), 0);
4440
4441 // UPSTREAM-PARITY (oracle-verified 2.15.3): empty elements emit no
4442 // END_ELEMENT event — the second Read returns EOF directly.
4443 assert_eq!(xmlTextReaderRead(reader), 0);
4444 assert_eq!((*reader).ReadState(), ReadState::EOF);
4445
4446 free_reader(reader);
4447 }
4448 }
4449
4450 /// Verifies reader API entry points return error indicators for a NULL
4451 /// reader pointer.
4452 ///
4453 /// # Safety
4454 ///
4455 /// - NULL is passed only to reader API entry points that accept NULL and
4456 /// return error indicators without dereferencing the pointer.
4457 #[test]
4458 fn test_null_reader_returns_error() {
4459 unsafe {
4460 assert_eq!(xmlTextReaderRead(ptr::null_mut()), -1);
4461 assert_eq!(xmlTextReaderDepth(ptr::null_mut()), -1);
4462 assert_eq!(xmlTextReaderNodeType(ptr::null_mut()), -1);
4463 assert!(xmlTextReaderName(ptr::null_mut()).is_null());
4464 assert!(xmlTextReaderValue(ptr::null_mut()).is_null());
4465 assert_eq!(xmlTextReaderHasValue(ptr::null_mut()), 0);
4466 assert_eq!(xmlTextReaderIsEmptyElement(ptr::null_mut()), 0);
4467 assert_eq!(
4468 xmlTextReaderReadState(ptr::null_mut()),
4469 ReadState::ERROR as c_int
4470 );
4471 }
4472 }
4473
4474 /// Verifies freeing a NULL reader does not crash.
4475 ///
4476 /// # Safety
4477 ///
4478 /// - `xmlFreeTextReader` must tolerate a NULL pointer without
4479 /// dereferencing it.
4480 #[test]
4481 fn test_xmlFreeTextReader_null() {
4482 unsafe {
4483 // Should not crash.
4484 xmlFreeTextReader(ptr::null_mut());
4485 }
4486 }
4487
4488 /// Verifies `xmlTextReaderName` and `xmlTextReaderValue` results.
4489 ///
4490 /// # Safety
4491 ///
4492 /// - The `reader` pointer is asserted non-NULL before use and freed
4493 /// exactly once with `free_reader`.
4494 /// - `xmlTextReaderName` and `xmlTextReaderValue` return heap strings
4495 /// allocated via `xml_strdup`; each non-NULL result is freed exactly
4496 /// once with `xmlFreeImpl`.
4497 #[test]
4498 fn test_reader_name_and_value() {
4499 unsafe {
4500 let reader = create_reader("<root>hello</root>");
4501 assert!(!reader.is_null());
4502
4503 // Read root element.
4504 assert_eq!(xmlTextReaderRead(reader), 1);
4505 let name = xmlTextReaderName(reader);
4506 assert!(!name.is_null());
4507 assert_eq!(xmlstr_to_string(name), "root");
4508 xmlFreeImpl(name as *mut c_void);
4509
4510 assert_eq!(xmlTextReaderHasValue(reader), 0);
4511
4512 // Read text node.
4513 assert_eq!(xmlTextReaderRead(reader), 1);
4514 assert_eq!((*reader).NodeType(), ReaderNodeType::TEXT);
4515 assert_eq!((*reader).HasValue(), 1);
4516
4517 let val = xmlTextReaderValue(reader);
4518 assert!(!val.is_null());
4519 assert_eq!(xmlstr_to_string(val), "hello");
4520 xmlFreeImpl(val as *mut c_void);
4521
4522 free_reader(reader);
4523 }
4524 }
4525
4526 /// Verifies an empty element reports no END_ELEMENT event.
4527 ///
4528 /// # Safety
4529 ///
4530 /// - The reader is created from a static string literal that stays alive
4531 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4532 /// before it is dereferenced and is freed exactly once with
4533 /// `free_reader`.
4534 #[test]
4535 fn test_empty_element() {
4536 unsafe {
4537 let reader = create_reader("<empty/>");
4538 assert!(!reader.is_null());
4539
4540 assert_eq!(xmlTextReaderRead(reader), 1);
4541 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4542 assert_eq!((*reader).IsEmptyElement(), 1);
4543 assert_eq!((*reader).HasAttributes(), 0);
4544 assert_eq!((*reader).AttributeCount(), 0);
4545
4546 // UPSTREAM-PARITY (oracle-verified 2.15.3): empty elements emit
4547 // NO END_ELEMENT event; the next Read returns EOF.
4548 assert_eq!(xmlTextReaderRead(reader), 0);
4549 assert_eq!((*reader).ReadState(), ReadState::EOF);
4550
4551 free_reader(reader);
4552 }
4553 }
4554
4555 /// Verifies attribute counting on an element with two attributes.
4556 ///
4557 /// # Safety
4558 ///
4559 /// - The reader is created from a static string literal that stays alive
4560 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4561 /// before it is dereferenced and is freed exactly once with
4562 /// `free_reader`.
4563 #[test]
4564 fn test_element_with_attributes() {
4565 unsafe {
4566 let reader = create_reader(r#"<root a="1" b="2"/>"#);
4567 assert!(!reader.is_null());
4568
4569 assert_eq!(xmlTextReaderRead(reader), 1);
4570 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4571 assert_eq!((*reader).HasAttributes(), 1);
4572
4573 // We know the attribute count if we've built the events properly.
4574 // The count_attributes checks the element's properties list.
4575 let attrs = xmlTextReaderAttributeCount(reader);
4576 assert_eq!(attrs, 2);
4577
4578 free_reader(reader);
4579 }
4580 }
4581
4582 /// Verifies attribute navigation (first, next, by name, by index).
4583 ///
4584 /// # Safety
4585 ///
4586 /// - The reader is created from a static string literal that stays alive
4587 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4588 /// before it is dereferenced and is freed exactly once with
4589 /// `free_reader`.
4590 /// - The pointers returned by `xmlTextReaderConstName` and
4591 /// `xmlTextReaderConstValue` are borrowed from the reader and are not
4592 /// freed by the test.
4593 #[test]
4594 fn test_attribute_navigation() {
4595 unsafe {
4596 let reader = create_reader(r#"<root a="1" b="2"></root>"#);
4597 assert!(!reader.is_null());
4598
4599 // Position on root element.
4600 assert_eq!(xmlTextReaderRead(reader), 1);
4601 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4602
4603 // Move to first attribute.
4604 assert_eq!(xmlTextReaderMoveToFirstAttribute(reader), 1);
4605 assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
4606
4607 let name = xmlTextReaderConstName(reader);
4608 assert!(!name.is_null());
4609 assert_eq!(xmlstr_to_bytes(name), b"a");
4610
4611 let val = xmlTextReaderConstValue(reader);
4612 assert!(!val.is_null());
4613 assert_eq!(xmlstr_to_bytes(val), b"1");
4614
4615 // Move to next attribute.
4616 assert_eq!(xmlTextReaderMoveToNextAttribute(reader), 1);
4617 let name = xmlTextReaderConstName(reader);
4618 assert!(!name.is_null());
4619 assert_eq!(xmlstr_to_bytes(name), b"b");
4620 let val = xmlTextReaderConstValue(reader);
4621 assert!(!val.is_null());
4622 assert_eq!(xmlstr_to_bytes(val), b"2");
4623
4624 // No more attributes.
4625 assert_eq!(xmlTextReaderMoveToNextAttribute(reader), 0);
4626
4627 // Move back to element.
4628 assert_eq!(xmlTextReaderMoveToElement(reader), 1);
4629 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4630
4631 // Move to attribute by name.
4632 assert_eq!(
4633 xmlTextReaderMoveToAttribute(reader, b"a\0" as *const u8 as *const xmlChar),
4634 1
4635 );
4636 assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
4637
4638 // Move to attribute by index.
4639 assert_eq!(xmlTextReaderMoveToElement(reader), 1);
4640 assert_eq!(xmlTextReaderMoveToAttributeNo(reader, 1), 1);
4641 assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
4642
4643 free_reader(reader);
4644 }
4645 }
4646
4647 /// Verifies attribute lookup by name and by index.
4648 ///
4649 /// # Safety
4650 ///
4651 /// - The `reader` pointer is asserted non-NULL before use and freed
4652 /// exactly once with `free_reader`.
4653 /// - `xmlTextReaderGetAttribute` and `xmlTextReaderGetAttributeNo` return
4654 /// heap strings; each non-NULL result is freed exactly once with
4655 /// `xmlFreeImpl`.
4656 #[test]
4657 fn test_get_attribute() {
4658 unsafe {
4659 let reader = create_reader(r#"<root a="hello" b="world"/>"#);
4660 assert!(!reader.is_null());
4661
4662 assert_eq!(xmlTextReaderRead(reader), 1);
4663
4664 // Get attribute by name.
4665 let val = xmlTextReaderGetAttribute(reader, b"a\0" as *const u8 as *const xmlChar);
4666 assert!(!val.is_null());
4667 assert_eq!(xmlstr_to_bytes(val), b"hello");
4668 xmlFreeImpl(val as *mut c_void);
4669
4670 let val = xmlTextReaderGetAttribute(reader, b"b\0" as *const u8 as *const xmlChar);
4671 assert!(!val.is_null());
4672 assert_eq!(xmlstr_to_bytes(val), b"world");
4673 xmlFreeImpl(val as *mut c_void);
4674
4675 // Non-existent attribute.
4676 let val = xmlTextReaderGetAttribute(reader, b"c\0" as *const u8 as *const xmlChar);
4677 assert!(val.is_null());
4678
4679 // Get attribute by index.
4680 let val = xmlTextReaderGetAttributeNo(reader, 0);
4681 assert!(!val.is_null());
4682 assert_eq!(xmlstr_to_bytes(val), b"hello");
4683 xmlFreeImpl(val as *mut c_void);
4684
4685 let val = xmlTextReaderGetAttributeNo(reader, 1);
4686 assert!(!val.is_null());
4687 assert_eq!(xmlstr_to_bytes(val), b"world");
4688 xmlFreeImpl(val as *mut c_void);
4689
4690 let val = xmlTextReaderGetAttributeNo(reader, 2);
4691 assert!(val.is_null());
4692
4693 free_reader(reader);
4694 }
4695 }
4696
4697 /// Verifies depth tracking across nested elements.
4698 ///
4699 /// # Safety
4700 ///
4701 /// - The reader is created from a static string literal that stays alive
4702 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4703 /// before it is dereferenced and is freed exactly once with
4704 /// `free_reader`.
4705 #[test]
4706 fn test_depth_tracking() {
4707 unsafe {
4708 let reader = create_reader("<a><b><c/></b></a>");
4709 assert!(!reader.is_null());
4710
4711 let nodes = collect_nodes(reader);
4712 // UPSTREAM-PARITY (oracle-verified 2.15.3): empty elements emit no
4713 // END_ELEMENT event.
4714 // ELEMENT a (0), ELEMENT b (1), ELEMENT c (2),
4715 // END_ELEMENT b (1), END_ELEMENT a (0)
4716 assert_eq!(nodes.len(), 5);
4717 assert_eq!(nodes[0].2, 0); // a depth 0
4718 assert_eq!(nodes[1].2, 1); // b depth 1
4719 assert_eq!(nodes[2].2, 2); // c depth 2
4720 assert_eq!(nodes[3].2, 1); // END b depth 1
4721 assert_eq!(nodes[4].2, 0); // END a depth 0
4722
4723 free_reader(reader);
4724 }
4725 }
4726
4727 /// Verifies traversal of multiple sibling elements.
4728 ///
4729 /// # Safety
4730 ///
4731 /// - The reader is created from a static string literal that stays alive
4732 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4733 /// before it is dereferenced and is freed exactly once with
4734 /// `free_reader`.
4735 #[test]
4736 fn test_multiple_siblings() {
4737 unsafe {
4738 let reader = create_reader("<root><a>A</a><b>B</b><c>C</c></root>");
4739 assert!(!reader.is_null());
4740
4741 let nodes = collect_nodes(reader);
4742 // ELEMENT root(0), ELEMENT a(1), TEXT(2), END a(1),
4743 // ELEMENT b(1), TEXT(2), END b(1),
4744 // ELEMENT c(1), TEXT(2), END c(1),
4745 // END root(0)
4746 assert_eq!(nodes.len(), 11);
4747
4748 // Check the sibling elements.
4749 assert_eq!(nodes[1], (ReaderNodeType::ELEMENT, "a".to_string(), 1));
4750 assert_eq!(nodes[4], (ReaderNodeType::ELEMENT, "b".to_string(), 1));
4751 assert_eq!(nodes[7], (ReaderNodeType::ELEMENT, "c".to_string(), 1));
4752
4753 free_reader(reader);
4754 }
4755 }
4756
4757 /// Verifies `xmlTextReaderNext` semantics against the upstream streaming
4758 /// reader (xmlreader.c xmlTextReaderNext): from a NON-element node Next
4759 /// degrades to a plain Read — one step forward in document order, which
4760 /// may land on the parent's END_ELEMENT — and only an element START is
4761 /// skipped (subtree + END events) to the next sibling. On exhaustion the
4762 /// cursor is cleared (EOF).
4763 ///
4764 /// # Safety
4765 ///
4766 /// - The reader is created from a static string literal that stays alive
4767 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4768 /// before it is dereferenced and is freed exactly once with
4769 /// `free_reader`.
4770 /// - The `name` field read through `(*reader)` is borrowed from the
4771 /// reader and is not freed.
4772 #[test]
4773 fn test_next_skip_to_sibling() {
4774 unsafe {
4775 let reader = create_reader("<root><a>A</a><b>B</b><c>C</c></root>");
4776 assert!(!reader.is_null());
4777
4778 // Read to first node (root element).
4779 assert_eq!(xmlTextReaderRead(reader), 1);
4780 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4781
4782 // Read to a.
4783 assert_eq!(xmlTextReaderRead(reader), 1);
4784 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4785 assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "a");
4786
4787 // Read to text of a.
4788 assert_eq!(xmlTextReaderRead(reader), 1);
4789 assert_eq!((*reader).NodeType(), ReaderNodeType::TEXT);
4790
4791 // Next from a TEXT node is a plain Read: it lands on the END of
4792 // the parent element (upstream xmlTextReaderNext, oracle-verified
4793 // via XMLReader 010/next_basic).
4794 assert_eq!(xmlTextReaderNext(reader), 1);
4795 assert_eq!((*reader).NodeType(), ReaderNodeType::END_ELEMENT);
4796 assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "a");
4797
4798 // Next again from the END event — a single step to element b.
4799 assert_eq!(xmlTextReaderNext(reader), 1);
4800 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4801 assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "b");
4802
4803 // Next from the b element start skips its subtree to c.
4804 assert_eq!(xmlTextReaderNext(reader), 1);
4805 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4806 assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "c");
4807
4808 // Next again — the traversal is exhausted and the cursor clears.
4809 assert_eq!(xmlTextReaderNext(reader), 0);
4810 assert_eq!((*reader).NodeType(), ReaderNodeType::NONE);
4811
4812 free_reader(reader);
4813 }
4814 }
4815
4816 /// Verifies comment and processing-instruction nodes are reported.
4817 ///
4818 /// # Safety
4819 ///
4820 /// - The static NUL-terminated `xml` buffer stays valid for the
4821 /// `xmlReaderForMemory` call; the returned `reader` is asserted non-NULL
4822 /// before use and is freed exactly once with `free_reader`.
4823 #[test]
4824 fn test_comment_and_pi_nodes() {
4825 unsafe {
4826 let xml = b"<?pi target?><root><!-- comment -->text</root>\0";
4827 let reader = xmlReaderForMemory(
4828 xml.as_ptr() as *const c_char,
4829 (xml.len() - 1) as c_int,
4830 ptr::null(),
4831 ptr::null(),
4832 0,
4833 );
4834 assert!(!reader.is_null());
4835
4836 let nodes = collect_nodes(reader);
4837 // PI, ELEMENT root, COMMENT, TEXT, END_ELEMENT root
4838 // Note: PI appears as PROCESSING_INSTRUCTION node.
4839 assert!(!nodes.is_empty(), "no nodes collected: {:?}", nodes);
4840
4841 // Check PI.
4842 assert_eq!(
4843 nodes[0].0,
4844 ReaderNodeType::PROCESSING_INSTRUCTION,
4845 "expected PI at nodes[0], got {:?} name={}",
4846 nodes[0].0,
4847 nodes[0].1
4848 );
4849 assert_eq!(
4850 nodes[0].0,
4851 ReaderNodeType::PROCESSING_INSTRUCTION,
4852 "expected PI at nodes[0], got {:?} name={}",
4853 nodes[0].0,
4854 nodes[0].1
4855 );
4856
4857 // Check root element.
4858 let root_idx = nodes
4859 .iter()
4860 .position(|(t, n, _)| *t == ReaderNodeType::ELEMENT && n == "root");
4861 assert!(
4862 root_idx.is_some(),
4863 "no ELEMENT root found in nodes: {:?}",
4864 nodes
4865 .iter()
4866 .map(|(t, n, _)| format!("{:?}:{}", t, n))
4867 .collect::<Vec<_>>()
4868 );
4869
4870 // Check comment.
4871 let comment_idx = nodes
4872 .iter()
4873 .position(|(t, _, _)| *t == ReaderNodeType::COMMENT);
4874 assert!(comment_idx.is_some(), "no COMMENT found");
4875
4876 // Check text.
4877 let text_idx = nodes
4878 .iter()
4879 .position(|(t, _, _)| *t == ReaderNodeType::TEXT);
4880 assert!(text_idx.is_some(), "no TEXT found");
4881
4882 free_reader(reader);
4883 }
4884 }
4885
4886 /// Verifies `xmlTextReaderLocalName` returns the local name.
4887 ///
4888 /// # Safety
4889 ///
4890 /// - The `reader` pointer is asserted non-NULL before use and freed
4891 /// exactly once with `free_reader`.
4892 /// - `xmlTextReaderLocalName` returns a heap string that is freed exactly
4893 /// once with `xmlFreeImpl`.
4894 #[test]
4895 fn test_local_name() {
4896 unsafe {
4897 // We need a namespace-aware element. For now, test without namespace.
4898 let reader = create_reader("<root/>");
4899 assert!(!reader.is_null());
4900
4901 assert_eq!(xmlTextReaderRead(reader), 1);
4902 let local = xmlTextReaderLocalName(reader);
4903 assert!(!local.is_null());
4904 assert_eq!(xmlstr_to_bytes(local), b"root");
4905 xmlFreeImpl(local as *mut c_void);
4906
4907 free_reader(reader);
4908 }
4909 }
4910
4911 /// Verifies the base URI is NULL for memory-created readers.
4912 ///
4913 /// # Safety
4914 ///
4915 /// - The reader is created from a static string literal that stays alive
4916 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4917 /// before it is dereferenced and is freed exactly once with
4918 /// `free_reader`.
4919 #[test]
4920 fn test_base_uri() {
4921 unsafe {
4922 let reader = create_reader("<root/>");
4923 assert!(!reader.is_null());
4924
4925 assert_eq!(xmlTextReaderRead(reader), 1);
4926 // Base URI should be NULL for memory-created readers.
4927 let uri = xmlTextReaderBaseUri(reader);
4928 assert!(uri.is_null());
4929
4930 free_reader(reader);
4931 }
4932 }
4933
4934 /// Verifies namespace prefix lookup on the reader.
4935 ///
4936 /// # Safety
4937 ///
4938 /// - The `reader` pointer is asserted non-NULL before use and freed
4939 /// exactly once with `free_reader`.
4940 /// - `xmlTextReaderLookupNamespace` returns a heap string; the non-NULL
4941 /// result is freed exactly once with `xmlFreeImpl`.
4942 #[test]
4943 fn test_lookup_namespace() {
4944 unsafe {
4945 let reader = create_reader(r#"<root xmlns:ns="http://example.com"><ns:child/></root>"#);
4946 assert!(!reader.is_null());
4947
4948 // Read to root.
4949 assert_eq!(xmlTextReaderRead(reader), 1);
4950 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
4951
4952 // Read to child (ns:child).
4953 assert_eq!(xmlTextReaderRead(reader), 1);
4954
4955 // Lookup the "ns" prefix.
4956 let uri = xmlTextReaderLookupNamespace(reader, b"ns\0" as *const u8 as *const xmlChar);
4957 assert!(!uri.is_null());
4958 assert_eq!(xmlstr_to_bytes(uri), b"http://example.com");
4959 xmlFreeImpl(uri as *mut c_void);
4960
4961 // Lookup default namespace (NULL prefix).
4962 let uri = xmlTextReaderLookupNamespace(reader, ptr::null());
4963 assert!(uri.is_null());
4964
4965 // Lookup non-existent prefix.
4966 let uri = xmlTextReaderLookupNamespace(
4967 reader,
4968 b"nonexistent\0" as *const u8 as *const xmlChar,
4969 );
4970 assert!(uri.is_null());
4971
4972 free_reader(reader);
4973 }
4974 }
4975
4976 /// Verifies parser property get/set round trips and error codes.
4977 ///
4978 /// # Safety
4979 ///
4980 /// - The reader is created from a static string literal that stays alive
4981 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
4982 /// before it is dereferenced and is freed exactly once with
4983 /// `free_reader`.
4984 #[test]
4985 fn test_parser_properties() {
4986 unsafe {
4987 let reader = create_reader("<root/>");
4988 assert!(!reader.is_null());
4989
4990 // Get default properties.
4991 assert_eq!(xmlTextReaderGetParserProp(reader, 1), 0); // LOADDTD
4992 assert_eq!(xmlTextReaderGetParserProp(reader, 2), 0); // DEFAULTATTRS
4993 assert_eq!(xmlTextReaderGetParserProp(reader, 3), 0); // VALIDATE
4994 assert_eq!(xmlTextReaderGetParserProp(reader, 4), 0); // SUBST_ENTITIES
4995
4996 // Set and verify.
4997 assert_eq!(xmlTextReaderSetParserProp(reader, 1, 1), 0);
4998 assert_eq!(xmlTextReaderGetParserProp(reader, 1), 1);
4999
5000 assert_eq!(xmlTextReaderSetParserProp(reader, 4, 1), 0);
5001 assert_eq!(xmlTextReaderGetParserProp(reader, 4), 1);
5002
5003 // Invalid property.
5004 assert_eq!(xmlTextReaderGetParserProp(reader, 99), -1);
5005 assert_eq!(xmlTextReaderSetParserProp(reader, 99, 1), -1);
5006
5007 free_reader(reader);
5008 }
5009 }
5010
5011 /// Verifies the current document is available after the first read.
5012 ///
5013 /// # Safety
5014 ///
5015 /// - The reader is created from a static string literal that stays alive
5016 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5017 /// before it is dereferenced and is freed exactly once with
5018 /// `free_reader`.
5019 #[test]
5020 fn test_current_doc() {
5021 unsafe {
5022 let reader = create_reader("<root/>");
5023 assert!(!reader.is_null());
5024
5025 // Before reading, doc should be null.
5026 assert!((*reader).CurrentDoc().is_null());
5027
5028 // After reading, doc should be available.
5029 assert_eq!(xmlTextReaderRead(reader), 1);
5030 let doc = xmlTextReaderCurrentDoc(reader);
5031 assert!(!doc.is_null());
5032
5033 free_reader(reader);
5034 }
5035 }
5036
5037 /// Verifies a reader can be freed after reading to EOF.
5038 ///
5039 /// # Safety
5040 ///
5041 /// - The reader is created from a static string literal that stays alive
5042 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5043 /// before it is dereferenced and is freed exactly once with
5044 /// `free_reader`.
5045 #[test]
5046 fn test_free_reader_after_read() {
5047 unsafe {
5048 let reader = create_reader("<root><child/></root>");
5049 assert!(!reader.is_null());
5050
5051 // Read through the document.
5052 while xmlTextReaderRead(reader) > 0 {}
5053 assert_eq!((*reader).ReadState(), ReadState::EOF);
5054
5055 // Free should not crash.
5056 free_reader(reader);
5057 }
5058 }
5059
5060 /// Verifies a NULL buffer with a non-zero size is rejected.
5061 ///
5062 /// # Safety
5063 ///
5064 /// - A NULL buffer with a non-zero size must be rejected by
5065 /// `xmlReaderForMemory` without reading any memory.
5066 #[test]
5067 fn test_reader_for_memory_null_buffer() {
5068 unsafe {
5069 let reader = xmlReaderForMemory(ptr::null(), 10, ptr::null(), ptr::null(), 0);
5070 assert!(reader.is_null());
5071 }
5072 }
5073
5074 /// Verifies a zero size makes `xmlReaderForMemory` return NULL.
5075 ///
5076 /// # Safety
5077 ///
5078 /// - The static buffer is not read: a size of 0 must make
5079 /// `xmlReaderForMemory` return a NULL reader.
5080 #[test]
5081 fn test_reader_for_memory_empty_size() {
5082 unsafe {
5083 let data = b"<root/>";
5084 let reader = xmlReaderForMemory(
5085 data.as_ptr() as *const c_char,
5086 0,
5087 ptr::null(),
5088 ptr::null(),
5089 0,
5090 );
5091 assert!(reader.is_null());
5092 }
5093 }
5094
5095 /// Verifies a nonexistent file yields a NULL reader.
5096 ///
5097 /// # Safety
5098 ///
5099 /// - The static NUL-terminated filename stays valid for the
5100 /// `xmlReaderForFile` call; a nonexistent file must yield a NULL reader
5101 /// without reading.
5102 #[test]
5103 fn test_reader_for_file_not_found() {
5104 unsafe {
5105 let filename = b"/nonexistent/file.xml\0" as *const u8 as *const c_char;
5106 let reader = xmlReaderForFile(filename, ptr::null(), 0);
5107 assert!(reader.is_null());
5108 }
5109 }
5110
5111 /// Verifies const name and value pointers borrowed from the reader.
5112 ///
5113 /// # Safety
5114 ///
5115 /// - The `reader` pointer is asserted non-NULL before use and freed
5116 /// exactly once with `free_reader`.
5117 /// - The pointers returned by `xmlTextReaderConstName` and
5118 /// `xmlTextReaderConstValue` are borrowed from the reader and must not
5119 /// be freed by the caller.
5120 #[test]
5121 fn test_const_name_and_value() {
5122 unsafe {
5123 let reader = create_reader("<root>text</root>");
5124 assert!(!reader.is_null());
5125
5126 // Root element.
5127 assert_eq!(xmlTextReaderRead(reader), 1);
5128 let cname = xmlTextReaderConstName(reader);
5129 assert!(!cname.is_null());
5130 assert_eq!(xmlstr_to_bytes(cname), b"root");
5131
5132 // Text node.
5133 assert_eq!(xmlTextReaderRead(reader), 1);
5134 let cval = xmlTextReaderConstValue(reader);
5135 assert!(!cval.is_null());
5136 assert_eq!(xmlstr_to_bytes(cval), b"text");
5137
5138 free_reader(reader);
5139 }
5140 }
5141
5142 /// Reads a complex nested document and counts node types.
5143 ///
5144 /// # Safety
5145 ///
5146 /// - The reader is created from a static string literal that stays alive
5147 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5148 /// before it is dereferenced and is freed exactly once with
5149 /// `free_reader`.
5150 #[test]
5151 fn test_complex_nested_document() {
5152 unsafe {
5153 let xml = r#"<?xml version="1.0"?>
5154<library>
5155 <book id="1">
5156 <title>XML Fundamentals</title>
5157 <author>John Doe</author>
5158 </book>
5159 <book id="2">
5160 <title>XSLT Recipes</title>
5161 <author>Jane Smith</author>
5162 </book>
5163</library>"#;
5164
5165 let reader = create_reader(xml);
5166 assert!(!reader.is_null());
5167
5168 let mut element_count = 0;
5169 let mut end_element_count = 0;
5170 let mut text_count = 0;
5171 let mut pi_count = 0;
5172
5173 loop {
5174 let ret = xmlTextReaderRead(reader);
5175 if ret <= 0 {
5176 break;
5177 }
5178 match (*reader).NodeType() {
5179 ReaderNodeType::ELEMENT => element_count += 1,
5180 ReaderNodeType::END_ELEMENT => end_element_count += 1,
5181 ReaderNodeType::TEXT => text_count += 1,
5182 ReaderNodeType::PROCESSING_INSTRUCTION => pi_count += 1,
5183 _ => {}
5184 }
5185 }
5186
5187 // Elements: library, book(2), title(2), author(2) = 7
5188 assert_eq!(element_count, 7);
5189 // End elements: same count as elements
5190 assert_eq!(end_element_count, 7);
5191 // Text nodes: one per title and author = 4
5192 assert_eq!(text_count, 4);
5193 // UPSTREAM-PARITY: XML declaration (<?xml ...?>) is NOT stored as
5194 // a PI node in the tree. It is consumed by the parser and stored
5195 // in the document's version/encoding fields. Only <?pi ...?> nodes
5196 // (processing instructions) appear as XML_PI_NODE in the tree.
5197 assert_eq!(pi_count, 0);
5198
5199 free_reader(reader);
5200 }
5201 }
5202
5203 /// Verifies `xmlTextReaderSetup` reinitializes a used reader.
5204 ///
5205 /// # Safety
5206 ///
5207 /// - The `reader` pointer is asserted non-NULL before use and freed
5208 /// exactly once with `free_reader`.
5209 /// - `xmlTextReaderSetup` with a NULL input must reset the reader without
5210 /// dereferencing the NULL input.
5211 #[test]
5212 fn test_setup_reinitialize() {
5213 unsafe {
5214 let reader = create_reader("<root/>");
5215 assert!(!reader.is_null());
5216
5217 // Read through.
5218 assert_eq!(xmlTextReaderRead(reader), 1);
5219 assert_eq!((*reader).ReadState(), ReadState::READING);
5220
5221 // Setup with new input (simulate re-initialization).
5222 // For this test, we just verify the setup function exists and
5223 // handles a NULL input gracefully (resetting the reader).
5224 assert_eq!(
5225 xmlTextReaderSetup(reader, ptr::null_mut(), ptr::null(), ptr::null(), 0),
5226 0
5227 );
5228 assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
5229
5230 free_reader(reader);
5231 }
5232 }
5233
5234 /// Verifies attribute queries on non-element and attribute-less nodes.
5235 ///
5236 /// # Safety
5237 ///
5238 /// - The reader is created from a static string literal that stays alive
5239 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5240 /// before it is dereferenced and is freed exactly once with
5241 /// `free_reader`.
5242 #[test]
5243 fn test_has_attributes_on_non_element() {
5244 unsafe {
5245 let reader = create_reader("<root>text</root>");
5246 assert!(!reader.is_null());
5247
5248 // Position on text node.
5249 assert_eq!(xmlTextReaderRead(reader), 1); // root element
5250 assert_eq!((*reader).HasAttributes(), 0); // 0 attributes on root
5251 assert_eq!(xmlTextReaderRead(reader), 1); // text
5252 assert_eq!((*reader).HasAttributes(), 0);
5253
5254 free_reader(reader);
5255 }
5256 }
5257
5258 /// Verifies `xmlTextReaderPrev` fails after EOF.
5259 ///
5260 /// # Safety
5261 ///
5262 /// - The reader is created from a static string literal that stays alive
5263 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5264 /// before it is dereferenced and is freed exactly once with
5265 /// `free_reader`.
5266 #[test]
5267 fn test_prev_sibling() {
5268 unsafe {
5269 let reader = create_reader("<root><a/><b/><c/></root>");
5270 assert!(!reader.is_null());
5271
5272 // Read through the document.
5273 while xmlTextReaderRead(reader) > 0 {
5274 // Skip to END_ELEMENT root or beyond.
5275 }
5276
5277 // Can't go prev after EOF.
5278 assert_eq!(xmlTextReaderPrev(reader), -1);
5279
5280 free_reader(reader);
5281 }
5282 }
5283
5284 /// Verifies moving to an attribute index fails on an element without
5285 /// attributes.
5286 ///
5287 /// # Safety
5288 ///
5289 /// - The reader is created from a static string literal that stays alive
5290 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5291 /// before it is dereferenced and is freed exactly once with
5292 /// `free_reader`.
5293 #[test]
5294 fn test_move_to_attribute_no_not_on_element() {
5295 unsafe {
5296 let reader = create_reader("<root>text</root>");
5297 assert!(!reader.is_null());
5298
5299 assert_eq!(xmlTextReaderRead(reader), 1); // root
5300 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
5301
5302 // Move to non-existent attribute index.
5303 assert_eq!(xmlTextReaderMoveToAttributeNo(reader, 0), 0);
5304
5305 free_reader(reader);
5306 }
5307 }
5308
5309 /// Verifies namespaced attribute lookup with a NULL namespace URI.
5310 ///
5311 /// # Safety
5312 ///
5313 /// - The `reader` pointer is asserted non-NULL before use and freed
5314 /// exactly once with `free_reader`.
5315 /// - `xmlTextReaderGetAttributeNs` returns a heap string; the non-NULL
5316 /// result is freed exactly once with `xmlFreeImpl`.
5317 #[test]
5318 fn test_get_attribute_ns() {
5319 unsafe {
5320 let reader = create_reader(r#"<root a="1" b="2"/>"#);
5321 assert!(!reader.is_null());
5322
5323 assert_eq!(xmlTextReaderRead(reader), 1);
5324
5325 // Get attribute by local name only (namespaceURI is NULL).
5326 let val = xmlTextReaderGetAttributeNs(
5327 reader,
5328 b"a\0" as *const u8 as *const xmlChar,
5329 ptr::null(),
5330 );
5331 assert!(!val.is_null());
5332 assert_eq!(xmlstr_to_bytes(val), b"1");
5333 xmlFreeImpl(val as *mut c_void);
5334
5335 free_reader(reader);
5336 }
5337 }
5338
5339 /// Verifies mixed content (text and elements) event ordering.
5340 ///
5341 /// # Safety
5342 ///
5343 /// - The reader is created from a static string literal that stays alive
5344 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5345 /// before it is dereferenced and is freed exactly once with
5346 /// `free_reader`.
5347 #[test]
5348 fn test_mixed_content() {
5349 unsafe {
5350 let reader = create_reader("<root>before<child/>after</root>");
5351 assert!(!reader.is_null());
5352
5353 let nodes = collect_nodes(reader);
5354 // UPSTREAM-PARITY (oracle-verified 2.15.3): empty elements emit no
5355 // END_ELEMENT event.
5356 // ELEMENT root(0), TEXT "before"(1), ELEMENT child(1),
5357 // TEXT "after"(1), END_ELEMENT root(0)
5358 assert_eq!(nodes.len(), 5);
5359 assert_eq!(nodes[0], (ReaderNodeType::ELEMENT, "root".to_string(), 0));
5360 assert_eq!(nodes[1].0, ReaderNodeType::TEXT);
5361 assert_eq!(nodes[2], (ReaderNodeType::ELEMENT, "child".to_string(), 1));
5362 assert_eq!(nodes[3].0, ReaderNodeType::TEXT);
5363
5364 free_reader(reader);
5365 }
5366 }
5367
5368 /// Verifies malformed XML makes reading fail gracefully.
5369 ///
5370 /// # Safety
5371 ///
5372 /// - The static `data` buffer is valid for the 7-byte `xmlReaderForMemory`
5373 /// read; the returned `reader` is asserted non-NULL before being read
5374 /// and is freed exactly once with `free_reader`.
5375 #[test]
5376 fn test_error_handling_invalid_xml() {
5377 unsafe {
5378 // Malformed XML.
5379 let data = b"<root><\0" as *const u8 as *const c_char;
5380 let reader = xmlReaderForMemory(data, 7, ptr::null(), ptr::null(), 0);
5381 assert!(!reader.is_null());
5382
5383 // Reading should fail.
5384 let ret = xmlTextReaderRead(reader);
5385 assert!(ret == -1 || ret == 0);
5386
5387 free_reader(reader);
5388 }
5389 }
5390
5391 /// Verifies parser options are stored and honored by the reader.
5392 ///
5393 /// # Safety
5394 ///
5395 /// - The static NUL-terminated `data` buffer stays valid for the
5396 /// `xmlReaderForMemory` call; the returned `reader` is asserted non-NULL
5397 /// before dereferencing its `options` field and before
5398 /// `xmlTextReaderRead`, and is freed exactly once with `free_reader`.
5399 #[test]
5400 fn test_reader_with_options() {
5401 unsafe {
5402 let data = b"<root/>\0" as *const u8 as *const c_char;
5403 let reader = xmlReaderForMemory(
5404 data,
5405 7,
5406 ptr::null(),
5407 ptr::null(),
5408 XML_PARSE_NOENT | XML_PARSE_DTDLOAD,
5409 );
5410 assert!(!reader.is_null());
5411
5412 // Verify options were set.
5413 assert_eq!((*reader).options & XML_PARSE_NOENT, XML_PARSE_NOENT);
5414 assert_eq!((*reader).options & XML_PARSE_DTDLOAD, XML_PARSE_DTDLOAD);
5415
5416 assert_eq!(xmlTextReaderRead(reader), 1);
5417 free_reader(reader);
5418 }
5419 }
5420
5421 /// Verifies reading a document from an open file descriptor.
5422 ///
5423 /// # Safety
5424 ///
5425 /// - The descriptor returned by `libc::open` must be a valid open
5426 /// descriptor passed to `xmlReaderForFd`, which reads from it; the
5427 /// returned `reader` is asserted non-NULL before use, freed with
5428 /// `free_reader`, and the descriptor is closed afterwards.
5429 #[test]
5430 fn test_reader_for_fd() {
5431 unsafe {
5432 // Create a temp file and test xmlReaderForFd.
5433 let tmp_path = "/tmp/libxml_rs_test_reader_fd.xml";
5434 let tmp_cstr = std::ffi::CString::new(tmp_path).unwrap();
5435 let content = b"<root><data/></root>";
5436 let fd = libc::open(
5437 tmp_cstr.as_ptr(),
5438 libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC,
5439 0o644,
5440 );
5441 assert!(fd >= 0);
5442 libc::write(fd, content.as_ptr() as *const c_void, content.len());
5443 libc::close(fd);
5444
5445 // Open for reading.
5446 let fd = libc::open(tmp_cstr.as_ptr(), libc::O_RDONLY, 0);
5447 assert!(fd >= 0);
5448
5449 let reader = xmlReaderForFd(fd, ptr::null(), ptr::null(), 0);
5450 assert!(!reader.is_null());
5451
5452 let nodes = collect_nodes(reader);
5453 // UPSTREAM-PARITY (oracle-verified 2.15.3): `<data/>` is empty, so
5454 // it contributes no END_ELEMENT: root, data, END root.
5455 assert_eq!(nodes.len(), 3);
5456
5457 free_reader(reader);
5458 libc::close(fd);
5459 std::fs::remove_file(tmp_path).ok();
5460 }
5461 }
5462
5463 #[test]
5464 fn test_reader_for_io() {
5465 unsafe {
5466 extern "C" fn io_read(context: *mut c_void, buffer: *mut c_char, len: c_int) -> c_int {
5467 if context.is_null() || buffer.is_null() || len <= 0 {
5468 return -1;
5469 }
5470 // SAFETY: context points to an IoCtx struct.
5471 let ctx = unsafe { &mut *(context as *mut IoCtx) };
5472 if ctx.pos >= ctx.data.len() {
5473 return 0;
5474 }
5475 let remaining = ctx.data.len() - ctx.pos;
5476 let to_copy = if (remaining as c_int) < len {
5477 remaining
5478 } else {
5479 len as usize
5480 };
5481 // SAFETY: buffer has at least `len` bytes of space.
5482 unsafe {
5483 std::ptr::copy_nonoverlapping(
5484 ctx.data.as_ptr().add(ctx.pos),
5485 buffer as *mut u8,
5486 to_copy,
5487 );
5488 }
5489 ctx.pos += to_copy;
5490 to_copy as c_int
5491 }
5492
5493 extern "C" fn io_close(_context: *mut c_void) -> c_int {
5494 0
5495 }
5496
5497 struct IoCtx {
5498 data: &'static [u8],
5499 pos: usize,
5500 }
5501 let mut ctx = IoCtx {
5502 data: b"<root/>",
5503 pos: 0,
5504 };
5505
5506 let reader = xmlReaderForIO(
5507 Some(io_read),
5508 Some(io_close),
5509 &mut ctx as *mut IoCtx as *mut c_void,
5510 ptr::null(),
5511 ptr::null(),
5512 0,
5513 );
5514 assert!(!reader.is_null());
5515
5516 // Read through the document.
5517 assert_eq!(xmlTextReaderRead(reader), 1);
5518 assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
5519 let cname = xmlTextReaderConstName(reader);
5520 assert!(!cname.is_null());
5521 assert_eq!(xmlstr_to_bytes(cname), b"root");
5522
5523 // UPSTREAM-PARITY (oracle-verified 2.15.3): `<root/>` is empty, so
5524 // there is no END_ELEMENT — the second Read returns EOF.
5525 assert_eq!(xmlTextReaderRead(reader), 0);
5526
5527 free_reader(reader);
5528 }
5529 }
5530
5531 /// xmlTextReaderPreservePattern: after a full traversal, the document
5532 /// contains only the pattern-matched nodes and their element ancestors
5533 /// (upstream NODE_IS_PRESERVED streaming prune; reader3.c — Phase-12
5534 /// EXTERNAL-CONSUMERS court).
5535 ///
5536 /// # Safety
5537 ///
5538 /// - The reader is created from a static string literal that stays alive
5539 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5540 /// and freed exactly once with `free_reader`; the returned doc is freed
5541 /// with `tree::free_doc` exactly once.
5542 #[test]
5543 fn test_preserve_pattern_prunes() {
5544 unsafe {
5545 let reader = create_reader("<doc><parent><drop/><keep/><drop/></parent></doc>");
5546 assert!(!reader.is_null());
5547 // register the pattern BEFORE the first Read, like reader3.c
5548 let pat = b"keep\0";
5549 assert!(
5550 xmlTextReaderPreservePattern(
5551 reader,
5552 pat.as_ptr() as *const xmlChar,
5553 ptr::null_mut(),
5554 ) >= 0
5555 );
5556 let mut ret = xmlTextReaderRead(reader);
5557 while ret == 1 {
5558 ret = xmlTextReaderRead(reader);
5559 }
5560 let doc = xmlTextReaderCurrentDoc(reader);
5561 assert!(!doc.is_null());
5562 free_reader(reader);
5563
5564 // the pruned doc keeps doc -> parent -> keep; drop nodes gone
5565 let root = tree::doc_get_root_element(doc);
5566 assert!(!root.is_null());
5567 assert_eq!(xmlstr_to_bytes((*root).name), b"doc");
5568 let mut children: Vec<*mut _xmlNode> = Vec::new();
5569 let mut c = (*root).children;
5570 while !c.is_null() {
5571 children.push(c);
5572 c = (*c).next;
5573 }
5574 // the doc's only child is the preserved ancestor <parent>
5575 assert_eq!(children.len(), 1);
5576 assert_eq!(xmlstr_to_bytes((*children[0]).name), b"parent");
5577 // <parent> keeps only the matched <keep/>; both <drop/> are gone
5578 let mut keep: Vec<*mut _xmlNode> = Vec::new();
5579 let mut c = (*children[0]).children;
5580 while !c.is_null() {
5581 keep.push(c);
5582 c = (*c).next;
5583 }
5584 assert_eq!(keep.len(), 1);
5585 assert_eq!(xmlstr_to_bytes((*keep[0]).name), b"keep");
5586 tree::free_doc(doc);
5587 }
5588 }
5589
5590 /// XML_PARSE_DTDVALID on a no-DTD document: the parser raises the
5591 /// no-DTD validity error and clears ctxt->valid (parse2.c — Phase-12
5592 /// EXTERNAL-CONSUMERS court).
5593 ///
5594 /// # Safety
5595 ///
5596 /// - The reader is created from a static string literal that stays alive
5597 /// for the reader's lifetime; the `reader` pointer is asserted non-NULL
5598 /// and freed exactly once with `free_reader`.
5599 #[test]
5600 fn test_reader_dtdvalid_no_dtd() {
5601 unsafe {
5602 let bytes = b"<doc/>";
5603 let ctxt = create_parser_ctxt();
5604 assert!(!ctxt.is_null());
5605 let input = input_from_memory(bytes.as_ptr() as *const c_char, bytes.len() as c_int);
5606 setup_parser_input(ctxt, input);
5607 crate::abi::exports_parser::apply_options(ctxt, XML_PARSE_DTDVALID);
5608 assert_eq!(parse_document(ctxt), 0);
5609 assert_eq!(
5610 (*ctxt).valid,
5611 0,
5612 "no-DTD validating parse must clear ctxt->valid"
5613 );
5614 assert_eq!((*ctxt).errNo, XML_DTD_NO_DTD);
5615 free_parser_ctxt(ctxt);
5616 }
5617 }
5618}
5619
5620// ═══════════════════════════════════════════════════════════════════════════════
5621// 11.1-I reader closure — remaining xmlTextReader API (R-000136)
5622// ═══════════════════════════════════════════════════════════════════════════════
5623
5624/// Error severity (upstream `xmlParserSeverities`, reader.h).
5625pub const XML_PARSER_SEVERITY_VALIDITY_WARNING: c_int = 1;
5626pub const XML_PARSER_SEVERITY_VALIDITY_ERROR: c_int = 2;
5627pub const XML_PARSER_SEVERITY_WARNING: c_int = 3;
5628pub const XML_PARSER_SEVERITY_ERROR: c_int = 4;
5629
5630/// Opaque locator passed to the reader error handler (upstream
5631/// `xmlTextReaderLocator`).
5632#[derive(Debug)]
5633#[repr(C)]
5634pub struct XmlTextReaderLocator {
5635 pub reader: *mut XmlTextReader,
5636}
5637
5638/// Reader error callback (upstream `xmlTextReaderErrorFunc`).
5639pub type xmlTextReaderErrorFunc = unsafe extern "C" fn(
5640 arg: *mut c_void,
5641 msg: *const c_char,
5642 severity: c_int,
5643 locator: *mut XmlTextReaderLocator,
5644);
5645
5646/// `xmlTextReaderPtr xmlReaderForDoc(const xmlChar *cur, const char *URL,
5647/// const char *encoding, int options)` — reader over an in-memory XML string.
5648///
5649/// # SAFETY
5650///
5651/// - `cur` must be a valid NUL-terminated XML document string.
5652#[no_mangle]
5653pub unsafe extern "C" fn xmlReaderForDoc(
5654 cur: *const xmlChar,
5655 URL: *const c_char,
5656 encoding: *const c_char,
5657 options: c_int,
5658) -> *mut XmlTextReader {
5659 if cur.is_null() {
5660 return ptr::null_mut();
5661 }
5662 let len = unsafe { libc::strlen(cur as *const libc::c_char) } as c_int;
5663 unsafe { xmlReaderForMemory(cur as *const c_char, len, URL, encoding, options) }
5664}
5665
5666/// `xmlTextReaderPtr xmlNewTextReaderFilename(const char *URI)` — upstream
5667/// xmlreader.c: creates a reader over the file, no encoding/options
5668/// (R-000176: the candidate previously exported a 3-argument extension).
5669///
5670/// # SAFETY
5671///
5672/// - `URI` must point to a valid NUL-terminated string or NULL.
5673#[no_mangle]
5674pub unsafe extern "C" fn xmlNewTextReaderFilename(URI: *const c_char) -> *mut XmlTextReader {
5675 unsafe { xmlReaderForFile(URI, ptr::null(), 0) }
5676}
5677
5678/// Rebuild a reader in place (upstream `xmlReaderNew*` reuse contract).
5679///
5680/// Upstream reuses the caller's existing reader allocation, so a caller's
5681/// pointer remains valid across `xmlReaderNew*`. The candidate mirrors that by
5682/// moving the freshly built reader's contents into the caller's allocation and
5683/// releasing the temporary allocation without dropping the moved contents.
5684///
5685/// # SAFETY
5686///
5687/// - `reader` must be a valid, non-NULL reader pointer.
5688/// - `new_reader` must be a valid, non-NULL reader pointer distinct from `reader`.
5689unsafe fn reader_renew(reader: *mut XmlTextReader, new_reader: *mut XmlTextReader) {
5690 debug_assert!(!reader.is_null() && !new_reader.is_null() && reader != new_reader);
5691 unsafe {
5692 // Drop the old contents, then bitwise-move the new reader into the
5693 // caller's allocation. The temporary allocation is deallocated without
5694 // dropping (its contents now live at `reader`).
5695 core::ptr::drop_in_place(reader);
5696 core::ptr::copy_nonoverlapping(new_reader, reader, 1);
5697 let layout = std::alloc::Layout::new::<XmlTextReader>();
5698 std::alloc::dealloc(new_reader as *mut u8, layout);
5699 }
5700}
5701
5702/// `int xmlReaderNewDoc(xmlTextReaderPtr reader, const xmlChar *cur, const char *URL, const char *encoding, int options)`.
5703///
5704/// # SAFETY
5705///
5706/// - `reader` must be valid pointers (or NULL
5707/// where the upstream C contract allows), obtained from the
5708/// matching constructor/owner and not yet freed; the callee may
5709/// take or keep ownership exactly as the C API specifies.
5710///
5711/// - `cur`, `URL`, `encoding` must point to valid NUL-terminated
5712/// strings (or NULL where the C contract allows) for the lifetime
5713/// of the call.
5714///
5715/// The caller must not race this call with concurrent mutation of the
5716/// same objects from other threads (per-object state is not internally
5717/// synchronized). Violating any of the above is undefined behavior.
5718///
5719/// Exercised by the C-API differential courts
5720/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5721/// courts; those pass byte-for-byte against the upstream oracle.
5722#[no_mangle]
5723pub unsafe extern "C" fn xmlReaderNewDoc(
5724 reader: *mut XmlTextReader,
5725 cur: *const xmlChar,
5726 URL: *const c_char,
5727 encoding: *const c_char,
5728 options: c_int,
5729) -> c_int {
5730 // UPSTREAM-PARITY: the New* family rejects a NULL reader before any work
5731 // (xmlreader.c: `if (reader == NULL) return (-1);`). It never allocates.
5732 if reader.is_null() || cur.is_null() {
5733 return -1;
5734 }
5735 let r = unsafe { xmlReaderForDoc(cur, URL, encoding, options) };
5736 if r.is_null() {
5737 return -1;
5738 }
5739 unsafe { reader_renew(reader, r) };
5740 0
5741}
5742
5743/// `int xmlReaderNewFile(xmlTextReaderPtr reader, const char *filename, const char *encoding, int options)`.
5744///
5745/// # SAFETY
5746///
5747/// - `reader` must be valid pointers (or NULL
5748/// where the upstream C contract allows), obtained from the
5749/// matching constructor/owner and not yet freed; the callee may
5750/// take or keep ownership exactly as the C API specifies.
5751///
5752/// - `filename`, `encoding` must point to valid NUL-terminated
5753/// strings (or NULL where the C contract allows) for the lifetime
5754/// of the call.
5755///
5756/// The caller must not race this call with concurrent mutation of the
5757/// same objects from other threads (per-object state is not internally
5758/// synchronized). Violating any of the above is undefined behavior.
5759///
5760/// Exercised by the C-API differential courts
5761/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5762/// courts; those pass byte-for-byte against the upstream oracle.
5763#[no_mangle]
5764pub unsafe extern "C" fn xmlReaderNewFile(
5765 reader: *mut XmlTextReader,
5766 filename: *const c_char,
5767 encoding: *const c_char,
5768 options: c_int,
5769) -> c_int {
5770 if reader.is_null() {
5771 return -1;
5772 }
5773 let r = unsafe { xmlReaderForFile(filename, encoding, options) };
5774 if r.is_null() {
5775 return -1;
5776 }
5777 unsafe { reader_renew(reader, r) };
5778 0
5779}
5780
5781/// `int xmlReaderNewMemory(xmlTextReaderPtr reader, const char *buffer, int size, const char *URL, const char *encoding, int options)`.
5782///
5783/// # SAFETY
5784///
5785/// - `reader` must be valid pointers (or NULL
5786/// where the upstream C contract allows), obtained from the
5787/// matching constructor/owner and not yet freed; the callee may
5788/// take or keep ownership exactly as the C API specifies.
5789///
5790/// - `buffer`, `URL`, `encoding` must point to valid NUL-terminated
5791/// strings (or NULL where the C contract allows) for the lifetime
5792/// of the call.
5793///
5794/// The caller must not race this call with concurrent mutation of the
5795/// same objects from other threads (per-object state is not internally
5796/// synchronized). Violating any of the above is undefined behavior.
5797///
5798/// Exercised by the C-API differential courts
5799/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5800/// courts; those pass byte-for-byte against the upstream oracle.
5801#[no_mangle]
5802pub unsafe extern "C" fn xmlReaderNewMemory(
5803 reader: *mut XmlTextReader,
5804 buffer: *const c_char,
5805 size: c_int,
5806 URL: *const c_char,
5807 encoding: *const c_char,
5808 options: c_int,
5809) -> c_int {
5810 if reader.is_null() || buffer.is_null() {
5811 return -1;
5812 }
5813 let r = unsafe { xmlReaderForMemory(buffer, size, URL, encoding, options) };
5814 if r.is_null() {
5815 return -1;
5816 }
5817 unsafe { reader_renew(reader, r) };
5818 0
5819}
5820
5821/// `int xmlReaderNewFd(xmlTextReaderPtr reader, int fd, const char *URL, const char *encoding, int options)`.
5822///
5823/// # SAFETY
5824///
5825/// - `reader` must be valid pointers (or NULL
5826/// where the upstream C contract allows), obtained from the
5827/// matching constructor/owner and not yet freed; the callee may
5828/// take or keep ownership exactly as the C API specifies.
5829///
5830/// - `URL`, `encoding` must point to valid NUL-terminated
5831/// strings (or NULL where the C contract allows) for the lifetime
5832/// of the call.
5833///
5834/// The caller must not race this call with concurrent mutation of the
5835/// same objects from other threads (per-object state is not internally
5836/// synchronized). Violating any of the above is undefined behavior.
5837///
5838/// Exercised by the C-API differential courts
5839/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5840/// courts; those pass byte-for-byte against the upstream oracle.
5841#[no_mangle]
5842pub unsafe extern "C" fn xmlReaderNewFd(
5843 reader: *mut XmlTextReader,
5844 fd: c_int,
5845 URL: *const c_char,
5846 encoding: *const c_char,
5847 options: c_int,
5848) -> c_int {
5849 if reader.is_null() {
5850 return -1;
5851 }
5852 let r = unsafe { xmlReaderForFd(fd, URL, encoding, options) };
5853 if r.is_null() {
5854 return -1;
5855 }
5856 unsafe { reader_renew(reader, r) };
5857 0
5858}
5859
5860/// `int xmlReaderNewIO(xmlTextReaderPtr reader, xmlInputReadCallback ioread, xmlInputCloseCallback ioclose, void *ioctx, const char *URL, const char *encoding, int options)`.
5861///
5862/// # SAFETY
5863///
5864/// - `reader`, `ioctx` must be valid pointers (or NULL
5865/// where the upstream C contract allows), obtained from the
5866/// matching constructor/owner and not yet freed; the callee may
5867/// take or keep ownership exactly as the C API specifies.
5868///
5869/// - `URL`, `encoding` must point to valid NUL-terminated
5870/// strings (or NULL where the C contract allows) for the lifetime
5871/// of the call.
5872///
5873/// - `ioread`, `ioclose` must be a valid callback (or None);
5874/// the callback is invoked with the documented context pointer and
5875/// must itself uphold the same pointer invariants.
5876///
5877/// The caller must not race this call with concurrent mutation of the
5878/// same objects from other threads (per-object state is not internally
5879/// synchronized). Violating any of the above is undefined behavior.
5880///
5881/// Exercised by the C-API differential courts
5882/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5883/// courts; those pass byte-for-byte against the upstream oracle.
5884#[no_mangle]
5885pub unsafe extern "C" fn xmlReaderNewIO(
5886 reader: *mut XmlTextReader,
5887 ioread: Option<xmlInputReadCallback>,
5888 ioclose: Option<xmlInputCloseCallback>,
5889 ioctx: *mut c_void,
5890 URL: *const c_char,
5891 encoding: *const c_char,
5892 options: c_int,
5893) -> c_int {
5894 // UPSTREAM-PARITY: NULL reader or NULL read callback is rejected (-1).
5895 if reader.is_null() || ioread.is_none() {
5896 return -1;
5897 }
5898 let r = unsafe { xmlReaderForIO(ioread, ioclose, ioctx, URL, encoding, options) };
5899 if r.is_null() {
5900 return -1;
5901 }
5902 unsafe { reader_renew(reader, r) };
5903 0
5904}
5905
5906/// `xmlTextReaderPtr xmlReaderWalker(xmlDocPtr doc)` — reader walking an
5907/// existing document tree.
5908///
5909/// # SAFETY
5910///
5911/// - `doc` must be a valid document.
5912#[no_mangle]
5913pub unsafe extern "C" fn xmlReaderWalker(doc: *mut _xmlDoc) -> *mut XmlTextReader {
5914 if doc.is_null() {
5915 return ptr::null_mut();
5916 }
5917 let mut reader = XmlTextReader::new(ptr::null_mut(), None, None);
5918 reader.doc = doc;
5919 reader.parsed = true;
5920 reader.owns_doc = false; // walker borrows the caller's document
5921 reader.state = ReadState::READING;
5922 reader.build_events();
5923 Box::into_raw(Box::new(reader))
5924}
5925
5926/// `int xmlReaderNewWalker(xmlTextReaderPtr reader, xmlDocPtr doc)`.
5927///
5928/// # SAFETY
5929///
5930/// - `reader`, `doc` must be valid pointers (or NULL
5931/// where the upstream C contract allows), obtained from the
5932/// matching constructor/owner and not yet freed; the callee may
5933/// take or keep ownership exactly as the C API specifies.
5934///
5935/// The caller must not race this call with concurrent mutation of the
5936/// same objects from other threads (per-object state is not internally
5937/// synchronized). Violating any of the above is undefined behavior.
5938///
5939/// Exercised by the C-API differential courts
5940/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5941/// courts; those pass byte-for-byte against the upstream oracle.
5942#[no_mangle]
5943pub unsafe extern "C" fn xmlReaderNewWalker(
5944 reader: *mut XmlTextReader,
5945 doc: *mut _xmlDoc,
5946) -> c_int {
5947 // UPSTREAM-PARITY: NULL reader or NULL doc is rejected (-1).
5948 if reader.is_null() || doc.is_null() {
5949 return -1;
5950 }
5951 let r = unsafe { xmlReaderWalker(doc) };
5952 if r.is_null() {
5953 return -1;
5954 }
5955 unsafe { reader_renew(reader, r) };
5956 0
5957}
5958
5959/// `long xmlTextReaderByteConsumed(xmlTextReaderPtr reader)`.
5960///
5961/// Returns the total bytes consumed from the input (0 when unavailable —
5962/// the candidate parses the full input up front; documented divergence).
5963///
5964/// # SAFETY
5965///
5966/// - `reader` must be valid pointers (or NULL
5967/// where the upstream C contract allows), obtained from the
5968/// matching constructor/owner and not yet freed; the callee may
5969/// take or keep ownership exactly as the C API specifies.
5970///
5971/// The caller must not race this call with concurrent mutation of the
5972/// same objects from other threads (per-object state is not internally
5973/// synchronized). Violating any of the above is undefined behavior.
5974///
5975/// Exercised by the C-API differential courts
5976/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
5977/// courts; those pass byte-for-byte against the upstream oracle.
5978#[no_mangle]
5979pub const unsafe extern "C" fn xmlTextReaderByteConsumed(reader: *mut XmlTextReader) -> c_long {
5980 if reader.is_null() {
5981 return -1;
5982 }
5983 0
5984}
5985
5986/// `const xmlChar *xmlTextReaderConstBaseUri(xmlTextReaderPtr reader)` — the
5987/// base URI, valid until the reader is freed (no copy).
5988///
5989/// # SAFETY
5990///
5991/// - `reader` must be valid pointers (or NULL
5992/// where the upstream C contract allows), obtained from the
5993/// matching constructor/owner and not yet freed; the callee may
5994/// take or keep ownership exactly as the C API specifies.
5995///
5996/// The caller must not race this call with concurrent mutation of the
5997/// same objects from other threads (per-object state is not internally
5998/// synchronized). Violating any of the above is undefined behavior.
5999///
6000/// Exercised by the C-API differential courts
6001/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6002/// courts; those pass byte-for-byte against the upstream oracle.
6003#[no_mangle]
6004pub unsafe extern "C" fn xmlTextReaderConstBaseUri(reader: *mut XmlTextReader) -> *const xmlChar {
6005 if reader.is_null() {
6006 return ptr::null();
6007 }
6008 // UPSTREAM-PARITY (xmlreader.c xmlTextReaderConstBaseUri): the base URI
6009 // is exposed only once a node is current (upstream `reader->node == NULL`
6010 // returns NULL). Pre-first-Read and after EOF the PHP property must read
6011 // as the empty string, not the reader's setup URL.
6012 let r = unsafe { &*reader };
6013 if r.cur_node.is_null() {
6014 return ptr::null();
6015 }
6016 r.URL
6017}
6018
6019/// `const xmlChar *xmlTextReaderConstEncoding(xmlTextReaderPtr reader)`.
6020///
6021/// # SAFETY
6022///
6023/// - `reader` must be valid pointers (or NULL
6024/// where the upstream C contract allows), obtained from the
6025/// matching constructor/owner and not yet freed; the callee may
6026/// take or keep ownership exactly as the C API specifies.
6027///
6028/// The caller must not race this call with concurrent mutation of the
6029/// same objects from other threads (per-object state is not internally
6030/// synchronized). Violating any of the above is undefined behavior.
6031///
6032/// Exercised by the C-API differential courts
6033/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6034/// courts; those pass byte-for-byte against the upstream oracle.
6035#[no_mangle]
6036pub unsafe extern "C" fn xmlTextReaderConstEncoding(reader: *mut XmlTextReader) -> *const xmlChar {
6037 if reader.is_null() {
6038 return ptr::null();
6039 }
6040 let r = unsafe { &*reader };
6041 if !r.encoding.is_null() {
6042 return r.encoding;
6043 }
6044 if !r.doc.is_null() {
6045 return unsafe { (*r.doc).encoding };
6046 }
6047 ptr::null()
6048}
6049
6050/// `const xmlChar *xmlTextReaderConstLocalName(xmlTextReaderPtr reader)`.
6051///
6052/// UPSTREAM-PARITY: at an attribute position this is the attribute's local
6053/// name (or "xmlns"/the prefix for a namespace declaration); at an element
6054/// position the tree's local name.
6055///
6056/// # SAFETY
6057///
6058/// - `reader` must be valid pointers (or NULL
6059/// where the upstream C contract allows), obtained from the
6060/// matching constructor/owner and not yet freed; the callee may
6061/// take or keep ownership exactly as the C API specifies.
6062///
6063/// The caller must not race this call with concurrent mutation of the
6064/// same objects from other threads (per-object state is not internally
6065/// synchronized). Violating any of the above is undefined behavior.
6066///
6067/// Exercised by the C-API differential courts
6068/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6069/// courts; those pass byte-for-byte against the upstream oracle.
6070#[no_mangle]
6071pub unsafe extern "C" fn xmlTextReaderConstLocalName(reader: *mut XmlTextReader) -> *const xmlChar {
6072 if reader.is_null() {
6073 return ptr::null();
6074 }
6075 let r = unsafe { &*reader };
6076 if r.cur_node.is_null() {
6077 return ptr::null();
6078 }
6079 // Attribute position: the attribute's local name (upstream node->name for
6080 // XML_ATTRIBUTE_NODE; "xmlns"/prefix for a namespace declaration).
6081 if r.cur_attribute >= 0 {
6082 let target = unsafe { r.attr_at(r.cur_node, r.cur_attribute) };
6083 return match target {
6084 AttrTarget::Ns(ns) => {
6085 if ns.is_null() {
6086 ptr::null()
6087 } else if unsafe { (*ns).prefix }.is_null() {
6088 c"xmlns".as_ptr() as *const xmlChar
6089 } else {
6090 unsafe { (*ns).prefix }
6091 }
6092 }
6093 AttrTarget::Prop(p) => {
6094 if p.is_null() || unsafe { (*p).name }.is_null() {
6095 ptr::null()
6096 } else {
6097 unsafe { (*p).name }
6098 }
6099 }
6100 AttrTarget::None => ptr::null(),
6101 };
6102 }
6103 // Element position: the tree's local name (upstream node->name).
6104 let etype = unsafe { (*r.cur_node).type_ };
6105 if etype == XML_ELEMENT_NODE as c_int || etype == XML_ATTRIBUTE_NODE as c_int {
6106 unsafe { (*r.cur_node).name }
6107 } else {
6108 ptr::null()
6109 }
6110}
6111
6112/// `const xmlChar *xmlTextReaderConstNamespaceUri(xmlTextReaderPtr reader)`.
6113///
6114/// UPSTREAM-PARITY: at an attribute position the namespace comes from the
6115/// attribute (or namespace declaration) itself; elsewhere from the node.
6116///
6117/// # SAFETY
6118///
6119/// - `reader` must be valid pointers (or NULL
6120/// where the upstream C contract allows), obtained from the
6121/// matching constructor/owner and not yet freed; the callee may
6122/// take or keep ownership exactly as the C API specifies.
6123///
6124/// The caller must not race this call with concurrent mutation of the
6125/// same objects from other threads (per-object state is not internally
6126/// synchronized). Violating any of the above is undefined behavior.
6127///
6128/// Exercised by the C-API differential courts
6129/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6130/// courts; those pass byte-for-byte against the upstream oracle.
6131#[no_mangle]
6132pub unsafe extern "C" fn xmlTextReaderConstNamespaceUri(
6133 reader: *mut XmlTextReader,
6134) -> *const xmlChar {
6135 if reader.is_null() {
6136 return ptr::null();
6137 }
6138 let r = unsafe { &*reader };
6139 if r.cur_node.is_null() {
6140 return ptr::null();
6141 }
6142 // Attribute position: resolve the current attribute's namespace.
6143 if r.cur_attribute >= 0 {
6144 let target = unsafe { r.attr_at(r.cur_node, r.cur_attribute) };
6145 return match target {
6146 AttrTarget::Ns(_ns) => {
6147 // UPSTREAM-PARITY (xmlTextReaderConstNamespaceUri): a
6148 // namespace declaration reports the xmlns namespace URI,
6149 // not the declared URI.
6150 c"http://www.w3.org/2000/xmlns/".as_ptr() as *const xmlChar
6151 }
6152 AttrTarget::Prop(p) => {
6153 if p.is_null() || unsafe { (*p).ns }.is_null() {
6154 ptr::null()
6155 } else {
6156 unsafe { (*(*p).ns).href }
6157 }
6158 }
6159 AttrTarget::None => ptr::null(),
6160 };
6161 }
6162 let ns = unsafe { (*r.cur_node).ns };
6163 if ns.is_null() || unsafe { (*ns).href }.is_null() {
6164 ptr::null()
6165 } else {
6166 unsafe { (*ns).href }
6167 }
6168}
6169
6170/// `const xmlChar *xmlTextReaderConstPrefix(xmlTextReaderPtr reader)`.
6171///
6172/// UPSTREAM-PARITY: at an attribute position the prefix comes from the
6173/// attribute; for a namespace declaration the prefix is reported as "xmlns"
6174/// (and NULL for the default declaration) — an upstream quirk reproduced here.
6175///
6176/// # SAFETY
6177///
6178/// - `reader` must be valid pointers (or NULL
6179/// where the upstream C contract allows), obtained from the
6180/// matching constructor/owner and not yet freed; the callee may
6181/// take or keep ownership exactly as the C API specifies.
6182///
6183/// The caller must not race this call with concurrent mutation of the
6184/// same objects from other threads (per-object state is not internally
6185/// synchronized). Violating any of the above is undefined behavior.
6186///
6187/// Exercised by the C-API differential courts
6188/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6189/// courts; those pass byte-for-byte against the upstream oracle.
6190#[no_mangle]
6191pub unsafe extern "C" fn xmlTextReaderConstPrefix(reader: *mut XmlTextReader) -> *const xmlChar {
6192 if reader.is_null() {
6193 return ptr::null();
6194 }
6195 let r = unsafe { &*reader };
6196 if r.cur_node.is_null() {
6197 return ptr::null();
6198 }
6199 // Attribute position: resolve the current attribute's namespace.
6200 if r.cur_attribute >= 0 {
6201 let target = unsafe { r.attr_at(r.cur_node, r.cur_attribute) };
6202 return match target {
6203 AttrTarget::Ns(ns) => {
6204 if ns.is_null() || unsafe { (*ns).prefix }.is_null() {
6205 ptr::null()
6206 } else {
6207 c"xmlns".as_ptr() as *const xmlChar
6208 }
6209 }
6210 AttrTarget::Prop(p) => {
6211 if p.is_null() || unsafe { (*p).ns }.is_null() {
6212 ptr::null()
6213 } else {
6214 unsafe { (*(*p).ns).prefix }
6215 }
6216 }
6217 AttrTarget::None => ptr::null(),
6218 };
6219 }
6220 let ns = unsafe { (*r.cur_node).ns };
6221 if ns.is_null() || unsafe { (*ns).prefix }.is_null() {
6222 ptr::null()
6223 } else {
6224 unsafe { (*ns).prefix }
6225 }
6226}
6227
6228/// `const xmlChar *xmlTextReaderConstString(xmlTextReaderPtr reader, const xmlChar *str)`
6229/// — the reader's dictionary-internalized copy of `str`; the candidate
6230/// returns `str` unchanged (dictionary interning is an internal detail).
6231///
6232/// # SAFETY
6233///
6234/// - `_reader` must be valid pointers (or NULL
6235/// where the upstream C contract allows), obtained from the
6236/// matching constructor/owner and not yet freed; the callee may
6237/// take or keep ownership exactly as the C API specifies.
6238///
6239/// - `str` must point to valid NUL-terminated
6240/// strings (or NULL where the C contract allows) for the lifetime
6241/// of the call.
6242///
6243/// The caller must not race this call with concurrent mutation of the
6244/// same objects from other threads (per-object state is not internally
6245/// synchronized). Violating any of the above is undefined behavior.
6246///
6247/// Exercised by the C-API differential courts
6248/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6249/// courts; those pass byte-for-byte against the upstream oracle.
6250#[no_mangle]
6251pub const unsafe extern "C" fn xmlTextReaderConstString(
6252 _reader: *mut XmlTextReader,
6253 str: *const xmlChar,
6254) -> *const xmlChar {
6255 str
6256}
6257
6258/// `const xmlChar *xmlTextReaderConstXmlLang(xmlTextReaderPtr reader)`.
6259///
6260/// # SAFETY
6261///
6262/// - `reader` must be valid pointers (or NULL
6263/// where the upstream C contract allows), obtained from the
6264/// matching constructor/owner and not yet freed; the callee may
6265/// take or keep ownership exactly as the C API specifies.
6266///
6267/// The caller must not race this call with concurrent mutation of the
6268/// same objects from other threads (per-object state is not internally
6269/// synchronized). Violating any of the above is undefined behavior.
6270///
6271/// Exercised by the C-API differential courts
6272/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6273/// courts; those pass byte-for-byte against the upstream oracle.
6274#[no_mangle]
6275pub unsafe extern "C" fn xmlTextReaderConstXmlLang(reader: *mut XmlTextReader) -> *const xmlChar {
6276 if reader.is_null() {
6277 return ptr::null();
6278 }
6279 let r = unsafe { &*reader };
6280 let mut node = r.cur_node;
6281 while !node.is_null() {
6282 let mut prop = unsafe { (*node).properties };
6283 while !prop.is_null() {
6284 let p = unsafe { &*prop };
6285 if !p.name.is_null()
6286 && unsafe { *p.name } == b'x'
6287 && unsafe { *p.name.add(1) } == b'm'
6288 && unsafe { *p.name.add(2) } == b'l'
6289 && unsafe { *p.name.add(3) } == b':'
6290 && unsafe { *p.name.add(4) } == b'l'
6291 && unsafe { *p.name.add(5) } == b'a'
6292 && unsafe { *p.name.add(6) } == b'n'
6293 && unsafe { *p.name.add(7) } == b'g'
6294 && unsafe { *p.name.add(8) } == 0
6295 {
6296 if !p.children.is_null() {
6297 let txt = p.children;
6298 if unsafe { (*txt).type_ }
6299 == crate::abi::types::xmlElementType::XML_TEXT_NODE as c_int
6300 {
6301 return unsafe { (*txt).content };
6302 }
6303 }
6304 return ptr::null();
6305 }
6306 prop = p.next;
6307 }
6308 node = unsafe { (*node).parent };
6309 }
6310 ptr::null()
6311}
6312
6313/// `const xmlChar *xmlTextReaderConstXmlVersion(xmlTextReaderPtr reader)`.
6314///
6315/// # SAFETY
6316///
6317/// - `reader` must be valid pointers (or NULL
6318/// where the upstream C contract allows), obtained from the
6319/// matching constructor/owner and not yet freed; the callee may
6320/// take or keep ownership exactly as the C API specifies.
6321///
6322/// The caller must not race this call with concurrent mutation of the
6323/// same objects from other threads (per-object state is not internally
6324/// synchronized). Violating any of the above is undefined behavior.
6325///
6326/// Exercised by the C-API differential courts
6327/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6328/// courts; those pass byte-for-byte against the upstream oracle.
6329#[no_mangle]
6330pub unsafe extern "C" fn xmlTextReaderConstXmlVersion(
6331 reader: *mut XmlTextReader,
6332) -> *const xmlChar {
6333 if reader.is_null() {
6334 return ptr::null();
6335 }
6336 let r = unsafe { &*reader };
6337 if r.doc.is_null() {
6338 return ptr::null();
6339 }
6340 unsafe { (*r.doc).version }
6341}
6342
6343/// `int xmlTextReaderQuoteChar(xmlTextReaderPtr reader)`.
6344///
6345/// UPSTREAM-PARITY: libxml2 2.13/2.15 returns `'"'` unconditionally for any
6346/// non-NULL reader (the implementation is a placeholder that does not inspect
6347/// the attribute; see the `/* TODO maybe lookup the attribute value */` comment
6348/// in xmlreader.c). The candidate reproduces that historical behavior exactly.
6349///
6350/// # SAFETY
6351///
6352/// - `reader` must be valid pointers (or NULL
6353/// where the upstream C contract allows), obtained from the
6354/// matching constructor/owner and not yet freed; the callee may
6355/// take or keep ownership exactly as the C API specifies.
6356///
6357/// The caller must not race this call with concurrent mutation of the
6358/// same objects from other threads (per-object state is not internally
6359/// synchronized). Violating any of the above is undefined behavior.
6360///
6361/// Exercised by the C-API differential courts
6362/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6363/// courts; those pass byte-for-byte against the upstream oracle.
6364#[no_mangle]
6365pub const unsafe extern "C" fn xmlTextReaderQuoteChar(reader: *mut XmlTextReader) -> c_int {
6366 if reader.is_null() {
6367 return -1;
6368 }
6369 b'"' as c_int
6370}
6371
6372/// `int xmlTextReaderIsDefault(xmlTextReaderPtr reader)` — whether the current
6373/// attribute came from the DTD default. The candidate returns 0 for a valid
6374/// reader (DTD default attribute expansion is not annotated; documented
6375/// divergence), -1 for a NULL reader (upstream contract).
6376///
6377/// # SAFETY
6378///
6379/// - `reader` must be valid pointers (or NULL
6380/// where the upstream C contract allows), obtained from the
6381/// matching constructor/owner and not yet freed; the callee may
6382/// take or keep ownership exactly as the C API specifies.
6383///
6384/// The caller must not race this call with concurrent mutation of the
6385/// same objects from other threads (per-object state is not internally
6386/// synchronized). Violating any of the above is undefined behavior.
6387///
6388/// Exercised by the C-API differential courts
6389/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6390/// courts; those pass byte-for-byte against the upstream oracle.
6391#[no_mangle]
6392pub const unsafe extern "C" fn xmlTextReaderIsDefault(reader: *mut XmlTextReader) -> c_int {
6393 if reader.is_null() {
6394 return -1;
6395 }
6396 0
6397}
6398
6399/// `int xmlTextReaderIsNamespaceDecl(xmlTextReaderPtr reader)` — whether the
6400/// current attribute position is a namespace declaration.
6401///
6402/// # SAFETY
6403///
6404/// - `reader` must be valid pointers (or NULL
6405/// where the upstream C contract allows), obtained from the
6406/// matching constructor/owner and not yet freed; the callee may
6407/// take or keep ownership exactly as the C API specifies.
6408///
6409/// The caller must not race this call with concurrent mutation of the
6410/// same objects from other threads (per-object state is not internally
6411/// synchronized). Violating any of the above is undefined behavior.
6412///
6413/// Exercised by the C-API differential courts
6414/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6415/// courts; those pass byte-for-byte against the upstream oracle.
6416#[no_mangle]
6417pub unsafe extern "C" fn xmlTextReaderIsNamespaceDecl(reader: *mut XmlTextReader) -> c_int {
6418 if reader.is_null() {
6419 return -1;
6420 }
6421 let r = unsafe { &*reader };
6422 if r.cur_node.is_null() {
6423 return -1;
6424 }
6425 r.cur_attr_is_ns as c_int
6426}
6427
6428/// `int xmlTextReaderMoveToAttributeNs(xmlTextReaderPtr reader, const xmlChar *localName, const xmlChar *namespaceURI)`.
6429///
6430/// UPSTREAM-PARITY (xmlreader.c, 2.15): NULL reader/localName/namespaceURI
6431/// returns -1; a NULL `namespaceURI` is NOT treated as "no namespace" — the
6432/// caller must pass the actual URI. The `http://www.w3.org/2000/xmlns/`
6433/// namespace searches namespace declarations (matching the default `xmlns`
6434/// declaration or a prefix), everything else searches only namespace-qualified
6435/// properties (`prop->ns != NULL`).
6436///
6437/// # SAFETY
6438///
6439/// - `localName`/`namespaceURI` must be valid strings (non-NULL).
6440#[no_mangle]
6441pub unsafe extern "C" fn xmlTextReaderMoveToAttributeNs(
6442 reader: *mut XmlTextReader,
6443 localName: *const xmlChar,
6444 namespaceURI: *const xmlChar,
6445) -> c_int {
6446 if reader.is_null() || localName.is_null() || namespaceURI.is_null() {
6447 return -1;
6448 }
6449 let r = unsafe { &mut *reader };
6450 let node = r.cur_node;
6451 if node.is_null() {
6452 return -1;
6453 }
6454 if unsafe { (*node).type_ } != XML_ELEMENT_NODE as c_int {
6455 return 0;
6456 }
6457
6458 const XMLNS_URI: &[u8] = b"http://www.w3.org/2000/xmlns/\0";
6459 if libc::strcmp(
6460 namespaceURI as *const libc::c_char,
6461 XMLNS_URI.as_ptr() as *const libc::c_char,
6462 ) == 0
6463 {
6464 // Namespace-declaration search: localName "xmlns" addresses the
6465 // default declaration, any other localName is a prefix.
6466 let is_default = libc::strcmp(
6467 localName as *const libc::c_char,
6468 c"xmlns".as_ptr() as *const libc::c_char,
6469 ) == 0;
6470 let mut ns = unsafe { (*node).nsDef };
6471 let mut index = 0;
6472 while !ns.is_null() {
6473 let n = unsafe { &*ns };
6474 let prefix_match = if is_default {
6475 n.prefix.is_null()
6476 } else {
6477 !n.prefix.is_null()
6478 && libc::strcmp(
6479 n.prefix as *const libc::c_char,
6480 localName as *const libc::c_char,
6481 ) == 0
6482 };
6483 if prefix_match {
6484 r.cur_attribute = index;
6485 r.node_type = ReaderNodeType::ATTRIBUTE;
6486 r.cache_attribute_info(AttrTarget::Ns(ns));
6487 return 1;
6488 }
6489 index += 1;
6490 ns = unsafe { (*ns).next };
6491 }
6492 return 0;
6493 }
6494
6495 // Property search: only namespace-qualified attributes are matchable.
6496 let mut prop = unsafe { (*node).properties };
6497 let mut index = 0;
6498 let mut ns_count = 0;
6499 let mut ns = unsafe { (*node).nsDef };
6500 while !ns.is_null() {
6501 ns_count += 1;
6502 ns = unsafe { (*ns).next };
6503 }
6504 while !prop.is_null() {
6505 let p = unsafe { &*prop };
6506 if !p.name.is_null()
6507 && !p.ns.is_null()
6508 && !(*p.ns).href.is_null()
6509 && libc::strcmp(
6510 p.name as *const libc::c_char,
6511 localName as *const libc::c_char,
6512 ) == 0
6513 && libc::strcmp(
6514 (*p.ns).href as *const libc::c_char,
6515 namespaceURI as *const libc::c_char,
6516 ) == 0
6517 {
6518 r.cur_attribute = ns_count + index;
6519 r.node_type = ReaderNodeType::ATTRIBUTE;
6520 r.cache_attribute_info(AttrTarget::Prop(prop));
6521 return 1;
6522 }
6523 index += 1;
6524 prop = unsafe { (*prop).next };
6525 }
6526 0
6527}
6528
6529/// `xmlNodePtr xmlTextReaderPreserve(xmlTextReaderPtr reader)` — the current
6530/// node (the candidate's reader owns the whole tree, so no separate
6531/// preservation step is needed).
6532///
6533/// # SAFETY
6534///
6535/// - `reader` must be valid pointers (or NULL
6536/// where the upstream C contract allows), obtained from the
6537/// matching constructor/owner and not yet freed; the callee may
6538/// take or keep ownership exactly as the C API specifies.
6539///
6540/// The caller must not race this call with concurrent mutation of the
6541/// same objects from other threads (per-object state is not internally
6542/// synchronized). Violating any of the above is undefined behavior.
6543///
6544/// Exercised by the C-API differential courts
6545/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6546/// courts; those pass byte-for-byte against the upstream oracle.
6547#[no_mangle]
6548pub unsafe extern "C" fn xmlTextReaderPreserve(reader: *mut XmlTextReader) -> *mut _xmlNode {
6549 if reader.is_null() {
6550 return ptr::null_mut();
6551 }
6552 unsafe { (*reader).cur_node }
6553}
6554
6555/// `int xmlTextReaderPreservePattern(xmlTextReaderPtr reader, const xmlChar *pattern, const xmlChar **namespaces)`.
6556///
6557/// Compiles the XPath-subset pattern (upstream `xmlPatterncompile`) and
6558/// registers it like upstream `reader->patternTab`; after the parse,
6559/// matched nodes and their element ancestors are kept and every other node
6560/// is unlinked and freed (upstream's NODE_IS_PRESERVED streaming prune,
6561/// applied post-parse by the candidate's whole-tree reader). Returns the
6562/// index of the registered pattern, or -1 on error (upstream xmlreader.c
6563/// xmlTextReaderPreservePattern).
6564///
6565/// # SAFETY
6566///
6567/// - `reader` must be valid pointers (or NULL
6568/// where the upstream C contract allows), obtained from the
6569/// matching constructor/owner and not yet freed; the callee may
6570/// take or keep ownership exactly as the C API specifies.
6571///
6572/// - `pattern` must point to a valid NUL-terminated string;
6573/// `namespaces` must be NULL or a NULL-terminated array of
6574/// NUL-terminated strings, both live for the duration of the call.
6575///
6576/// The caller must not race this call with concurrent mutation of the
6577/// same objects from other threads (per-object state is not internally
6578/// synchronized). Violating any of the above is undefined behavior.
6579///
6580/// Exercised by the Phase-12 EXTERNAL-CONSUMERS court (reader3.c).
6581#[no_mangle]
6582pub unsafe extern "C" fn xmlTextReaderPreservePattern(
6583 reader: *mut XmlTextReader,
6584 pattern: *const xmlChar,
6585 namespaces: *mut *const xmlChar,
6586) -> c_int {
6587 if reader.is_null() || pattern.is_null() {
6588 return -1;
6589 }
6590 let comp = unsafe {
6591 crate::abi::exports_automata::xmlPatterncompile(
6592 pattern,
6593 ptr::null_mut(),
6594 0,
6595 namespaces as *const *const xmlChar,
6596 )
6597 };
6598 if comp.is_null() {
6599 return -1;
6600 }
6601 // SAFETY: reader is valid (checked); pattern_tab owns the compiled
6602 // pattern and frees it in Drop.
6603 let idx = unsafe { (*reader).pattern_tab.len() };
6604 unsafe { (*reader).pattern_tab.push(comp) };
6605 idx as c_int
6606}
6607
6608/// `int xmlTextReaderSetErrorHandler(xmlTextReaderPtr reader, xmlTextReaderErrorFunc f, void *arg)`.
6609///
6610/// # SAFETY
6611///
6612/// - `f` must be a valid callback or NULL.
6613#[no_mangle]
6614pub unsafe extern "C" fn xmlTextReaderSetErrorHandler(
6615 reader: *mut XmlTextReader,
6616 f: Option<xmlTextReaderErrorFunc>,
6617 arg: *mut c_void,
6618) {
6619 if reader.is_null() {
6620 return;
6621 }
6622 unsafe {
6623 (*reader).error_handler = f;
6624 (*reader).error_arg = arg;
6625 }
6626}
6627
6628/// `void xmlTextReaderGetErrorHandler(xmlTextReaderPtr reader, xmlTextReaderErrorFunc *f, void **arg)`.
6629///
6630/// # SAFETY
6631///
6632/// - `f`/`arg` must be valid out-pointers or NULL.
6633#[no_mangle]
6634pub unsafe extern "C" fn xmlTextReaderGetErrorHandler(
6635 reader: *mut XmlTextReader,
6636 f: *mut Option<xmlTextReaderErrorFunc>,
6637 arg: *mut *mut c_void,
6638) {
6639 if reader.is_null() {
6640 return;
6641 }
6642 unsafe {
6643 if !f.is_null() {
6644 *f = (*reader).error_handler;
6645 }
6646 if !arg.is_null() {
6647 *arg = (*reader).error_arg;
6648 }
6649 }
6650}
6651
6652/// `void xmlTextReaderSetStructuredErrorHandler(xmlTextReaderPtr reader, xmlStructuredErrorFunc f, void *arg)`.
6653///
6654/// # SAFETY
6655///
6656/// - `reader`, `arg` must be valid pointers (or NULL
6657/// where the upstream C contract allows), obtained from the
6658/// matching constructor/owner and not yet freed; the callee may
6659/// take or keep ownership exactly as the C API specifies.
6660///
6661/// - `f` must be a valid callback (or None);
6662/// the callback is invoked with the documented context pointer and
6663/// must itself uphold the same pointer invariants.
6664///
6665/// The caller must not race this call with concurrent mutation of the
6666/// same objects from other threads (per-object state is not internally
6667/// synchronized). Violating any of the above is undefined behavior.
6668///
6669/// Exercised by the C-API differential courts
6670/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6671/// courts; those pass byte-for-byte against the upstream oracle.
6672#[no_mangle]
6673pub unsafe extern "C" fn xmlTextReaderSetStructuredErrorHandler(
6674 reader: *mut XmlTextReader,
6675 f: Option<crate::abi::callbacks::xmlStructuredErrorFunc>,
6676 arg: *mut c_void,
6677) {
6678 if reader.is_null() {
6679 return;
6680 }
6681 unsafe {
6682 (*reader).structured_handler = f;
6683 (*reader).structured_arg = arg;
6684 }
6685}
6686
6687/// `void xmlTextReaderSetResourceLoader(xmlTextReaderPtr reader,
6688/// xmlResourceLoader loader, void *data)` — install a custom resource
6689/// loader; stored on the reader and forwarded to its parser context
6690/// (upstream xmlreader.c).
6691///
6692/// # SAFETY
6693///
6694/// - `reader`, `data` must be valid pointers (or NULL
6695/// where the upstream C contract allows), obtained from the
6696/// matching constructor/owner and not yet freed; the callee may
6697/// take or keep ownership exactly as the C API specifies.
6698///
6699/// - `loader` must be a valid callback (or None);
6700/// the callback is invoked with the documented context pointer and
6701/// must itself uphold the same pointer invariants.
6702///
6703/// The caller must not race this call with concurrent mutation of the
6704/// same objects from other threads (per-object state is not internally
6705/// synchronized). Violating any of the above is undefined behavior.
6706///
6707/// Exercised by the C-API differential courts
6708/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6709/// courts; those pass byte-for-byte against the upstream oracle.
6710#[no_mangle]
6711pub unsafe extern "C" fn xmlTextReaderSetResourceLoader(
6712 reader: *mut XmlTextReader,
6713 loader: Option<crate::abi::callbacks::xmlResourceLoader>,
6714 data: *mut c_void,
6715) {
6716 if reader.is_null() {
6717 return;
6718 }
6719 unsafe {
6720 if !(*reader).ctxt.is_null() {
6721 crate::abi::exports_parserint::xmlCtxtSetResourceLoader((*reader).ctxt, loader, data);
6722 }
6723 }
6724}
6725
6726/// `const xmlError *xmlTextReaderGetLastError(xmlTextReaderPtr reader)` —
6727/// pointer to the reader's embedded `_xmlError` (upstream returns
6728/// `&reader->ctxt->lastError`, which is always present while the reader
6729/// exists; valid until the next error is collected).
6730///
6731/// # SAFETY
6732///
6733/// - `reader` must be valid pointers (or NULL
6734/// where the upstream C contract allows), obtained from the
6735/// matching constructor/owner and not yet freed; the callee may
6736/// take or keep ownership exactly as the C API specifies.
6737///
6738/// The caller must not race this call with concurrent mutation of the
6739/// same objects from other threads (per-object state is not internally
6740/// synchronized). Violating any of the above is undefined behavior.
6741///
6742/// Exercised by the C-API differential courts
6743/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6744/// courts; those pass byte-for-byte against the upstream oracle.
6745#[no_mangle]
6746pub unsafe extern "C" fn xmlTextReaderGetLastError(
6747 reader: *mut XmlTextReader,
6748) -> *const crate::abi::structs::_xmlError {
6749 if reader.is_null() {
6750 return ptr::null();
6751 }
6752 let r = unsafe { &mut *reader };
6753 // Sync the embedded struct from the most recent collected error, if any.
6754 // With no errors the struct stays zeroed (message NULL) — matching the
6755 // oracle, which still returns a non-NULL pointer here.
6756 if let Some(msg) = r.errors.last() {
6757 unsafe {
6758 // Message is a fresh NUL-terminated xmlMalloc copy owned by the
6759 // reader (freed on replacement and on drop).
6760 let bytes = msg.as_bytes();
6761 let m = libc::malloc(bytes.len() + 1) as *mut xmlChar;
6762 if !m.is_null() {
6763 libc::memcpy(
6764 m as *mut libc::c_void,
6765 bytes.as_ptr() as *const libc::c_void,
6766 bytes.len(),
6767 );
6768 *m.add(bytes.len()) = 0;
6769 if !r.last_err.message.is_null() {
6770 libc::free(r.last_err.message as *mut libc::c_void);
6771 }
6772 (*reader).last_err.message = m as *mut c_char;
6773 (*reader).last_err.domain = crate::abi::types::XML_FROM_PARSER as c_int;
6774 (*reader).last_err.level = crate::abi::types::xmlErrorLevel::XML_ERR_ERROR as c_int;
6775 (*reader).last_err.code = crate::abi::types::XML_ERR_INTERNAL_ERROR as c_int;
6776 }
6777 }
6778 }
6779 &(*reader).last_err as *const crate::abi::structs::_xmlError
6780}
6781
6782/// `xmlChar *xmlTextReaderLocatorBaseURI(xmlTextReaderLocatorPtr locator)`.
6783///
6784/// # SAFETY
6785///
6786/// - `locator` must be valid or NULL.
6787#[no_mangle]
6788pub unsafe extern "C" fn xmlTextReaderLocatorBaseURI(
6789 locator: *mut XmlTextReaderLocator,
6790) -> *mut xmlChar {
6791 if locator.is_null() {
6792 return ptr::null_mut();
6793 }
6794 unsafe {
6795 let r = (*locator).reader;
6796 if r.is_null() {
6797 return ptr::null_mut();
6798 }
6799 xml_strdup((*r).URL)
6800 }
6801}
6802
6803/// `int xmlTextReaderLocatorLineNumber(xmlTextReaderLocatorPtr locator)`.
6804///
6805/// # SAFETY
6806///
6807/// - `locator` must be valid pointers (or NULL
6808/// where the upstream C contract allows), obtained from the
6809/// matching constructor/owner and not yet freed; the callee may
6810/// take or keep ownership exactly as the C API specifies.
6811///
6812/// The caller must not race this call with concurrent mutation of the
6813/// same objects from other threads (per-object state is not internally
6814/// synchronized). Violating any of the above is undefined behavior.
6815///
6816/// Exercised by the C-API differential courts
6817/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6818/// courts; those pass byte-for-byte against the upstream oracle.
6819#[no_mangle]
6820pub unsafe extern "C" fn xmlTextReaderLocatorLineNumber(
6821 locator: *mut XmlTextReaderLocator,
6822) -> c_int {
6823 if locator.is_null() {
6824 return -1;
6825 }
6826 unsafe {
6827 let r = (*locator).reader;
6828 if r.is_null() {
6829 return -1;
6830 }
6831 let node = (*r).cur_node;
6832 if node.is_null() {
6833 return -1;
6834 }
6835 (*node).line as c_int
6836 }
6837}
6838
6839/// `xmlParserInputBufferPtr xmlTextReaderGetRemainder(xmlTextReaderPtr reader)`.
6840///
6841/// Returns NULL — the candidate reads the whole input up front (documented
6842/// divergence: no unconsumed input remains).
6843///
6844/// # SAFETY
6845///
6846/// - `_reader` must be valid pointers (or NULL
6847/// where the upstream C contract allows), obtained from the
6848/// matching constructor/owner and not yet freed; the callee may
6849/// take or keep ownership exactly as the C API specifies.
6850///
6851/// The caller must not race this call with concurrent mutation of the
6852/// same objects from other threads (per-object state is not internally
6853/// synchronized). Violating any of the above is undefined behavior.
6854///
6855/// Exercised by the C-API differential courts
6856/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6857/// courts; those pass byte-for-byte against the upstream oracle.
6858#[no_mangle]
6859pub const unsafe extern "C" fn xmlTextReaderGetRemainder(
6860 _reader: *mut XmlTextReader,
6861) -> *mut crate::abi::structs::_xmlParserInputBuffer {
6862 ptr::null_mut()
6863}
6864
6865/// `void xmlTextReaderSetMaxAmplification(xmlTextReaderPtr reader, unsigned maxAmpl)`.
6866///
6867/// # SAFETY
6868///
6869/// - `reader` must be valid pointers (or NULL
6870/// where the upstream C contract allows), obtained from the
6871/// matching constructor/owner and not yet freed; the callee may
6872/// take or keep ownership exactly as the C API specifies.
6873///
6874/// The caller must not race this call with concurrent mutation of the
6875/// same objects from other threads (per-object state is not internally
6876/// synchronized). Violating any of the above is undefined behavior.
6877///
6878/// Exercised by the C-API differential courts
6879/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6880/// courts; those pass byte-for-byte against the upstream oracle.
6881#[no_mangle]
6882pub unsafe extern "C" fn xmlTextReaderSetMaxAmplification(
6883 reader: *mut XmlTextReader,
6884 maxAmpl: c_uint,
6885) {
6886 if reader.is_null() {
6887 return;
6888 }
6889 unsafe { (*reader).max_amplification = maxAmpl as c_int };
6890}
6891
6892/// `int xmlTextReaderSchemaValidate(xmlTextReaderPtr reader, const char *xsd)` —
6893/// parse `xsd` and validate the reader's document as it is processed.
6894///
6895/// UPSTREAM-PARITY (xmlreader.c xmlTextReaderSchemaValidateInternal):
6896/// activation is only possible before the first Read(); the schema is
6897/// compiled NOW (a compile failure returns -1, which php reports as
6898/// "Schema contains errors") and the document is validated at read time —
6899/// validity diagnostics surface on the reader's error channel while
6900/// Read() streams, not as this call's return value.
6901///
6902/// # SAFETY
6903///
6904/// - `xsd` must be a valid path or NULL.
6905#[no_mangle]
6906pub unsafe extern "C" fn xmlTextReaderSchemaValidate(
6907 reader: *mut XmlTextReader,
6908 xsd: *const c_char,
6909) -> c_int {
6910 if reader.is_null() {
6911 return -1;
6912 }
6913 let r = unsafe { &mut *reader };
6914 if r.parsed {
6915 // Upstream: only XML_TEXTREADER_MODE_INITIAL readers can activate.
6916 return -1;
6917 }
6918 if xsd.is_null() {
6919 // Deactivate validation.
6920 if r.schema_owned && !r.schema.is_null() {
6921 // SAFETY: schema was compiled by xmlSchemaParse.
6922 unsafe { crate::xml::schemas::xmlSchemaFree(r.schema) };
6923 }
6924 r.schema = ptr::null_mut();
6925 r.schema_owned = false;
6926 r.xsd_result = -1;
6927 return 0;
6928 }
6929 // Compile the schema now. I/O and well-formedness diagnostics of the
6930 // schema document are raised by the parser machinery inside this call
6931 // (php prefixes them with XMLReader::setSchema()).
6932 let pctxt = crate::xml::schemas::xmlSchemaNewParserCtxt(xsd);
6933 if pctxt.is_null() {
6934 return -1;
6935 }
6936 let schema = crate::xml::schemas::xmlSchemaParse(pctxt);
6937 crate::xml::schemas::xmlSchemaFreeParserCtxt(pctxt);
6938 if schema.is_null() {
6939 return -1;
6940 }
6941 // Drop a previously attached reader-owned schema.
6942 if r.schema_owned && !r.schema.is_null() {
6943 // SAFETY: schema was compiled by xmlSchemaParse.
6944 unsafe { crate::xml::schemas::xmlSchemaFree(r.schema) };
6945 }
6946 r.schema = schema;
6947 r.schema_owned = true;
6948 r.xsd_result = -1;
6949 0
6950}
6951
6952/// `int xmlTextReaderSchemaValidateCtxt(xmlTextReaderPtr reader, xmlSchemaValidCtxtPtr ctxt, int options)`.
6953///
6954/// # SAFETY
6955///
6956/// - `reader`, `ctxt` must be valid pointers (or NULL
6957/// where the upstream C contract allows), obtained from the
6958/// matching constructor/owner and not yet freed; the callee may
6959/// take or keep ownership exactly as the C API specifies.
6960///
6961/// The caller must not race this call with concurrent mutation of the
6962/// same objects from other threads (per-object state is not internally
6963/// synchronized). Violating any of the above is undefined behavior.
6964///
6965/// Exercised by the C-API differential courts
6966/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6967/// courts; those pass byte-for-byte against the upstream oracle.
6968#[no_mangle]
6969pub unsafe extern "C" fn xmlTextReaderSchemaValidateCtxt(
6970 reader: *mut XmlTextReader,
6971 ctxt: *mut c_void,
6972 _options: c_int,
6973) -> c_int {
6974 if reader.is_null() || ctxt.is_null() {
6975 return -1;
6976 }
6977 if unsafe { (*reader).doc }.is_null() && !unsafe { (*reader).parsed } {
6978 unsafe { (*reader).Read() };
6979 }
6980 crate::xml::schemas::xmlSchemaValidateDoc(ctxt, unsafe { (*reader).doc })
6981}
6982
6983/// `int xmlTextReaderSetSchema(xmlTextReaderPtr reader, xmlSchemaPtr schema)`.
6984///
6985/// # SAFETY
6986///
6987/// - `reader`, `schema` must be valid pointers (or NULL
6988/// where the upstream C contract allows), obtained from the
6989/// matching constructor/owner and not yet freed; the callee may
6990/// take or keep ownership exactly as the C API specifies.
6991///
6992/// The caller must not race this call with concurrent mutation of the
6993/// same objects from other threads (per-object state is not internally
6994/// synchronized). Violating any of the above is undefined behavior.
6995///
6996/// Exercised by the C-API differential courts
6997/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
6998/// courts; those pass byte-for-byte against the upstream oracle.
6999#[no_mangle]
7000pub unsafe extern "C" fn xmlTextReaderSetSchema(
7001 reader: *mut XmlTextReader,
7002 schema: *mut c_void,
7003) -> c_int {
7004 if reader.is_null() {
7005 return -1;
7006 }
7007 unsafe {
7008 let r = &mut *reader;
7009 // Drop a previously attached reader-owned schema (a caller-owned one
7010 // is released by its owner; upstream never frees the passed-in
7011 // schema).
7012 if r.schema_owned && !r.schema.is_null() && r.schema != schema {
7013 crate::xml::schemas::xmlSchemaFree(r.schema);
7014 }
7015 r.schema = schema;
7016 r.schema_owned = false;
7017 r.xsd_result = -1;
7018 }
7019 0
7020}
7021
7022/// `int xmlTextReaderRelaxNGValidate(xmlTextReaderPtr reader, const char *rng)`.
7023///
7024/// # SAFETY
7025///
7026/// - `reader` must be valid pointers (or NULL
7027/// where the upstream C contract allows), obtained from the
7028/// matching constructor/owner and not yet freed; the callee may
7029/// take or keep ownership exactly as the C API specifies.
7030///
7031/// - `rng` must point to valid NUL-terminated
7032/// strings (or NULL where the C contract allows) for the lifetime
7033/// of the call.
7034///
7035/// The caller must not race this call with concurrent mutation of the
7036/// same objects from other threads (per-object state is not internally
7037/// synchronized). Violating any of the above is undefined behavior.
7038///
7039/// Exercised by the C-API differential courts
7040/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
7041/// courts; those pass byte-for-byte against the upstream oracle.
7042#[no_mangle]
7043pub unsafe extern "C" fn xmlTextReaderRelaxNGValidate(
7044 reader: *mut XmlTextReader,
7045 rng: *const c_char,
7046) -> c_int {
7047 if reader.is_null() {
7048 return -1;
7049 }
7050 let r = unsafe { &mut *reader };
7051 if r.parsed {
7052 // Upstream: only XML_TEXTREADER_MODE_INITIAL readers can activate.
7053 return -1;
7054 }
7055 if rng.is_null() {
7056 // Deactivate validation.
7057 if r.rng_owned && !r.rng.is_null() {
7058 // SAFETY: rng was compiled by xmlRelaxNGParse.
7059 unsafe { crate::xml::relaxng::xmlRelaxNGFree(r.rng) };
7060 }
7061 r.rng = ptr::null_mut();
7062 r.rng_owned = false;
7063 r.rng_result = -1;
7064 return 0;
7065 }
7066 // Compile the schema now (I/O diagnostics of the schema resource are
7067 // raised inside this call); validation itself is deferred to read time.
7068 let pctxt = crate::xml::relaxng::xmlRelaxNGNewParserCtxt(rng);
7069 if pctxt.is_null() {
7070 return -1;
7071 }
7072 let schema = crate::xml::relaxng::xmlRelaxNGParse(pctxt);
7073 crate::xml::relaxng::xmlRelaxNGFreeParserCtxt(pctxt);
7074 if schema.is_null() {
7075 return -1;
7076 }
7077 if r.rng_owned && !r.rng.is_null() {
7078 // SAFETY: rng was compiled by xmlRelaxNGParse.
7079 unsafe { crate::xml::relaxng::xmlRelaxNGFree(r.rng) };
7080 }
7081 r.rng = schema;
7082 r.rng_owned = true;
7083 r.rng_result = -1;
7084 0
7085}
7086
7087/// `int xmlTextReaderRelaxNGValidateCtxt(xmlTextReaderPtr reader, xmlRelaxNGValidCtxtPtr ctxt, int options)`.
7088///
7089/// # SAFETY
7090///
7091/// - `reader`, `ctxt` must be valid pointers (or NULL
7092/// where the upstream C contract allows), obtained from the
7093/// matching constructor/owner and not yet freed; the callee may
7094/// take or keep ownership exactly as the C API specifies.
7095///
7096/// The caller must not race this call with concurrent mutation of the
7097/// same objects from other threads (per-object state is not internally
7098/// synchronized). Violating any of the above is undefined behavior.
7099///
7100/// Exercised by the C-API differential courts
7101/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
7102/// courts; those pass byte-for-byte against the upstream oracle.
7103#[no_mangle]
7104pub unsafe extern "C" fn xmlTextReaderRelaxNGValidateCtxt(
7105 reader: *mut XmlTextReader,
7106 ctxt: *mut c_void,
7107 _options: c_int,
7108) -> c_int {
7109 if reader.is_null() || ctxt.is_null() {
7110 return -1;
7111 }
7112 if unsafe { (*reader).doc }.is_null() && !unsafe { (*reader).parsed } {
7113 unsafe { (*reader).Read() };
7114 }
7115 crate::xml::relaxng::xmlRelaxNGValidateDoc(ctxt, unsafe { (*reader).doc })
7116}
7117
7118/// `int xmlTextReaderRelaxNGSetSchema(xmlTextReaderPtr reader, xmlRelaxNGPtr schema)`.
7119///
7120/// # SAFETY
7121///
7122/// - `reader`, `schema` must be valid pointers (or NULL
7123/// where the upstream C contract allows), obtained from the
7124/// matching constructor/owner and not yet freed; the callee may
7125/// take or keep ownership exactly as the C API specifies.
7126///
7127/// The caller must not race this call with concurrent mutation of the
7128/// same objects from other threads (per-object state is not internally
7129/// synchronized). Violating any of the above is undefined behavior.
7130///
7131/// Exercised by the C-API differential courts
7132/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
7133/// courts; those pass byte-for-byte against the upstream oracle.
7134#[no_mangle]
7135pub unsafe extern "C" fn xmlTextReaderRelaxNGSetSchema(
7136 reader: *mut XmlTextReader,
7137 schema: *mut c_void,
7138) -> c_int {
7139 if reader.is_null() {
7140 return -1;
7141 }
7142 unsafe {
7143 let r = &mut *reader;
7144 // Drop a previously attached reader-owned schema (a caller-owned one
7145 // — php keeps it in intern->schema — is released by its owner).
7146 if r.rng_owned && !r.rng.is_null() && r.rng != schema {
7147 crate::xml::relaxng::xmlRelaxNGFree(r.rng);
7148 }
7149 r.rng = schema;
7150 r.rng_owned = false;
7151 r.rng_result = -1;
7152 }
7153 0
7154}