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