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