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