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