#![allow(
missing_docs,
non_snake_case,
non_camel_case_types,
non_upper_case_globals
)]
use core::ffi::c_void;
use core::ptr;
use std::os::raw::{c_char, c_int};
use crate::abi::allocator::xmlFree;
use crate::abi::callbacks::{xmlInputCloseCallback, xmlInputReadCallback};
use crate::abi::structs::{_xmlAttr, _xmlDoc, _xmlNode, _xmlParserCtxt, _xmlParserInputBuffer};
use crate::abi::types::xmlElementType::*;
use crate::abi::types::*;
use crate::xml::parser::helpers::{
create_parser_ctxt, free_parser_ctxt, input_from_file, input_from_io, input_from_memory,
parse_document, setup_parser_input,
};
use crate::xml::parser::input::InputBuffer;
use crate::xml::string::{bytes_to_xmlstr, xml_strdup, xmlstr_to_bytes, xmlstr_to_string};
use crate::xml::tree;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub(crate) enum ReaderNodeType {
NONE = 0,
ELEMENT = 1,
ATTRIBUTE = 2,
TEXT = 3,
CDATA = 4,
ENTITY_REFERENCE = 5,
ENTITY = 6,
PROCESSING_INSTRUCTION = 7,
COMMENT = 8,
DOCUMENT = 9,
DOCUMENT_TYPE = 10,
DOCUMENT_FRAGMENT = 11,
NOTATION = 12,
WHITESPACE = 13,
SIGNIFICANT_WHITESPACE = 14,
END_ELEMENT = 15,
END_ENTITY = 16,
XML_DECLARATION = 17,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub(crate) enum ReadState {
NOT_INITIALIZED = 0,
INITIALIZED = 1,
READING = 2,
EOF = 3,
CLOSED = 4,
ERROR = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub(crate) enum ParserProp {
LOADDTD = 1,
DEFAULTATTRS = 2,
VALIDATE = 3,
SUBST_ENTITIES = 4,
}
#[derive(Debug, Clone)]
struct TraversalEvent {
node: *mut _xmlNode,
is_end: bool,
depth: i32,
}
unsafe fn compute_depth(node: *mut _xmlNode) -> i32 {
if node.is_null() {
return 0;
}
let mut depth: i32 = 0;
let mut cur = unsafe { (*node).parent };
while !cur.is_null() {
if unsafe { (*cur).type_ } == XML_ELEMENT_NODE as c_int {
depth += 1;
}
cur = unsafe { (*cur).parent };
}
depth
}
fn element_type_to_reader_type(etype: c_int) -> ReaderNodeType {
match etype {
x if x == XML_ELEMENT_NODE as c_int => ReaderNodeType::ELEMENT,
x if x == XML_ATTRIBUTE_NODE as c_int => ReaderNodeType::ATTRIBUTE,
x if x == XML_TEXT_NODE as c_int => ReaderNodeType::TEXT,
x if x == XML_CDATA_SECTION_NODE as c_int => ReaderNodeType::CDATA,
x if x == XML_ENTITY_REF_NODE as c_int => ReaderNodeType::ENTITY_REFERENCE,
x if x == XML_ENTITY_NODE as c_int => ReaderNodeType::ENTITY,
x if x == XML_PI_NODE as c_int => ReaderNodeType::PROCESSING_INSTRUCTION,
x if x == XML_COMMENT_NODE as c_int => ReaderNodeType::COMMENT,
x if x == XML_DOCUMENT_NODE as c_int => ReaderNodeType::DOCUMENT,
x if x == XML_DOCUMENT_TYPE_NODE as c_int => ReaderNodeType::DOCUMENT_TYPE,
x if x == XML_DOCUMENT_FRAG_NODE as c_int => ReaderNodeType::DOCUMENT_FRAGMENT,
x if x == XML_NOTATION_NODE as c_int => ReaderNodeType::NOTATION,
x if x == XML_DTD_NODE as c_int => ReaderNodeType::DOCUMENT_TYPE,
x if x == XML_NAMESPACE_DECL as c_int => ReaderNodeType::NONE,
_ => ReaderNodeType::NONE,
}
}
fn is_whitespace_only(text: &[u8]) -> bool {
text.iter()
.all(|&b| b == b' ' || b == b'\t' || b == b'\n' || b == b'\r')
}
pub(crate) struct XmlTextReader {
doc: *mut _xmlDoc,
ctxt: *mut _xmlParserCtxt,
events: Vec<TraversalEvent>,
event_index: usize,
state: ReadState,
cur_node: *mut _xmlNode,
node_type: ReaderNodeType,
depth: i32,
name: *mut xmlChar,
value: *mut xmlChar,
attribute_count: i32,
cur_attribute: i32,
options: c_int,
encoding: *mut xmlChar,
URL: *mut xmlChar,
errors: Vec<String>,
parsed: bool,
}
impl XmlTextReader {
unsafe fn new(ctxt: *mut _xmlParserCtxt, URL: Option<&[u8]>, encoding: Option<&[u8]>) -> Self {
let url_ptr = URL
.map(|u| unsafe { bytes_to_xmlstr(u) })
.unwrap_or(ptr::null_mut());
let enc_ptr = encoding
.map(|e| unsafe { bytes_to_xmlstr(e) })
.unwrap_or(ptr::null_mut());
XmlTextReader {
doc: ptr::null_mut(),
ctxt,
events: Vec::new(),
event_index: 0,
state: ReadState::INITIALIZED,
cur_node: ptr::null_mut(),
node_type: ReaderNodeType::NONE,
depth: 0,
name: ptr::null_mut(),
value: ptr::null_mut(),
attribute_count: -1,
cur_attribute: -1,
options: 0,
encoding: enc_ptr,
URL: url_ptr,
errors: Vec::new(),
parsed: false,
}
}
unsafe fn parse_and_build_events(&mut self) -> c_int {
if self.ctxt.is_null() {
self.state = ReadState::ERROR;
self.errors.push("No parser context".to_string());
return -1;
}
unsafe {
(*self.ctxt).options = self.options;
}
let result = unsafe { parse_document(self.ctxt) };
let doc = unsafe { (*self.ctxt).myDoc };
self.doc = doc;
if !self.ctxt.is_null() {
unsafe { free_parser_ctxt(self.ctxt) };
}
self.ctxt = ptr::null_mut();
if result != 0 || doc.is_null() {
self.state = ReadState::ERROR;
self.errors.push("Failed to parse document".to_string());
return -1;
}
if self.encoding.is_null() && !doc.is_null() {
let doc_enc = unsafe { (*doc).encoding };
if !doc_enc.is_null() {
self.encoding = unsafe { xml_strdup(doc_enc as *const xmlChar) };
}
}
self.build_events();
self.parsed = true;
0
}
fn build_events(&mut self) {
self.events.clear();
if self.doc.is_null() {
return;
}
let root = unsafe { (*self.doc).children };
if root.is_null() {
return;
}
unsafe {
let mut n = root;
while !n.is_null() {
self.walk_tree(n, 0);
n = (*n).next;
}
}
}
unsafe fn walk_tree(&mut self, node: *mut _xmlNode, depth: i32) {
if node.is_null() {
return;
}
let node_type = unsafe { (*node).type_ };
if node_type == XML_ELEMENT_NODE as c_int {
self.events.push(TraversalEvent {
node,
is_end: false,
depth,
});
let mut child = unsafe { (*node).children };
while !child.is_null() {
let child_depth = depth + 1;
self.walk_tree(child, child_depth);
child = unsafe { (*child).next };
}
self.events.push(TraversalEvent {
node,
is_end: true,
depth,
});
} else if node_type == XML_TEXT_NODE as c_int
|| node_type == XML_CDATA_SECTION_NODE as c_int
|| node_type == XML_COMMENT_NODE as c_int
|| node_type == XML_PI_NODE as c_int
|| node_type == XML_ENTITY_REF_NODE as c_int
{
if node_type == XML_TEXT_NODE as c_int || node_type == XML_CDATA_SECTION_NODE as c_int {
let content = unsafe { (*node).content };
if !content.is_null() {
let len = crate::xml::tree::xml_strlen(content);
let slice = unsafe { core::slice::from_raw_parts(content, len as usize) };
if slice
.iter()
.all(|&b| b == b' ' || b == b'\t' || b == b'\n' || b == b'\r')
{
return;
}
}
}
self.events.push(TraversalEvent {
node,
is_end: false,
depth,
});
} else {
self.events.push(TraversalEvent {
node,
is_end: false,
depth,
});
}
}
fn position_at(&mut self, index: usize) {
if index >= self.events.len() {
self.state = ReadState::EOF;
self.cur_node = ptr::null_mut();
self.node_type = ReaderNodeType::NONE;
self.depth = 0;
self.clear_cached_name();
self.clear_cached_value();
self.attribute_count = -1;
self.cur_attribute = -1;
return;
}
let ev_node: *mut _xmlNode;
let ev_is_end: bool;
let ev_depth: i32;
{
let event = &self.events[index];
ev_node = event.node;
ev_is_end = event.is_end;
ev_depth = event.depth;
}
self.event_index = index;
self.cur_node = ev_node;
self.depth = ev_depth;
let etype = unsafe { (*ev_node).type_ };
if ev_is_end {
self.node_type = ReaderNodeType::END_ELEMENT;
} else {
self.node_type = element_type_to_reader_type(etype);
}
unsafe { self.cache_name_and_value(ev_node, ev_is_end) };
if etype == XML_ELEMENT_NODE as c_int && !ev_is_end {
self.attribute_count = unsafe { self.count_attributes(ev_node) };
} else {
self.attribute_count = -1;
}
self.cur_attribute = -1;
}
unsafe fn cache_name_and_value(&mut self, node: *mut _xmlNode, is_end: bool) {
self.clear_cached_name();
self.clear_cached_value();
if node.is_null() {
return;
}
let etype = unsafe { (*node).type_ };
let name: *mut xmlChar = if is_end {
unsafe { (*node).name as *mut xmlChar }
} else {
if etype == XML_ELEMENT_NODE as c_int
|| etype == XML_PI_NODE as c_int
|| etype == XML_ENTITY_REF_NODE as c_int
|| etype == XML_ENTITY_NODE as c_int
|| etype == XML_DOCUMENT_TYPE_NODE as c_int
|| etype == XML_NOTATION_NODE as c_int
{
unsafe { (*node).name as *mut xmlChar }
} else if etype == XML_ATTRIBUTE_NODE as c_int {
ptr::null_mut()
} else {
ptr::null_mut()
}
};
if !name.is_null() {
self.name = unsafe { xml_strdup(name as *const xmlChar) };
}
let value: *mut xmlChar = if etype == XML_TEXT_NODE as c_int
|| etype == XML_CDATA_SECTION_NODE as c_int
|| etype == XML_COMMENT_NODE as c_int
{
unsafe { (*node).content }
} else if etype == XML_PI_NODE as c_int {
unsafe { (*node).content }
} else if etype == XML_ENTITY_REF_NODE as c_int {
unsafe { (*node).content }
} else {
ptr::null_mut()
};
if !value.is_null() {
self.value = unsafe { xml_strdup(value as *const xmlChar) };
}
}
unsafe fn count_attributes(&self, node: *mut _xmlNode) -> i32 {
let mut count: i32 = 0;
let mut prop = unsafe { (*node).properties };
while !prop.is_null() {
count += 1;
prop = unsafe { (*prop).next };
}
count
}
fn clear_cached_name(&mut self) {
if !self.name.is_null() {
unsafe { xmlFree(self.name as *mut c_void) };
self.name = ptr::null_mut();
}
}
fn clear_cached_value(&mut self) {
if !self.value.is_null() {
unsafe { xmlFree(self.value as *mut c_void) };
self.value = ptr::null_mut();
}
}
pub unsafe fn Read(&mut self) -> c_int {
if self.state == ReadState::ERROR || self.state == ReadState::CLOSED {
return -1;
}
if !self.parsed {
if self.parse_and_build_events() != 0 {
self.state = ReadState::ERROR;
return -1;
}
self.state = ReadState::READING;
}
if self.state == ReadState::EOF {
return 0;
}
if self.cur_attribute >= 0 {
self.cur_attribute = -1;
if !self.cur_node.is_null() {
unsafe { self.cache_name_and_value(self.cur_node, false) };
}
}
if self.events.is_empty() {
self.state = ReadState::EOF;
return 0;
}
let next_index = if self.cur_node.is_null() {
0
} else {
self.event_index + 1
};
if next_index < self.events.len() {
self.position_at(next_index);
1
} else {
self.state = ReadState::EOF;
self.cur_node = ptr::null_mut();
self.node_type = ReaderNodeType::NONE;
self.depth = 0;
self.clear_cached_name();
self.clear_cached_value();
self.attribute_count = -1;
self.cur_attribute = -1;
0
}
}
pub unsafe fn Next(&mut self) -> c_int {
if self.state != ReadState::READING || self.cur_node.is_null() {
return -1;
}
let current_depth = self.depth;
let mut i = self.event_index + 1;
while i < self.events.len() {
let event = &self.events[i];
if event.depth <= current_depth && !event.is_end {
self.position_at(i);
return 1;
}
i += 1;
}
0
}
pub unsafe fn MoveToElement(&mut self) -> c_int {
if self.cur_attribute < 0 {
return 0;
}
self.cur_attribute = -1;
if !self.cur_node.is_null() {
unsafe { self.cache_name_and_value(self.cur_node, false) };
self.node_type = ReaderNodeType::ELEMENT;
}
1
}
pub unsafe fn MoveToAttribute(&mut self, name: *const xmlChar) -> c_int {
if self.cur_node.is_null() {
return -1;
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return -1;
}
let mut prop = unsafe { (*self.cur_node).properties };
let mut index: i32 = 0;
while !prop.is_null() {
let prop_name = unsafe { (*prop).name };
if !prop_name.is_null() {
if unsafe { crate::xml::string::xml_strcmp(prop_name as *const xmlChar, name) == 0 }
{
self.cur_attribute = index;
self.cache_attribute_info(prop);
return 1;
}
}
index += 1;
prop = unsafe { (*prop).next };
}
0
}
pub unsafe fn MoveToAttributeNo(&mut self, index: c_int) -> c_int {
if self.cur_node.is_null() || index < 0 {
return -1;
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return -1;
}
let mut prop = unsafe { (*self.cur_node).properties };
let mut i: i32 = 0;
while !prop.is_null() {
if i == index {
self.cur_attribute = index;
self.cache_attribute_info(prop);
return 1;
}
i += 1;
prop = unsafe { (*prop).next };
}
0
}
pub unsafe fn MoveToFirstAttribute(&mut self) -> c_int {
if self.cur_node.is_null() {
return -1;
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return -1;
}
let first_prop = unsafe { (*self.cur_node).properties };
if first_prop.is_null() {
return 0;
}
self.cur_attribute = 0;
self.cache_attribute_info(first_prop);
1
}
pub unsafe fn MoveToNextAttribute(&mut self) -> c_int {
if self.cur_attribute < 0 || self.cur_node.is_null() {
return -1;
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return -1;
}
let mut prop = unsafe { (*self.cur_node).properties };
let mut i: i32 = 0;
while !prop.is_null() && i <= self.cur_attribute {
if i == self.cur_attribute {
let next_prop = unsafe { (*prop).next };
if next_prop.is_null() {
return 0;
}
self.cur_attribute += 1;
self.cache_attribute_info(next_prop);
return 1;
}
i += 1;
prop = unsafe { (*prop).next };
}
0
}
unsafe fn cache_attribute_info(&mut self, prop: *mut _xmlAttr) {
self.node_type = ReaderNodeType::ATTRIBUTE;
self.clear_cached_name();
self.clear_cached_value();
let attr = unsafe { &*prop };
if !attr.name.is_null() {
self.name = unsafe { xml_strdup(attr.name as *const xmlChar) };
}
if !attr.children.is_null() {
let val = unsafe { (*attr.children).content };
if !val.is_null() {
self.value = unsafe { xml_strdup(val as *const xmlChar) };
}
}
}
pub unsafe fn Prev(&mut self) -> c_int {
if self.state != ReadState::READING || self.cur_node.is_null() {
return -1;
}
let current_depth = self.depth;
let mut i = if self.event_index > 0 {
self.event_index - 1
} else {
return 0;
};
loop {
let event = &self.events[i];
if event.depth == current_depth && !event.is_end {
self.position_at(i);
return 1;
}
if i == 0 {
break;
}
i -= 1;
}
0
}
pub fn Depth(&self) -> c_int {
self.depth
}
pub fn NodeType(&self) -> ReaderNodeType {
self.node_type
}
pub unsafe fn Name(&self) -> *mut xmlChar {
if self.name.is_null() {
return ptr::null_mut();
}
unsafe { xml_strdup(self.name as *const xmlChar) }
}
pub unsafe fn Value(&self) -> *mut xmlChar {
if self.value.is_null() {
return ptr::null_mut();
}
unsafe { xml_strdup(self.value as *const xmlChar) }
}
pub fn ConstName(&self) -> *const xmlChar {
self.name as *const xmlChar
}
pub fn ConstValue(&self) -> *const xmlChar {
self.value as *const xmlChar
}
pub fn HasValue(&self) -> c_int {
if self.value.is_null() {
0
} else {
1
}
}
pub fn HasAttributes(&self) -> c_int {
if self.cur_node.is_null() {
return 0;
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return 0;
}
let props = unsafe { (*self.cur_node).properties };
if props.is_null() {
0
} else {
1
}
}
pub fn IsEmptyElement(&self) -> c_int {
if self.cur_node.is_null() {
return 0;
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return 0;
}
let children = unsafe { (*self.cur_node).children };
if children.is_null() {
1
} else {
0
}
}
pub unsafe fn BaseUri(&self) -> *mut xmlChar {
if self.doc.is_null() {
return ptr::null_mut();
}
let url = unsafe { (*self.doc).URL };
if url.is_null() {
return ptr::null_mut();
}
unsafe { xml_strdup(url as *const xmlChar) }
}
pub unsafe fn LocalName(&self) -> *mut xmlChar {
if self.name.is_null() {
return ptr::null_mut();
}
let name_bytes = unsafe { xmlstr_to_bytes(self.name as *const xmlChar) };
if let Some(pos) = name_bytes.iter().position(|&b| b == b':') {
let local = &name_bytes[pos + 1..];
if local.is_empty() {
return ptr::null_mut();
}
unsafe { bytes_to_xmlstr(local) }
} else {
unsafe { xml_strdup(self.name as *const xmlChar) }
}
}
pub unsafe fn NamespaceUri(&self) -> *mut xmlChar {
if self.cur_node.is_null() {
return ptr::null_mut();
}
let ns = unsafe { (*self.cur_node).ns };
if ns.is_null() {
return ptr::null_mut();
}
let href = unsafe { (*ns).href };
if href.is_null() {
return ptr::null_mut();
}
unsafe { xml_strdup(href as *const xmlChar) }
}
pub unsafe fn Prefix(&self) -> *mut xmlChar {
if self.cur_node.is_null() {
return ptr::null_mut();
}
let ns = unsafe { (*self.cur_node).ns };
if ns.is_null() {
return ptr::null_mut();
}
let prefix = unsafe { (*ns).prefix };
if prefix.is_null() {
return ptr::null_mut();
}
unsafe { xml_strdup(prefix as *const xmlChar) }
}
pub fn AttributeCount(&self) -> c_int {
self.attribute_count
}
pub fn ReadState(&self) -> ReadState {
self.state
}
pub unsafe fn GetAttribute(&self, name: *const xmlChar) -> *mut xmlChar {
if self.cur_node.is_null() {
return ptr::null_mut();
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return ptr::null_mut();
}
let mut prop = unsafe { (*self.cur_node).properties };
while !prop.is_null() {
let prop_name = unsafe { (*prop).name };
if !prop_name.is_null() {
if unsafe { crate::xml::string::xml_strcmp(prop_name as *const xmlChar, name) == 0 }
{
let val = unsafe { (*prop).children };
if !val.is_null() {
let content = unsafe { (*val).content };
if !content.is_null() {
return unsafe { xml_strdup(content as *const xmlChar) };
}
}
return ptr::null_mut();
}
}
prop = unsafe { (*prop).next };
}
ptr::null_mut()
}
pub unsafe fn GetAttributeNo(&self, index: c_int) -> *mut xmlChar {
if self.cur_node.is_null() || index < 0 {
return ptr::null_mut();
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return ptr::null_mut();
}
let mut prop = unsafe { (*self.cur_node).properties };
let mut i: i32 = 0;
while !prop.is_null() {
if i == index {
let val = unsafe { (*prop).children };
if !val.is_null() {
let content = unsafe { (*val).content };
if !content.is_null() {
return unsafe { xml_strdup(content as *const xmlChar) };
}
}
return ptr::null_mut();
}
i += 1;
prop = unsafe { (*prop).next };
}
ptr::null_mut()
}
pub unsafe fn GetAttributeNs(
&self,
localName: *const xmlChar,
namespaceURI: *const xmlChar,
) -> *mut xmlChar {
if self.cur_node.is_null() {
return ptr::null_mut();
}
let etype = unsafe { (*self.cur_node).type_ };
if etype != XML_ELEMENT_NODE as c_int {
return ptr::null_mut();
}
let mut prop = unsafe { (*self.cur_node).properties };
while !prop.is_null() {
let prop_local = unsafe { (*prop).name };
let prop_ns = unsafe { (*prop).ns };
if prop_local.is_null() {
prop = unsafe { (*prop).next };
continue;
}
let name_match = unsafe {
crate::xml::string::xml_strcmp(prop_local as *const xmlChar, localName) == 0
};
if name_match {
let ns_match = if namespaceURI.is_null() {
prop_ns.is_null()
} else if prop_ns.is_null() {
false
} else {
unsafe {
crate::xml::string::xml_strcmp(
(*prop_ns).href as *const xmlChar,
namespaceURI,
) == 0
}
};
if ns_match {
let val = unsafe { (*prop).children };
if !val.is_null() {
let content = unsafe { (*val).content };
if !content.is_null() {
return unsafe { xml_strdup(content as *const xmlChar) };
}
}
return ptr::null_mut();
}
}
prop = unsafe { (*prop).next };
}
ptr::null_mut()
}
pub unsafe fn LookupNamespace(&self, prefix: *const xmlChar) -> *mut xmlChar {
if self.cur_node.is_null() {
return ptr::null_mut();
}
let mut cur = self.cur_node;
while !cur.is_null() {
let mut ns_def = unsafe { (*cur).nsDef };
while !ns_def.is_null() {
let ns_prefix = unsafe { (*ns_def).prefix };
let match_prefix = if prefix.is_null() || *prefix == 0 {
ns_prefix.is_null()
} else if ns_prefix.is_null() {
false
} else {
unsafe {
crate::xml::string::xml_strcmp(ns_prefix as *const xmlChar, prefix) == 0
}
};
if match_prefix {
let href = unsafe { (*ns_def).href };
if !href.is_null() {
return unsafe { xml_strdup(href as *const xmlChar) };
}
return ptr::null_mut();
}
ns_def = unsafe { (*ns_def).next };
}
cur = unsafe { (*cur).parent };
}
ptr::null_mut()
}
pub fn GetParserProp(&self, prop: c_int) -> c_int {
match prop {
1 => {
if (self.options & XML_PARSE_DTDLOAD) != 0 { 1 } else { 0 }
}
2 => {
if (self.options & XML_PARSE_DTDATTR) != 0 { 1 } else { 0 }
}
3 => {
if (self.options & XML_PARSE_DTDVALID) != 0 { 1 } else { 0 }
}
4 => {
if (self.options & XML_PARSE_NOENT) != 0 { 1 } else { 0 }
}
_ => -1,
}
}
pub fn SetParserProp(&mut self, prop: c_int, value: c_int) -> c_int {
match prop {
1 => {
if value != 0 {
self.options |= XML_PARSE_DTDLOAD;
} else {
self.options &= !XML_PARSE_DTDLOAD;
}
0
}
2 => {
if value != 0 {
self.options |= XML_PARSE_DTDATTR;
} else {
self.options &= !XML_PARSE_DTDATTR;
}
0
}
3 => {
if value != 0 {
self.options |= XML_PARSE_DTDVALID;
} else {
self.options &= !XML_PARSE_DTDVALID;
}
0
}
4 => {
if value != 0 {
self.options |= XML_PARSE_NOENT;
} else {
self.options &= !XML_PARSE_NOENT;
}
0
}
_ => -1,
}
}
pub fn CurrentDoc(&self) -> *mut _xmlDoc {
self.doc
}
}
impl Drop for XmlTextReader {
fn drop(&mut self) {
self.clear_cached_name();
self.clear_cached_value();
if !self.encoding.is_null() {
unsafe { xmlFree(self.encoding as *mut c_void) };
self.encoding = ptr::null_mut();
}
if !self.URL.is_null() {
unsafe { xmlFree(self.URL as *mut c_void) };
self.URL = ptr::null_mut();
}
if !self.doc.is_null() {
unsafe { tree::free_doc(self.doc) };
self.doc = ptr::null_mut();
}
if !self.ctxt.is_null() {
unsafe { free_parser_ctxt(self.ctxt) };
self.ctxt = ptr::null_mut();
}
}
}
unsafe fn reader_from_input(
input: *mut _xmlParserInputBuffer,
URL: *const c_char,
encoding: *const c_char,
options: c_int,
) -> *mut XmlTextReader {
if input.is_null() {
return ptr::null_mut();
}
let ctxt = create_parser_ctxt();
if ctxt.is_null() {
return ptr::null_mut();
}
let mut data = Vec::new();
let mut tmp = [0u8; 4096];
let read_cb = unsafe { (*input).readcallback };
let ioctx = unsafe { (*input).context };
if let Some(read) = read_cb {
loop {
let n = unsafe { read(ioctx, tmp.as_mut_ptr() as *mut c_char, tmp.len() as c_int) };
if n <= 0 {
break;
}
data.extend_from_slice(&tmp[..n as usize]);
}
}
let close_cb = unsafe { (*input).closecallback };
if let Some(close) = close_cb {
unsafe { close(ioctx) };
}
let input_buf = InputBuffer::from_memory(&data, None);
setup_parser_input(ctxt, input_buf);
unsafe {
(*ctxt).options = options;
}
let url_bytes = if URL.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(URL);
Some(cstr.to_bytes().to_vec())
}
};
let enc_bytes = if encoding.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(encoding);
Some(cstr.to_bytes().to_vec())
}
};
let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
reader.options = options;
Box::into_raw(Box::new(reader))
}
#[no_mangle]
pub unsafe extern "C" fn xmlNewTextReader(
input: *mut _xmlParserInputBuffer,
URI: *const c_char,
) -> *mut XmlTextReader {
unsafe { reader_from_input(input, URI, ptr::null(), 0) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlReaderForFile(
filename: *const c_char,
encoding: *const c_char,
options: c_int,
) -> *mut XmlTextReader {
if filename.is_null() {
return ptr::null_mut();
}
let ctxt = unsafe { create_parser_ctxt() };
if ctxt.is_null() {
return ptr::null_mut();
}
let input = match unsafe { input_from_file(filename) } {
Ok(input) => input,
Err(_) => {
unsafe { free_parser_ctxt(ctxt) };
return ptr::null_mut();
}
};
unsafe { setup_parser_input(ctxt, input) };
unsafe {
(*ctxt).options = options;
}
let enc_bytes = if encoding.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(encoding);
Some(cstr.to_bytes().to_vec())
}
};
let mut reader = XmlTextReader::new(ctxt, None, enc_bytes.as_deref());
reader.options = options;
Box::into_raw(Box::new(reader))
}
#[no_mangle]
pub unsafe extern "C" fn xmlReaderForMemory(
buffer: *const c_char,
size: c_int,
URL: *const c_char,
encoding: *const c_char,
options: c_int,
) -> *mut XmlTextReader {
if buffer.is_null() || size <= 0 {
return ptr::null_mut();
}
let ctxt = unsafe { create_parser_ctxt() };
if ctxt.is_null() {
return ptr::null_mut();
}
let input = unsafe { input_from_memory(buffer, size) };
unsafe { setup_parser_input(ctxt, input) };
unsafe {
(*ctxt).options = options;
}
let url_bytes = if URL.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(URL);
Some(cstr.to_bytes().to_vec())
}
};
let enc_bytes = if encoding.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(encoding);
Some(cstr.to_bytes().to_vec())
}
};
let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
reader.options = options;
Box::into_raw(Box::new(reader))
}
#[no_mangle]
pub unsafe extern "C" fn xmlReaderForFd(
fd: c_int,
URL: *const c_char,
encoding: *const c_char,
options: c_int,
) -> *mut XmlTextReader {
let ctxt = unsafe { create_parser_ctxt() };
if ctxt.is_null() {
return ptr::null_mut();
}
let mut buf = Vec::new();
let mut tmp = [0u8; 4096];
loop {
let n = unsafe { libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len()) };
if n <= 0 {
break;
}
buf.extend_from_slice(&tmp[..n as usize]);
}
let input = unsafe { input_from_memory(buf.as_ptr() as *const c_char, buf.len() as c_int) };
unsafe { setup_parser_input(ctxt, input) };
unsafe {
(*ctxt).options = options;
}
let url_bytes = if URL.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(URL);
Some(cstr.to_bytes().to_vec())
}
};
let enc_bytes = if encoding.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(encoding);
Some(cstr.to_bytes().to_vec())
}
};
let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
reader.options = options;
Box::into_raw(Box::new(reader))
}
#[no_mangle]
pub unsafe extern "C" fn xmlReaderForIO(
ioread: Option<xmlInputReadCallback>,
ioclose: Option<xmlInputCloseCallback>,
ioctx: *mut c_void,
URL: *const c_char,
encoding: *const c_char,
options: c_int,
) -> *mut XmlTextReader {
let ctxt = unsafe { create_parser_ctxt() };
if ctxt.is_null() {
return ptr::null_mut();
}
let input = unsafe { input_from_io(ioread, ioclose, ioctx) };
unsafe { setup_parser_input(ctxt, input) };
unsafe {
(*ctxt).options = options;
}
let url_bytes = if URL.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(URL);
Some(cstr.to_bytes().to_vec())
}
};
let enc_bytes = if encoding.is_null() {
None
} else {
unsafe {
let cstr = std::ffi::CStr::from_ptr(encoding);
Some(cstr.to_bytes().to_vec())
}
};
let mut reader = XmlTextReader::new(ctxt, url_bytes.as_deref(), enc_bytes.as_deref());
reader.options = options;
Box::into_raw(Box::new(reader))
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderRead(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).Read() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderNext(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).Next() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderNextSibling(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).Next() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderPrev(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).Prev() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderMoveToElement(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).MoveToElement() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderMoveToAttribute(
reader: *mut XmlTextReader,
name: *const xmlChar,
) -> c_int {
if reader.is_null() || name.is_null() {
return -1;
}
unsafe { (*reader).MoveToAttribute(name) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderMoveToAttributeNo(
reader: *mut XmlTextReader,
index: c_int,
) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).MoveToAttributeNo(index) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderMoveToFirstAttribute(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).MoveToFirstAttribute() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderMoveToNextAttribute(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).MoveToNextAttribute() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderAttributeCount(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).AttributeCount() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderDepth(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).Depth() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderNodeType(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).NodeType() as c_int }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderName(reader: *mut XmlTextReader) -> *mut xmlChar {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).Name() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderValue(reader: *mut XmlTextReader) -> *mut xmlChar {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).Value() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderConstName(reader: *mut XmlTextReader) -> *const xmlChar {
if reader.is_null() {
return ptr::null();
}
unsafe { (*reader).ConstName() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderConstValue(reader: *mut XmlTextReader) -> *const xmlChar {
if reader.is_null() {
return ptr::null();
}
unsafe { (*reader).ConstValue() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderBaseUri(reader: *mut XmlTextReader) -> *mut xmlChar {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).BaseUri() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderLocalName(reader: *mut XmlTextReader) -> *mut xmlChar {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).LocalName() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderNamespaceUri(reader: *mut XmlTextReader) -> *mut xmlChar {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).NamespaceUri() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderPrefix(reader: *mut XmlTextReader) -> *mut xmlChar {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).Prefix() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderHasValue(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return 0;
}
unsafe { (*reader).HasValue() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderHasAttributes(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return 0;
}
unsafe { (*reader).HasAttributes() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderIsEmptyElement(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return 0;
}
unsafe { (*reader).IsEmptyElement() }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderReadState(reader: *mut XmlTextReader) -> c_int {
if reader.is_null() {
return ReadState::ERROR as c_int;
}
unsafe { (*reader).ReadState() as c_int }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderGetAttribute(
reader: *mut XmlTextReader,
name: *const xmlChar,
) -> *mut xmlChar {
if reader.is_null() || name.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).GetAttribute(name) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderGetAttributeNo(
reader: *mut XmlTextReader,
index: c_int,
) -> *mut xmlChar {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).GetAttributeNo(index) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderGetAttributeNs(
reader: *mut XmlTextReader,
localName: *const xmlChar,
namespaceURI: *const xmlChar,
) -> *mut xmlChar {
if reader.is_null() || localName.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).GetAttributeNs(localName, namespaceURI) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderLookupNamespace(
reader: *mut XmlTextReader,
prefix: *const xmlChar,
) -> *mut xmlChar {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).LookupNamespace(prefix) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderGetParserProp(
reader: *mut XmlTextReader,
prop: c_int,
) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).GetParserProp(prop) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderSetParserProp(
reader: *mut XmlTextReader,
prop: c_int,
value: c_int,
) -> c_int {
if reader.is_null() {
return -1;
}
unsafe { (*reader).SetParserProp(prop, value) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlFreeTextReader(reader: *mut XmlTextReader) {
if reader.is_null() {
return;
}
unsafe {
let _ = Box::from_raw(reader);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderSetup(
reader: *mut XmlTextReader,
input: *mut _xmlParserInputBuffer,
URL: *const c_char,
encoding: *const c_char,
options: c_int,
) -> c_int {
if reader.is_null() {
return -1;
}
let r = unsafe { &mut *reader };
r.clear_cached_name();
r.clear_cached_value();
if !r.doc.is_null() {
unsafe { tree::free_doc(r.doc) };
r.doc = ptr::null_mut();
}
if !r.ctxt.is_null() {
unsafe { free_parser_ctxt(r.ctxt) };
r.ctxt = ptr::null_mut();
}
r.events.clear();
r.event_index = 0;
r.state = ReadState::INITIALIZED;
r.cur_node = ptr::null_mut();
r.node_type = ReaderNodeType::NONE;
r.depth = 0;
r.attribute_count = -1;
r.cur_attribute = -1;
r.options = options;
r.parsed = false;
r.errors.clear();
if !r.URL.is_null() {
unsafe { xmlFree(r.URL as *mut c_void) };
r.URL = ptr::null_mut();
}
if !URL.is_null() {
let url_str = unsafe { std::ffi::CStr::from_ptr(URL) };
r.URL = unsafe { bytes_to_xmlstr(url_str.to_bytes()) };
}
if !r.encoding.is_null() {
unsafe { xmlFree(r.encoding as *mut c_void) };
r.encoding = ptr::null_mut();
}
if !encoding.is_null() {
let enc_str = unsafe { std::ffi::CStr::from_ptr(encoding) };
r.encoding = unsafe { bytes_to_xmlstr(enc_str.to_bytes()) };
}
if !input.is_null() {
let ctxt = unsafe { create_parser_ctxt() };
if ctxt.is_null() {
return -1;
}
let mut data = Vec::new();
let mut tmp = [0u8; 4096];
let read_cb = unsafe { (*input).readcallback };
let ioctx = unsafe { (*input).context };
if let Some(read) = read_cb {
loop {
let n = unsafe { read(ioctx, tmp.as_mut_ptr() as *mut c_char, tmp.len() as c_int) };
if n <= 0 {
break;
}
data.extend_from_slice(&tmp[..n as usize]);
}
}
let close_cb = unsafe { (*input).closecallback };
if let Some(close) = close_cb {
unsafe { close(ioctx) };
}
let input_buf = InputBuffer::from_memory(&data, None);
unsafe { setup_parser_input(ctxt, input_buf) };
unsafe {
(*ctxt).options = options;
}
r.ctxt = ctxt;
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlTextReaderCurrentDoc(reader: *mut XmlTextReader) -> *mut _xmlDoc {
if reader.is_null() {
return ptr::null_mut();
}
unsafe { (*reader).CurrentDoc() }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abi::allocator::xmlFree;
use core::ffi::c_void;
use std::os::raw::c_char;
unsafe fn create_reader(xml: &str) -> *mut XmlTextReader {
let bytes = xml.as_bytes();
xmlReaderForMemory(
bytes.as_ptr() as *const c_char,
bytes.len() as c_int,
ptr::null(),
ptr::null(),
0,
)
}
unsafe fn free_reader(reader: *mut XmlTextReader) {
if !reader.is_null() {
xmlFreeTextReader(reader);
}
}
unsafe fn collect_nodes(reader: *mut XmlTextReader) -> Vec<(ReaderNodeType, String, i32)> {
let mut result = Vec::new();
loop {
let ret = xmlTextReaderRead(reader);
if ret <= 0 {
break;
}
let r = &*reader;
let ntype = r.NodeType();
let name = if r.name.is_null() {
String::new()
} else {
xmlstr_to_string(r.name as *const xmlChar)
};
let depth = r.Depth();
result.push((ntype, name, depth));
}
result
}
#[test]
fn test_create_reader_from_memory() {
unsafe {
let reader = create_reader("<root/>");
assert!(!reader.is_null());
assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
free_reader(reader);
}
}
#[test]
fn test_read_simple_document() {
unsafe {
let reader = create_reader("<root><child>text</child></root>");
assert!(!reader.is_null());
let nodes = collect_nodes(reader);
assert_eq!(nodes.len(), 5);
assert_eq!(nodes[0], (ReaderNodeType::ELEMENT, "root".to_string(), 0));
assert_eq!(nodes[1], (ReaderNodeType::ELEMENT, "child".to_string(), 1));
assert_eq!(nodes[2], (ReaderNodeType::TEXT, "".to_string(), 2));
assert_eq!(
nodes[3],
(ReaderNodeType::END_ELEMENT, "child".to_string(), 1)
);
assert_eq!(
nodes[4],
(ReaderNodeType::END_ELEMENT, "root".to_string(), 0)
);
assert_eq!((*reader).ReadState(), ReadState::EOF);
free_reader(reader);
}
}
#[test]
fn test_read_state_transitions() {
unsafe {
let reader = create_reader("<root/>");
assert!(!reader.is_null());
assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).ReadState(), ReadState::READING);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!((*reader).Depth(), 0);
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::END_ELEMENT);
assert_eq!(xmlTextReaderRead(reader), 0);
assert_eq!((*reader).ReadState(), ReadState::EOF);
free_reader(reader);
}
}
#[test]
fn test_null_reader_returns_error() {
unsafe {
assert_eq!(xmlTextReaderRead(ptr::null_mut()), -1);
assert_eq!(xmlTextReaderDepth(ptr::null_mut()), -1);
assert_eq!(xmlTextReaderNodeType(ptr::null_mut()), -1);
assert!(xmlTextReaderName(ptr::null_mut()).is_null());
assert!(xmlTextReaderValue(ptr::null_mut()).is_null());
assert_eq!(xmlTextReaderHasValue(ptr::null_mut()), 0);
assert_eq!(xmlTextReaderIsEmptyElement(ptr::null_mut()), 0);
assert_eq!(
xmlTextReaderReadState(ptr::null_mut()),
ReadState::ERROR as c_int
);
}
}
#[test]
fn test_xmlFreeTextReader_null() {
unsafe {
xmlFreeTextReader(ptr::null_mut());
}
}
#[test]
fn test_reader_name_and_value() {
unsafe {
let reader = create_reader("<root>hello</root>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
let name = xmlTextReaderName(reader);
assert!(!name.is_null());
assert_eq!(xmlstr_to_string(name), "root");
xmlFree(name as *mut c_void);
assert_eq!(xmlTextReaderHasValue(reader), 0);
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::TEXT);
assert_eq!((*reader).HasValue(), 1);
let val = xmlTextReaderValue(reader);
assert!(!val.is_null());
assert_eq!(xmlstr_to_string(val), "hello");
xmlFree(val as *mut c_void);
free_reader(reader);
}
}
#[test]
fn test_empty_element() {
unsafe {
let reader = create_reader("<empty/>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!((*reader).IsEmptyElement(), 1);
assert_eq!((*reader).HasAttributes(), 0);
assert_eq!((*reader).AttributeCount(), 0);
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::END_ELEMENT);
free_reader(reader);
}
}
#[test]
fn test_element_with_attributes() {
unsafe {
let reader = create_reader(r#"<root a="1" b="2"/>"#);
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!((*reader).HasAttributes(), 1);
let attrs = xmlTextReaderAttributeCount(reader);
assert_eq!(attrs, 2);
free_reader(reader);
}
}
#[test]
fn test_attribute_navigation() {
unsafe {
let reader = create_reader(r#"<root a="1" b="2"></root>"#);
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!(xmlTextReaderMoveToFirstAttribute(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
let name = xmlTextReaderConstName(reader);
assert!(!name.is_null());
assert_eq!(xmlstr_to_bytes(name), b"a");
let val = xmlTextReaderConstValue(reader);
assert!(!val.is_null());
assert_eq!(xmlstr_to_bytes(val), b"1");
assert_eq!(xmlTextReaderMoveToNextAttribute(reader), 1);
let name = xmlTextReaderConstName(reader);
assert!(!name.is_null());
assert_eq!(xmlstr_to_bytes(name), b"b");
let val = xmlTextReaderConstValue(reader);
assert!(!val.is_null());
assert_eq!(xmlstr_to_bytes(val), b"2");
assert_eq!(xmlTextReaderMoveToNextAttribute(reader), 0);
assert_eq!(xmlTextReaderMoveToElement(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!(
xmlTextReaderMoveToAttribute(reader, b"a\0" as *const u8 as *const xmlChar),
1
);
assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
assert_eq!(xmlTextReaderMoveToElement(reader), 1);
assert_eq!(xmlTextReaderMoveToAttributeNo(reader, 1), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ATTRIBUTE);
free_reader(reader);
}
}
#[test]
fn test_get_attribute() {
unsafe {
let reader = create_reader(r#"<root a="hello" b="world"/>"#);
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
let val = xmlTextReaderGetAttribute(reader, b"a\0" as *const u8 as *const xmlChar);
assert!(!val.is_null());
assert_eq!(xmlstr_to_bytes(val), b"hello");
xmlFree(val as *mut c_void);
let val = xmlTextReaderGetAttribute(reader, b"b\0" as *const u8 as *const xmlChar);
assert!(!val.is_null());
assert_eq!(xmlstr_to_bytes(val), b"world");
xmlFree(val as *mut c_void);
let val = xmlTextReaderGetAttribute(reader, b"c\0" as *const u8 as *const xmlChar);
assert!(val.is_null());
let val = xmlTextReaderGetAttributeNo(reader, 0);
assert!(!val.is_null());
assert_eq!(xmlstr_to_bytes(val), b"hello");
xmlFree(val as *mut c_void);
let val = xmlTextReaderGetAttributeNo(reader, 1);
assert!(!val.is_null());
assert_eq!(xmlstr_to_bytes(val), b"world");
xmlFree(val as *mut c_void);
let val = xmlTextReaderGetAttributeNo(reader, 2);
assert!(val.is_null());
free_reader(reader);
}
}
#[test]
fn test_depth_tracking() {
unsafe {
let reader = create_reader("<a><b><c/></b></a>");
assert!(!reader.is_null());
let nodes = collect_nodes(reader);
assert_eq!(nodes.len(), 6);
assert_eq!(nodes[0].2, 0); assert_eq!(nodes[1].2, 1); assert_eq!(nodes[2].2, 2); assert_eq!(nodes[3].2, 2); assert_eq!(nodes[4].2, 1); assert_eq!(nodes[5].2, 0);
free_reader(reader);
}
}
#[test]
fn test_multiple_siblings() {
unsafe {
let reader = create_reader("<root><a>A</a><b>B</b><c>C</c></root>");
assert!(!reader.is_null());
let nodes = collect_nodes(reader);
assert_eq!(nodes.len(), 11);
assert_eq!(nodes[1], (ReaderNodeType::ELEMENT, "a".to_string(), 1));
assert_eq!(nodes[4], (ReaderNodeType::ELEMENT, "b".to_string(), 1));
assert_eq!(nodes[7], (ReaderNodeType::ELEMENT, "c".to_string(), 1));
free_reader(reader);
}
}
#[test]
fn test_next_skip_to_sibling() {
unsafe {
let reader = create_reader("<root><a>A</a><b>B</b><c>C</c></root>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "a");
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::TEXT);
assert_eq!(xmlTextReaderNext(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "b");
assert_eq!(xmlTextReaderNext(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!(xmlstr_to_string((*reader).name as *const xmlChar), "c");
assert_eq!(xmlTextReaderNext(reader), 0);
free_reader(reader);
}
}
#[test]
fn test_comment_and_pi_nodes() {
unsafe {
let xml = b"<?pi target?><root><!-- comment -->text</root>\0";
let reader = xmlReaderForMemory(
xml.as_ptr() as *const c_char,
(xml.len() - 1) as c_int,
ptr::null(),
ptr::null(),
0,
);
assert!(!reader.is_null());
let nodes = collect_nodes(reader);
assert!(!nodes.is_empty(), "no nodes collected: {:?}", nodes);
assert_eq!(
nodes[0].0,
ReaderNodeType::PROCESSING_INSTRUCTION,
"expected PI at nodes[0], got {:?} name={}",
nodes[0].0,
nodes[0].1
);
assert_eq!(
nodes[0].0,
ReaderNodeType::PROCESSING_INSTRUCTION,
"expected PI at nodes[0], got {:?} name={}",
nodes[0].0,
nodes[0].1
);
let root_idx = nodes
.iter()
.position(|(t, n, _)| *t == ReaderNodeType::ELEMENT && n == "root");
assert!(
root_idx.is_some(),
"no ELEMENT root found in nodes: {:?}",
nodes
.iter()
.map(|(t, n, _)| format!("{:?}:{}", t, n))
.collect::<Vec<_>>()
);
let comment_idx = nodes
.iter()
.position(|(t, _, _)| *t == ReaderNodeType::COMMENT);
assert!(comment_idx.is_some(), "no COMMENT found");
let text_idx = nodes
.iter()
.position(|(t, _, _)| *t == ReaderNodeType::TEXT);
assert!(text_idx.is_some(), "no TEXT found");
free_reader(reader);
}
}
#[test]
fn test_local_name() {
unsafe {
let reader = create_reader("<root/>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
let local = xmlTextReaderLocalName(reader);
assert!(!local.is_null());
assert_eq!(xmlstr_to_bytes(local), b"root");
xmlFree(local as *mut c_void);
free_reader(reader);
}
}
#[test]
fn test_base_uri() {
unsafe {
let reader = create_reader("<root/>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
let uri = xmlTextReaderBaseUri(reader);
assert!(uri.is_null());
free_reader(reader);
}
}
#[test]
fn test_lookup_namespace() {
unsafe {
let reader = create_reader(r#"<root xmlns:ns="http://example.com"><ns:child/></root>"#);
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!(xmlTextReaderRead(reader), 1);
let uri = xmlTextReaderLookupNamespace(reader, b"ns\0" as *const u8 as *const xmlChar);
assert!(!uri.is_null());
assert_eq!(xmlstr_to_bytes(uri), b"http://example.com");
xmlFree(uri as *mut c_void);
let uri = xmlTextReaderLookupNamespace(reader, ptr::null());
assert!(uri.is_null());
let uri = xmlTextReaderLookupNamespace(
reader,
b"nonexistent\0" as *const u8 as *const xmlChar,
);
assert!(uri.is_null());
free_reader(reader);
}
}
#[test]
fn test_parser_properties() {
unsafe {
let reader = create_reader("<root/>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderGetParserProp(reader, 1), 0); assert_eq!(xmlTextReaderGetParserProp(reader, 2), 0); assert_eq!(xmlTextReaderGetParserProp(reader, 3), 0); assert_eq!(xmlTextReaderGetParserProp(reader, 4), 0);
assert_eq!(xmlTextReaderSetParserProp(reader, 1, 1), 0);
assert_eq!(xmlTextReaderGetParserProp(reader, 1), 1);
assert_eq!(xmlTextReaderSetParserProp(reader, 4, 1), 0);
assert_eq!(xmlTextReaderGetParserProp(reader, 4), 1);
assert_eq!(xmlTextReaderGetParserProp(reader, 99), -1);
assert_eq!(xmlTextReaderSetParserProp(reader, 99, 1), -1);
free_reader(reader);
}
}
#[test]
fn test_current_doc() {
unsafe {
let reader = create_reader("<root/>");
assert!(!reader.is_null());
assert!((*reader).CurrentDoc().is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
let doc = xmlTextReaderCurrentDoc(reader);
assert!(!doc.is_null());
free_reader(reader);
}
}
#[test]
fn test_free_reader_after_read() {
unsafe {
let reader = create_reader("<root><child/></root>");
assert!(!reader.is_null());
while xmlTextReaderRead(reader) > 0 {}
assert_eq!((*reader).ReadState(), ReadState::EOF);
free_reader(reader);
}
}
#[test]
fn test_reader_for_memory_null_buffer() {
unsafe {
let reader = xmlReaderForMemory(ptr::null(), 10, ptr::null(), ptr::null(), 0);
assert!(reader.is_null());
}
}
#[test]
fn test_reader_for_memory_empty_size() {
unsafe {
let data = b"<root/>";
let reader = xmlReaderForMemory(
data.as_ptr() as *const c_char,
0,
ptr::null(),
ptr::null(),
0,
);
assert!(reader.is_null());
}
}
#[test]
fn test_reader_for_file_not_found() {
unsafe {
let filename = b"/nonexistent/file.xml\0" as *const u8 as *const c_char;
let reader = xmlReaderForFile(filename, ptr::null(), 0);
assert!(reader.is_null());
}
}
#[test]
fn test_const_name_and_value() {
unsafe {
let reader = create_reader("<root>text</root>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
let cname = xmlTextReaderConstName(reader);
assert!(!cname.is_null());
assert_eq!(xmlstr_to_bytes(cname), b"root");
assert_eq!(xmlTextReaderRead(reader), 1);
let cval = xmlTextReaderConstValue(reader);
assert!(!cval.is_null());
assert_eq!(xmlstr_to_bytes(cval), b"text");
free_reader(reader);
}
}
#[test]
fn test_complex_nested_document() {
unsafe {
let xml = r#"<?xml version="1.0"?>
<library>
<book id="1">
<title>XML Fundamentals</title>
<author>John Doe</author>
</book>
<book id="2">
<title>XSLT Recipes</title>
<author>Jane Smith</author>
</book>
</library>"#;
let reader = create_reader(xml);
assert!(!reader.is_null());
let mut element_count = 0;
let mut end_element_count = 0;
let mut text_count = 0;
let mut pi_count = 0;
loop {
let ret = xmlTextReaderRead(reader);
if ret <= 0 {
break;
}
match (*reader).NodeType() {
ReaderNodeType::ELEMENT => element_count += 1,
ReaderNodeType::END_ELEMENT => end_element_count += 1,
ReaderNodeType::TEXT => text_count += 1,
ReaderNodeType::PROCESSING_INSTRUCTION => pi_count += 1,
_ => {}
}
}
assert_eq!(element_count, 7);
assert_eq!(end_element_count, 7);
assert_eq!(text_count, 4);
assert_eq!(pi_count, 0);
free_reader(reader);
}
}
#[test]
fn test_setup_reinitialize() {
unsafe {
let reader = create_reader("<root/>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).ReadState(), ReadState::READING);
assert_eq!(
xmlTextReaderSetup(reader, ptr::null_mut(), ptr::null(), ptr::null(), 0),
0
);
assert_eq!((*reader).ReadState(), ReadState::INITIALIZED);
free_reader(reader);
}
}
#[test]
fn test_has_attributes_on_non_element() {
unsafe {
let reader = create_reader("<root>text</root>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1); assert_eq!((*reader).HasAttributes(), 0); assert_eq!(xmlTextReaderRead(reader), 1); assert_eq!((*reader).HasAttributes(), 0);
free_reader(reader);
}
}
#[test]
fn test_prev_sibling() {
unsafe {
let reader = create_reader("<root><a/><b/><c/></root>");
assert!(!reader.is_null());
while xmlTextReaderRead(reader) > 0 {
}
assert_eq!(xmlTextReaderPrev(reader), -1);
free_reader(reader);
}
}
#[test]
fn test_move_to_attribute_no_not_on_element() {
unsafe {
let reader = create_reader("<root>text</root>");
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1); assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
assert_eq!(xmlTextReaderMoveToAttributeNo(reader, 0), 0);
free_reader(reader);
}
}
#[test]
fn test_get_attribute_ns() {
unsafe {
let reader = create_reader(r#"<root a="1" b="2"/>"#);
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
let val = xmlTextReaderGetAttributeNs(
reader,
b"a\0" as *const u8 as *const xmlChar,
ptr::null(),
);
assert!(!val.is_null());
assert_eq!(xmlstr_to_bytes(val), b"1");
xmlFree(val as *mut c_void);
free_reader(reader);
}
}
#[test]
fn test_mixed_content() {
unsafe {
let reader = create_reader("<root>before<child/>after</root>");
assert!(!reader.is_null());
let nodes = collect_nodes(reader);
assert_eq!(nodes.len(), 6);
assert_eq!(nodes[0], (ReaderNodeType::ELEMENT, "root".to_string(), 0));
assert_eq!(nodes[1].0, ReaderNodeType::TEXT);
assert_eq!(nodes[2], (ReaderNodeType::ELEMENT, "child".to_string(), 1));
assert_eq!(nodes[4].0, ReaderNodeType::TEXT);
free_reader(reader);
}
}
#[test]
fn test_error_handling_invalid_xml() {
unsafe {
let data = b"<root><\0" as *const u8 as *const c_char;
let reader = xmlReaderForMemory(data, 7, ptr::null(), ptr::null(), 0);
assert!(!reader.is_null());
let ret = xmlTextReaderRead(reader);
assert!(ret == -1 || ret == 0);
free_reader(reader);
}
}
#[test]
fn test_reader_with_options() {
unsafe {
let data = b"<root/>\0" as *const u8 as *const c_char;
let reader = xmlReaderForMemory(
data,
7,
ptr::null(),
ptr::null(),
XML_PARSE_NOENT | XML_PARSE_DTDLOAD,
);
assert!(!reader.is_null());
assert_eq!((*reader).options & XML_PARSE_NOENT, XML_PARSE_NOENT);
assert_eq!((*reader).options & XML_PARSE_DTDLOAD, XML_PARSE_DTDLOAD);
assert_eq!(xmlTextReaderRead(reader), 1);
free_reader(reader);
}
}
#[test]
fn test_reader_for_fd() {
unsafe {
let tmp_path = "/tmp/libxml_rs_test_reader_fd.xml";
let tmp_cstr = std::ffi::CString::new(tmp_path).unwrap();
let content = b"<root><data/></root>";
let fd = libc::open(
tmp_cstr.as_ptr(),
libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC,
0o644,
);
assert!(fd >= 0);
libc::write(fd, content.as_ptr() as *const c_void, content.len());
libc::close(fd);
let fd = libc::open(tmp_cstr.as_ptr(), libc::O_RDONLY, 0);
assert!(fd >= 0);
let reader = xmlReaderForFd(fd, ptr::null(), ptr::null(), 0);
assert!(!reader.is_null());
let nodes = collect_nodes(reader);
assert_eq!(nodes.len(), 4);
free_reader(reader);
libc::close(fd);
std::fs::remove_file(tmp_path).ok();
}
}
#[test]
fn test_reader_for_io() {
unsafe {
extern "C" fn io_read(context: *mut c_void, buffer: *mut c_char, len: c_int) -> c_int {
if context.is_null() || buffer.is_null() || len <= 0 {
return -1;
}
let ctx = unsafe { &mut *(context as *mut IoCtx) };
if ctx.pos >= ctx.data.len() {
return 0;
}
let remaining = ctx.data.len() - ctx.pos;
let to_copy = if (remaining as c_int) < len {
remaining
} else {
len as usize
};
unsafe {
std::ptr::copy_nonoverlapping(
ctx.data.as_ptr().add(ctx.pos),
buffer as *mut u8,
to_copy,
);
}
ctx.pos += to_copy;
to_copy as c_int
}
extern "C" fn io_close(_context: *mut c_void) -> c_int {
0
}
struct IoCtx {
data: &'static [u8],
pos: usize,
}
let mut ctx = IoCtx {
data: b"<root/>",
pos: 0,
};
let reader = xmlReaderForIO(
Some(io_read),
Some(io_close),
&mut ctx as *mut IoCtx as *mut c_void,
ptr::null(),
ptr::null(),
0,
);
assert!(!reader.is_null());
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::ELEMENT);
let cname = xmlTextReaderConstName(reader);
assert!(!cname.is_null());
assert_eq!(xmlstr_to_bytes(cname), b"root");
assert_eq!(xmlTextReaderRead(reader), 1);
assert_eq!((*reader).NodeType(), ReaderNodeType::END_ELEMENT);
assert_eq!(xmlTextReaderRead(reader), 0);
free_reader(reader);
}
}
}