#![allow(
missing_docs,
non_snake_case,
non_camel_case_types,
non_upper_case_globals
)]
use core::ffi::c_void;
use core::ptr;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::os::raw::{c_char, c_int};
use crate::abi::callbacks::{
xmlInputCloseCallback, xmlInputReadCallback, xmlOutputCloseCallback, xmlOutputWriteCallback,
};
use crate::abi::structs::{_xmlCharEncodingHandler, _xmlNode};
use crate::abi::types::xmlChar;
use crate::xml::automata::{
xmlAutomataIsDeterministic, xmlAutomataNewCountTrans, xmlAutomataNewOnceTrans,
xmlAutomataNewState, xmlAutomataNewTransition, XmlAutomataPtr, XmlAutomataStatePtr,
};
use crate::xml::encoding;
use crate::xml::regex::{xmlRegExecPushString, xmlRegexpIsDeterministic, RegExecCtxt, XmlRegexp};
unsafe fn build_token2_value(token: *const xmlChar, token2: *const xmlChar) -> Option<Vec<u8>> {
if token.is_null() {
return None;
}
let lenp = cstr_len(token);
let lenn = if token2.is_null() {
0
} else {
cstr_len(token2)
};
if lenn == 0 {
let mut v = Vec::with_capacity(lenp + 1);
v.extend_from_slice(unsafe { core::slice::from_raw_parts(token, lenp) });
v.push(0);
return Some(v);
}
let mut v = Vec::with_capacity(lenp + lenn + 2);
v.extend_from_slice(unsafe { core::slice::from_raw_parts(token, lenp) });
v.push(b'|');
v.extend_from_slice(unsafe { core::slice::from_raw_parts(token2, lenn) });
v.push(0);
Some(v)
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataIsDeterminist(am: XmlAutomataPtr) -> c_int {
xmlAutomataIsDeterministic(am)
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewTransition2(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
token: *const xmlChar,
token2: *const xmlChar,
data: *mut c_void,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || token.is_null() {
return ptr::null_mut();
}
let value = match unsafe { build_token2_value(token, token2) } {
Some(v) => v,
None => return ptr::null_mut(),
};
let target = if to.is_null() {
let s = xmlAutomataNewState(am);
if s.is_null() {
return ptr::null_mut();
}
s
} else {
to
};
xmlAutomataNewTransition(am, from, target, value.as_ptr() as *const c_char, data);
target
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewNegTrans(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
token: *const xmlChar,
token2: *const xmlChar,
data: *mut c_void,
) -> XmlAutomataStatePtr {
xmlAutomataNewTransition2(am, from, to, token, token2, data)
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewCountTrans2(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
token: *const xmlChar,
token2: *const xmlChar,
min: c_int,
max: c_int,
data: *mut c_void,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || token.is_null() {
return ptr::null_mut();
}
if min < 0 {
return ptr::null_mut();
}
if (max < min) || (max < 1) {
return ptr::null_mut();
}
let value = match unsafe { build_token2_value(token, token2) } {
Some(v) => v,
None => return ptr::null_mut(),
};
let target = if to.is_null() {
let s = xmlAutomataNewState(am);
if s.is_null() {
return ptr::null_mut();
}
s
} else {
to
};
xmlAutomataNewCountTrans(
am,
from,
target,
value.as_ptr() as *const c_char,
min,
max,
data,
);
target
}
#[no_mangle]
pub unsafe extern "C" fn xmlAutomataNewOnceTrans2(
am: XmlAutomataPtr,
from: XmlAutomataStatePtr,
to: XmlAutomataStatePtr,
token: *const xmlChar,
token2: *const xmlChar,
min: c_int,
max: c_int,
data: *mut c_void,
) -> XmlAutomataStatePtr {
if am.is_null() || from.is_null() || token.is_null() {
return ptr::null_mut();
}
if min < 1 {
return ptr::null_mut();
}
if max < min {
return ptr::null_mut();
}
let value = match unsafe { build_token2_value(token, token2) } {
Some(v) => v,
None => return ptr::null_mut(),
};
let target = if to.is_null() {
let s = xmlAutomataNewState(am);
if s.is_null() {
return ptr::null_mut();
}
s
} else {
to
};
xmlAutomataNewOnceTrans(
am,
from,
target,
value.as_ptr() as *const c_char,
min,
max,
data,
);
target
}
#[derive(Debug, Clone, Copy, Default)]
struct ExecState {
last_ret: c_int,
}
static EXEC_STATE: Lazy<Mutex<HashMap<usize, ExecState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
#[no_mangle]
pub unsafe extern "C" fn xmlRegExecPushString2(
exec: *mut RegExecCtxt,
value: *const xmlChar,
value2: *const xmlChar,
_data: *mut c_void,
) -> c_int {
if exec.is_null() {
return -1;
}
if value2.is_null() || value.is_null() {
let ret = xmlRegExecPushString(exec, value, _data);
EXEC_STATE
.lock()
.insert(exec as usize, ExecState { last_ret: ret });
return ret;
}
let lenp = cstr_len(value);
let lenn = cstr_len(value2);
let mut buf = Vec::with_capacity(lenp + lenn + 2);
buf.extend_from_slice(unsafe { core::slice::from_raw_parts(value, lenp) });
buf.push(b'|');
buf.extend_from_slice(unsafe { core::slice::from_raw_parts(value2, lenn) });
buf.push(0);
let ret = xmlRegExecPushString(exec, buf.as_ptr(), _data);
EXEC_STATE
.lock()
.insert(exec as usize, ExecState { last_ret: ret });
ret
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegExecNextValues(
exec: *mut RegExecCtxt,
nbval: *mut c_int,
nbneg: *mut c_int,
values: *mut *mut xmlChar,
terminal: *mut c_int,
) -> c_int {
if exec.is_null() || nbval.is_null() || nbneg.is_null() || values.is_null() {
return -1;
}
if unsafe { *nbval } <= 0 {
return -1;
}
unsafe {
*nbval = 0;
*nbneg = 0;
}
if !terminal.is_null() {
let last = EXEC_STATE
.lock()
.get(&(exec as usize))
.map_or(0, |s| s.last_ret);
unsafe { *terminal = if last == 1 { 1 } else { 0 } };
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegExecErrInfo(
exec: *mut RegExecCtxt,
string: *mut *const xmlChar,
nbval: *mut c_int,
nbneg: *mut c_int,
values: *mut *mut xmlChar,
terminal: *mut c_int,
) -> c_int {
if exec.is_null() {
return -1;
}
if !string.is_null() {
unsafe { *string = ptr::null() };
}
xmlRegExecNextValues(exec, nbval, nbneg, values, terminal)
}
#[no_mangle]
pub const unsafe extern "C" fn xmlRegexpIsDeterminist(comp: *const XmlRegexp) -> c_int {
xmlRegexpIsDeterministic(comp)
}
#[no_mangle]
pub unsafe extern "C" fn xmlRegisterCharEncodingHandler(handler: *mut _xmlCharEncodingHandler) {
encoding::add_encoding_handler(handler);
}
pub type xmlInputMatchCallback =
unsafe extern "C" fn(context: *mut c_void, filename: *const c_char) -> c_int;
pub type xmlInputOpenCallback =
unsafe extern "C" fn(context: *mut c_void, filename: *const c_char) -> *mut c_void;
pub type xmlOutputMatchCallback =
unsafe extern "C" fn(context: *mut c_void, filename: *const c_char) -> c_int;
pub type xmlOutputOpenCallback =
unsafe extern "C" fn(context: *mut c_void, filename: *const c_char) -> *mut c_void;
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
struct InputCallbackSet {
match_cb: Option<xmlInputMatchCallback>,
open_cb: Option<xmlInputOpenCallback>,
read_cb: Option<xmlInputReadCallback>,
close_cb: Option<xmlInputCloseCallback>,
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
struct OutputCallbackSet {
match_cb: Option<xmlOutputMatchCallback>,
open_cb: Option<xmlOutputOpenCallback>,
write_cb: Option<xmlOutputWriteCallback>,
close_cb: Option<xmlOutputCloseCallback>,
}
#[allow(dead_code)]
static INPUT_CALLBACKS: Lazy<Mutex<Vec<InputCallbackSet>>> = Lazy::new(|| Mutex::new(Vec::new()));
#[allow(dead_code)]
static OUTPUT_CALLBACKS: Lazy<Mutex<Vec<OutputCallbackSet>>> = Lazy::new(|| Mutex::new(Vec::new()));
#[allow(dead_code)]
const unsafe extern "C" fn xml_io_default_match(
_context: *mut c_void,
_filename: *const c_char,
) -> c_int {
1
}
#[derive(Debug)]
#[repr(C)]
pub struct _xmlPattern {
_opaque: [u8; 0],
}
#[derive(Debug)]
#[repr(C)]
pub struct _xmlStreamCtxt {
_opaque: [u8; 0],
}
pub type xmlPatternPtr = *mut _xmlPattern;
pub type xmlStreamCtxtPtr = *mut _xmlStreamCtxt;
#[allow(dead_code)]
const XML_PATTERN_DEFAULT: c_int = 0;
const XML_PATTERN_XPATH: c_int = 1 << 0;
const XML_PATTERN_XSSEL: c_int = 1 << 1;
const XML_PATTERN_XSFIELD: c_int = 1 << 2;
const XML_PATTERN_NOTPATTERN: c_int = XML_PATTERN_XPATH | XML_PATTERN_XSSEL | XML_PATTERN_XSFIELD;
const PAT_FROM_ROOT: c_int = 1 << 8;
const PAT_FROM_CUR: c_int = 1 << 9;
const XML_STREAM_STEP_DESC: c_int = 1;
const XML_STREAM_STEP_FINAL: c_int = 2;
const XML_STREAM_STEP_ROOT: c_int = 4;
const XML_STREAM_STEP_ATTR: c_int = 8;
const XML_STREAM_STEP_NODE: c_int = 16;
const XML_STREAM_STEP_IN_SET: c_int = 32;
const XML_STREAM_FINAL_IS_ANY_NODE: c_int = 1 << 14;
const XML_STREAM_FROM_ROOT: c_int = 1 << 15;
const XML_STREAM_DESC: c_int = 1 << 16;
const XML_STREAM_ANY_NODE: c_int = 100;
const XML_ELEMENT_NODE: c_int = 1;
const XML_ATTRIBUTE_NODE: c_int = 2;
const XML_DOCUMENT_NODE: c_int = 9;
const XML_HTML_DOCUMENT_NODE: c_int = 13;
const XML_NAMESPACE_DECL: c_int = 18;
const XML_OP_END: c_int = 0;
const XML_OP_ROOT: c_int = 1;
const XML_OP_ELEM: c_int = 2;
const XML_OP_CHILD: c_int = 3;
const XML_OP_ATTR: c_int = 4;
const XML_OP_PARENT: c_int = 5;
const XML_OP_ANCESTOR: c_int = 6;
const XML_OP_NS: c_int = 7;
const XML_OP_ALL: c_int = 8;
#[derive(Debug, Clone)]
struct StepOp {
op: c_int,
value: Option<Vec<u8>>,
value2: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
struct CompiledPattern {
flags: c_int,
steps: Vec<StepOp>,
stream: Option<StreamComp>,
}
#[derive(Debug, Clone)]
struct StreamComp {
nb_step: usize,
steps: Vec<StreamStep>,
flags: c_int,
}
#[derive(Debug, Clone)]
struct StreamStep {
flags: c_int,
name: Option<Vec<u8>>,
ns: Option<Vec<u8>>,
node_type: c_int,
}
struct PatternState {
branches: Vec<CompiledPattern>,
}
struct StreamCtxtState {
comp: StreamComp,
next: Option<usize>,
nb_state: usize,
states: Vec<(i32, i32)>,
level: c_int,
flags: c_int,
block_level: c_int,
}
static PATTERNS: Lazy<Mutex<HashMap<usize, PatternState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static NEXT_PATTERN_KEY: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(1));
static STREAM_CTXTS: Lazy<Mutex<HashMap<usize, StreamCtxtState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static NEXT_STREAM_KEY: Lazy<Mutex<usize>> = Lazy::new(|| Mutex::new(1));
const unsafe fn cstr_len(s: *const xmlChar) -> usize {
if s.is_null() {
return 0;
}
let mut len = 0usize;
while unsafe { *s.add(len) } != 0 {
len += 1;
}
len
}
const unsafe fn cstr_eq_opt(s: Option<&[u8]>, cstr: *const xmlChar) -> bool {
match s {
None => cstr.is_null(),
Some(bytes) => {
if cstr.is_null() {
return false;
}
let mut i = 0usize;
while i < bytes.len() {
if unsafe { *cstr.add(i) } != bytes[i] {
return false;
}
i += 1;
}
unsafe { *cstr.add(i) == 0 }
}
}
}
unsafe fn pat_match(comp: &CompiledPattern, mut node: *mut _xmlNode) -> c_int {
if node.is_null() {
return -1;
}
let mut i: isize = 0;
let mut states: Vec<(isize, *mut _xmlNode)> = Vec::new();
loop {
'process: {
while (i as usize) < comp.steps.len() {
let op = comp.steps[i as usize].op;
match op {
XML_OP_END => return 1,
XML_OP_ROOT => {
if unsafe { (*node).type_ } == XML_NAMESPACE_DECL {
break 'process;
}
node = unsafe { (*node).parent };
if node.is_null() {
break 'process;
}
let t = unsafe { (*node).type_ };
if t == XML_DOCUMENT_NODE || t == XML_HTML_DOCUMENT_NODE {
i += 1;
continue;
}
break 'process;
}
XML_OP_ELEM => {
if unsafe { (*node).type_ } != XML_ELEMENT_NODE {
break 'process;
}
let value = comp.steps[i as usize].value.clone();
let value2 = comp.steps[i as usize].value2.clone();
if let Some(value) = value {
let nm = unsafe { (*node).name };
if nm.is_null() || value[0] != *nm {
break 'process;
}
if !cstr_eq_opt(Some(&value), nm) {
break 'process;
}
let ns_href: *const xmlChar = if unsafe { (*node).ns }.is_null() {
ptr::null()
} else {
unsafe { (*(*node).ns).href }
};
if ns_href.is_null() {
if value2.is_some() {
break 'process;
}
} else if unsafe { *ns_href } != 0 {
if value2.is_none() {
break 'process;
}
if !cstr_eq_opt(value2.as_deref(), ns_href) {
break 'process;
}
}
}
i += 1;
continue;
}
XML_OP_CHILD => {
let t = unsafe { (*node).type_ };
if t != XML_ELEMENT_NODE
&& t != XML_DOCUMENT_NODE
&& t != XML_HTML_DOCUMENT_NODE
{
break 'process;
}
let value = comp.steps[i as usize].value.clone();
let mut lst = unsafe { (*node).children };
if let Some(value) = value {
while !lst.is_null() {
let lt = unsafe { (*lst).type_ };
let ln = unsafe { (*lst).name };
if lt == XML_ELEMENT_NODE
&& !ln.is_null()
&& value[0] == *ln
&& cstr_eq_opt(Some(&value), ln)
{
break;
}
lst = unsafe { (*lst).next };
}
if !lst.is_null() {
i += 1;
continue;
}
}
break 'process;
}
XML_OP_ATTR => {
if unsafe { (*node).type_ } != XML_ATTRIBUTE_NODE {
break 'process;
}
let value = comp.steps[i as usize].value.clone();
let value2 = comp.steps[i as usize].value2.clone();
if let Some(value) = value {
let nm = unsafe { (*node).name };
if nm.is_null() || value[0] != *nm {
break 'process;
}
if !cstr_eq_opt(Some(&value), nm) {
break 'process;
}
}
let ns_href: *const xmlChar = if unsafe { (*node).ns }.is_null() {
ptr::null()
} else {
unsafe { (*(*node).ns).href }
};
if ns_href.is_null() {
if value2.is_some() {
break 'process;
}
} else if value2.is_some() && !cstr_eq_opt(value2.as_deref(), ns_href) {
break 'process;
}
i += 1;
continue;
}
XML_OP_PARENT => {
let t = unsafe { (*node).type_ };
if t == XML_DOCUMENT_NODE
|| t == XML_HTML_DOCUMENT_NODE
|| t == XML_NAMESPACE_DECL
{
break 'process;
}
node = unsafe { (*node).parent };
if node.is_null() {
break 'process;
}
let value = comp.steps[i as usize].value.clone();
let value2 = comp.steps[i as usize].value2.clone();
if let Some(value) = value {
let nm = unsafe { (*node).name };
if nm.is_null() || value[0] != *nm {
break 'process;
}
if !cstr_eq_opt(Some(&value), nm) {
break 'process;
}
let ns_href: *const xmlChar = if unsafe { (*node).ns }.is_null() {
ptr::null()
} else {
unsafe { (*(*node).ns).href }
};
if ns_href.is_null() {
if value2.is_some() {
break 'process;
}
} else if unsafe { *ns_href } != 0 {
if value2.is_none() {
break 'process;
}
if !cstr_eq_opt(value2.as_deref(), ns_href) {
break 'process;
}
}
}
i += 1;
continue;
}
XML_OP_ANCESTOR => {
let mut value = comp.steps[i as usize].value.clone();
let mut value2 = comp.steps[i as usize].value2.clone();
let mut step_op = comp.steps[i as usize].op;
if value.is_none() {
i += 1;
if (i as usize) >= comp.steps.len() {
break 'process;
}
step_op = comp.steps[i as usize].op;
if step_op == XML_OP_ROOT {
return 1;
}
if step_op != XML_OP_ELEM {
break 'process;
}
value = comp.steps[i as usize].value.clone();
value2 = comp.steps[i as usize].value2.clone();
if value.is_none() {
return -1;
}
}
if node.is_null() {
break 'process;
}
let t = unsafe { (*node).type_ };
if t == XML_DOCUMENT_NODE
|| t == XML_HTML_DOCUMENT_NODE
|| t == XML_NAMESPACE_DECL
{
break 'process;
}
node = unsafe { (*node).parent };
let value = match value {
Some(v) => v,
None => break 'process,
};
while !node.is_null() {
let nt = unsafe { (*node).type_ };
let nn = unsafe { (*node).name };
if nt == XML_ELEMENT_NODE
&& !nn.is_null()
&& value[0] == *nn
&& cstr_eq_opt(Some(&value), nn)
{
let ns_href: *const xmlChar = if unsafe { (*node).ns }.is_null() {
ptr::null()
} else {
unsafe { (*(*node).ns).href }
};
if ns_href.is_null() {
if value2.is_none() {
break;
}
} else if unsafe { *ns_href } != 0
&& value2.is_some()
&& cstr_eq_opt(value2.as_deref(), ns_href)
{
break;
}
}
node = unsafe { (*node).parent };
}
if node.is_null() {
break 'process;
}
if step_op == XML_OP_ANCESTOR {
states.push((i, node));
} else {
states.push((i - 1, node));
}
i += 1;
continue;
}
XML_OP_NS => {
if unsafe { (*node).type_ } != XML_ELEMENT_NODE {
break 'process;
}
let value = comp.steps[i as usize].value.clone();
let ns_href: *const xmlChar = if unsafe { (*node).ns }.is_null() {
ptr::null()
} else {
unsafe { (*(*node).ns).href }
};
if ns_href.is_null() {
if value.is_some() {
break 'process;
}
} else if unsafe { *ns_href } != 0 {
if value.is_none() {
break 'process;
}
if !cstr_eq_opt(value.as_deref(), ns_href) {
break 'process;
}
}
i += 1;
continue;
}
XML_OP_ALL => {
if unsafe { (*node).type_ } != XML_ELEMENT_NODE {
break 'process;
}
i += 1;
continue;
}
_ => {
i += 1;
}
}
}
return 1;
}
match states.pop() {
None => return 0,
Some((si, sn)) => {
i = si;
node = sn;
}
}
}
}
struct PatCtxt<'a> {
cur: usize,
base: &'a [u8],
error: c_int,
namespaces: Vec<(Vec<u8>, Vec<u8>)>,
}
const fn is_blank(c: u8) -> bool {
c == 0x20 || c == 0x9 || c == 0xA || c == 0xD
}
const fn is_name_start(c: u8) -> bool {
c >= 0x80 || c == b'_' || c == b':' || c.is_ascii_alphabetic()
}
const fn is_name_char(c: u8) -> bool {
c >= 0x80 || c.is_ascii_alphanumeric() || c == b'.' || c == b'-' || c == b'_' || c == b':'
}
const fn is_ncname_start(c: u8) -> bool {
c >= 0x80 || c == b'_' || c.is_ascii_alphabetic()
}
const fn is_ncname_char(c: u8) -> bool {
c >= 0x80 || c.is_ascii_alphanumeric() || c == b'.' || c == b'-' || c == b'_'
}
impl<'a> PatCtxt<'a> {
fn cur_byte(&self) -> Option<u8> {
self.base.get(self.cur).copied()
}
fn peek(&self, off: usize) -> Option<u8> {
self.base.get(self.cur + off).copied()
}
fn is_blank_cur(&self) -> bool {
self.cur_byte().is_some_and(is_blank)
}
fn skip_blanks(&mut self) {
while self.cur_byte().is_some_and(is_blank) {
self.cur += 1;
}
}
fn scan_name(&mut self) -> Option<Vec<u8>> {
self.skip_blanks();
let start = self.cur;
let mut cur = start;
if self.base.get(cur).is_some_and(|&c| !is_name_start(c)) {
return None;
}
cur += 1;
while self.base.get(cur).is_some_and(|&c| is_name_char(c)) {
cur += 1;
}
if cur == start {
return None;
}
let ret = self.base[start..cur].to_vec();
self.cur = cur;
Some(ret)
}
fn scan_ncname(&mut self) -> Option<Vec<u8>> {
self.skip_blanks();
let start = self.cur;
let mut cur = start;
if self.base.get(cur).is_some_and(|&c| !is_ncname_start(c)) {
return None;
}
cur += 1;
while self.base.get(cur).is_some_and(|&c| is_ncname_char(c)) {
cur += 1;
}
if cur == start {
return None;
}
let ret = self.base[start..cur].to_vec();
self.cur = cur;
Some(ret)
}
}
const XML_XML_NAMESPACE: &[u8] = b"http://www.w3.org/XML/1998/namespace";
fn resolve_prefix(ctxt: &PatCtxt, prefix: &[u8]) -> Option<Vec<u8>> {
if prefix == b"xml" {
return Some(XML_XML_NAMESPACE.to_vec());
}
for (uri, pref) in &ctxt.namespaces {
if pref == prefix {
return Some(uri.clone());
}
}
None
}
fn compile_attribute_test(ctxt: &mut PatCtxt, comp: &mut CompiledPattern) {
ctxt.skip_blanks();
let name = ctxt.scan_ncname();
if ctxt.error < 0 {
return;
}
let Some(name) = name else {
if ctxt.cur_byte() == Some(b'*') {
comp.steps.push(StepOp {
op: XML_OP_ATTR,
value: None,
value2: None,
});
ctxt.cur += 1;
} else {
ctxt.error = 1;
}
return;
};
if ctxt.cur_byte() == Some(b':') {
let prefix = name;
ctxt.cur += 1;
if ctxt.is_blank_cur() {
ctxt.error = 1;
return;
}
let token = ctxt.scan_name();
let url = resolve_prefix(ctxt, &prefix);
let Some(url) = url else {
ctxt.error = 1;
return;
};
if let Some(token) = token {
comp.steps.push(StepOp {
op: XML_OP_ATTR,
value: Some(token),
value2: Some(url),
});
} else {
if ctxt.cur_byte() == Some(b'*') {
ctxt.cur += 1;
comp.steps.push(StepOp {
op: XML_OP_ATTR,
value: None,
value2: Some(url),
});
} else {
ctxt.error = 1;
}
}
} else {
comp.steps.push(StepOp {
op: XML_OP_ATTR,
value: Some(name),
value2: None,
});
}
}
fn compile_step_pattern(ctxt: &mut PatCtxt, comp: &mut CompiledPattern) {
ctxt.skip_blanks();
if ctxt.cur_byte() == Some(b'.') {
ctxt.cur += 1;
comp.steps.push(StepOp {
op: XML_OP_ELEM,
value: None,
value2: None,
});
return;
}
if ctxt.cur_byte() == Some(b'@') {
if comp.flags & XML_PATTERN_XSSEL != 0 {
ctxt.error = 1;
return;
}
ctxt.cur += 1;
compile_attribute_test(ctxt, comp);
if ctxt.error != 0 {
return;
}
return;
}
let name = ctxt.scan_ncname();
if ctxt.error < 0 {
return;
}
let Some(mut name) = name else {
if ctxt.cur_byte() == Some(b'*') {
ctxt.cur += 1;
comp.steps.push(StepOp {
op: XML_OP_ALL,
value: None,
value2: None,
});
} else {
ctxt.error = 1;
}
return;
};
let mut has_blanks = false;
if ctxt.is_blank_cur() {
has_blanks = true;
ctxt.skip_blanks();
}
if ctxt.cur_byte() == Some(b':') {
ctxt.cur += 1;
if ctxt.cur_byte() != Some(b':') {
let prefix = name;
if has_blanks || ctxt.is_blank_cur() {
ctxt.error = 1;
return;
}
let token = ctxt.scan_name();
let url = resolve_prefix(ctxt, &prefix);
let Some(url) = url else {
ctxt.error = 1;
return;
};
if let Some(token) = token {
comp.steps.push(StepOp {
op: XML_OP_ELEM,
value: Some(token),
value2: Some(url),
});
} else {
if ctxt.cur_byte() == Some(b'*') {
ctxt.cur += 1;
comp.steps.push(StepOp {
op: XML_OP_NS,
value: None,
value2: Some(url),
});
} else {
ctxt.error = 1;
}
}
return;
}
ctxt.cur += 1;
if name == b"child" {
match ctxt.scan_name() {
None => {
if ctxt.cur_byte() == Some(b'*') {
ctxt.cur += 1;
comp.steps.push(StepOp {
op: XML_OP_ALL,
value: None,
value2: None,
});
return;
}
ctxt.error = 1;
return;
}
Some(n) => name = n,
}
if ctxt.cur_byte() == Some(b':') {
let prefix = name;
ctxt.cur += 1;
if ctxt.is_blank_cur() {
ctxt.error = 1;
return;
}
let token = ctxt.scan_name();
let url = resolve_prefix(ctxt, &prefix);
let Some(url) = url else {
ctxt.error = 1;
return;
};
if let Some(token) = token {
comp.steps.push(StepOp {
op: XML_OP_ELEM,
value: Some(token),
value2: Some(url),
});
} else {
if ctxt.cur_byte() == Some(b'*') {
ctxt.cur += 1;
comp.steps.push(StepOp {
op: XML_OP_NS,
value: None,
value2: Some(url),
});
} else {
ctxt.error = 1;
}
}
return;
}
comp.steps.push(StepOp {
op: XML_OP_ELEM,
value: Some(name),
value2: None,
});
} else if name == b"attribute" {
if comp.flags & XML_PATTERN_XSSEL != 0 {
ctxt.error = 1;
return;
}
compile_attribute_test(ctxt, comp);
if ctxt.error != 0 {}
} else {
ctxt.error = 1;
}
} else if ctxt.cur_byte() == Some(b'*') {
ctxt.error = 1;
} else {
comp.steps.push(StepOp {
op: XML_OP_ELEM,
value: Some(name),
value2: None,
});
}
}
fn compile_path_pattern(ctxt: &mut PatCtxt, comp: &mut CompiledPattern) {
ctxt.skip_blanks();
if ctxt.cur_byte() == Some(b'/') {
comp.flags |= PAT_FROM_ROOT;
} else if ctxt.cur_byte() == Some(b'.') || (comp.flags & XML_PATTERN_NOTPATTERN) != 0 {
comp.flags |= PAT_FROM_CUR;
}
if ctxt.cur_byte() == Some(b'/') && ctxt.peek(1) == Some(b'/') {
comp.steps.push(StepOp {
op: XML_OP_ANCESTOR,
value: None,
value2: None,
});
ctxt.cur += 2;
} else if ctxt.cur_byte() == Some(b'.')
&& ctxt.peek(1) == Some(b'/')
&& ctxt.peek(2) == Some(b'/')
{
comp.steps.push(StepOp {
op: XML_OP_ANCESTOR,
value: None,
value2: None,
});
ctxt.cur += 3;
ctxt.skip_blanks();
if ctxt.cur_byte().is_none() {
ctxt.error = 1;
return;
}
}
if ctxt.cur_byte() == Some(b'@') {
ctxt.cur += 1;
compile_attribute_test(ctxt, comp);
if ctxt.error != 0 {
return;
}
ctxt.skip_blanks();
if ctxt.cur_byte().is_some() {
compile_step_pattern(ctxt, comp);
if ctxt.error != 0 {
return;
}
}
} else {
if ctxt.cur_byte() == Some(b'/') {
comp.steps.push(StepOp {
op: XML_OP_ROOT,
value: None,
value2: None,
});
ctxt.cur += 1;
ctxt.skip_blanks();
if ctxt.cur_byte().is_none() {
ctxt.error = 1;
return;
}
}
compile_step_pattern(ctxt, comp);
if ctxt.error != 0 {
return;
}
ctxt.skip_blanks();
while ctxt.cur_byte() == Some(b'/') {
if ctxt.peek(1) == Some(b'/') {
comp.steps.push(StepOp {
op: XML_OP_ANCESTOR,
value: None,
value2: None,
});
ctxt.cur += 2;
ctxt.skip_blanks();
compile_step_pattern(ctxt, comp);
if ctxt.error != 0 {
return;
}
} else {
comp.steps.push(StepOp {
op: XML_OP_PARENT,
value: None,
value2: None,
});
ctxt.cur += 1;
ctxt.skip_blanks();
if ctxt.cur_byte().is_none() {
ctxt.error = 1;
return;
}
compile_step_pattern(ctxt, comp);
if ctxt.error != 0 {
return;
}
}
}
}
if ctxt.cur_byte().is_some() {
ctxt.error = 1;
}
}
fn compile_idc_xpath_path(ctxt: &mut PatCtxt, comp: &mut CompiledPattern) {
ctxt.skip_blanks();
if ctxt.cur_byte() == Some(b'/') {
ctxt.error = 1;
return;
}
comp.flags |= PAT_FROM_CUR;
if ctxt.cur_byte() == Some(b'.') {
ctxt.cur += 1;
ctxt.skip_blanks();
if ctxt.cur_byte().is_none() {
comp.steps.push(StepOp {
op: XML_OP_ELEM,
value: None,
value2: None,
});
return;
}
if ctxt.cur_byte() != Some(b'/') {
ctxt.error = 1;
return;
}
ctxt.cur += 1;
ctxt.skip_blanks();
if ctxt.cur_byte() == Some(b'/') {
if ctxt.cur > 0 && is_blank(ctxt.base[ctxt.cur - 1]) {
ctxt.error = 1;
return;
}
comp.steps.push(StepOp {
op: XML_OP_ANCESTOR,
value: None,
value2: None,
});
ctxt.cur += 1;
ctxt.skip_blanks();
}
if ctxt.cur_byte().is_none() {
ctxt.error = 1;
return;
}
}
loop {
compile_step_pattern(ctxt, comp);
if ctxt.error != 0 {
return;
}
ctxt.skip_blanks();
if ctxt.cur_byte() != Some(b'/') {
break;
}
comp.steps.push(StepOp {
op: XML_OP_PARENT,
value: None,
value2: None,
});
ctxt.cur += 1;
ctxt.skip_blanks();
if ctxt.cur_byte() == Some(b'/') {
ctxt.error = 1;
return;
}
if ctxt.cur_byte().is_none() {
ctxt.error = 1;
return;
}
}
if ctxt.cur_byte().is_some() {
ctxt.error = 1;
}
}
fn reverse_pattern(comp: &mut CompiledPattern) {
if !comp.steps.is_empty() && comp.steps[0].op == XML_OP_ANCESTOR {
comp.steps.remove(0);
}
comp.steps.reverse();
comp.steps.push(StepOp {
op: XML_OP_END,
value: None,
value2: None,
});
}
fn stream_comp_add_step(
comp: &mut StreamComp,
name: Option<Vec<u8>>,
ns: Option<Vec<u8>>,
node_type: c_int,
flags: c_int,
) -> c_int {
comp.steps.push(StreamStep {
flags,
name,
ns,
node_type,
});
comp.nb_step += 1;
(comp.nb_step - 1) as c_int
}
fn stream_compile(comp: &mut CompiledPattern) -> Result<Option<StreamComp>, ()> {
if comp.steps.len() == 1
&& comp.steps[0].op == XML_OP_ELEM
&& comp.steps[0].value.is_none()
&& comp.steps[0].value2.is_none()
{
return Ok(Some(StreamComp {
nb_step: 0,
steps: Vec::new(),
flags: XML_STREAM_FINAL_IS_ANY_NODE,
}));
}
let mut stream = StreamComp {
nb_step: 0,
steps: Vec::with_capacity(comp.steps.len() / 2 + 1),
flags: 0,
};
if comp.flags & PAT_FROM_ROOT != 0 {
stream.flags |= XML_STREAM_FROM_ROOT;
}
let mut s: c_int = 0;
let mut root = 0;
let mut flags = 0;
let mut prevs: c_int = -1;
let nb_step = comp.steps.len();
for (i, step) in comp.steps.iter().enumerate() {
match step.op {
XML_OP_END => break,
XML_OP_ROOT => {
if i != 0 {
return Ok(None);
}
root = 1;
}
XML_OP_NS => {
s = stream_comp_add_step(
&mut stream,
None,
step.value.clone(),
XML_ELEMENT_NODE,
flags,
);
if s < 0 {
return Err(());
}
prevs = s;
flags = 0;
}
XML_OP_ATTR => {
flags |= XML_STREAM_STEP_ATTR;
prevs = -1;
s = stream_comp_add_step(
&mut stream,
step.value.clone(),
step.value2.clone(),
XML_ATTRIBUTE_NODE,
flags,
);
flags = 0;
if s < 0 {
return Err(());
}
}
XML_OP_ELEM => {
if step.value.is_none() && step.value2.is_none() {
if nb_step == i + 1 && (flags & XML_STREAM_STEP_DESC) != 0 {
if nb_step == i + 1 {
stream.flags |= XML_STREAM_FINAL_IS_ANY_NODE;
}
flags |= XML_STREAM_STEP_NODE;
s = stream_comp_add_step(
&mut stream,
None,
None,
XML_STREAM_ANY_NODE,
flags,
);
if s < 0 {
return Err(());
}
flags = 0;
if prevs != -1 {
stream.steps[prevs as usize].flags |= XML_STREAM_STEP_IN_SET;
prevs = -1;
}
continue;
} else {
continue;
}
}
s = stream_comp_add_step(
&mut stream,
step.value.clone(),
step.value2.clone(),
XML_ELEMENT_NODE,
flags,
);
if s < 0 {
return Err(());
}
prevs = s;
flags = 0;
}
XML_OP_CHILD => {
s = stream_comp_add_step(
&mut stream,
step.value.clone(),
step.value2.clone(),
XML_ELEMENT_NODE,
flags,
);
if s < 0 {
return Err(());
}
prevs = s;
flags = 0;
}
XML_OP_ALL => {
s = stream_comp_add_step(&mut stream, None, None, XML_ELEMENT_NODE, flags);
if s < 0 {
return Err(());
}
prevs = s;
flags = 0;
}
XML_OP_PARENT => {}
XML_OP_ANCESTOR if (flags & XML_STREAM_STEP_DESC) == 0 => {
flags |= XML_STREAM_STEP_DESC;
if (stream.flags & XML_STREAM_DESC) == 0 {
stream.flags |= XML_STREAM_DESC;
}
}
_ => {}
}
}
if root == 0 && (comp.flags & XML_PATTERN_NOTPATTERN) == 0 {
if (stream.flags & XML_STREAM_DESC) == 0 {
stream.flags |= XML_STREAM_DESC;
}
if stream.nb_step > 0 && (stream.steps[0].flags & XML_STREAM_STEP_DESC) == 0 {
stream.steps[0].flags |= XML_STREAM_STEP_DESC;
}
}
if stream.nb_step <= s as usize {
return Ok(None);
}
stream.steps[s as usize].flags |= XML_STREAM_STEP_FINAL;
if root != 0 {
stream.steps[0].flags |= XML_STREAM_STEP_ROOT;
}
Ok(Some(stream))
}
unsafe fn pattern_compile_safe_impl(
pattern: *const xmlChar,
_dict: *mut c_void,
flags: c_int,
namespaces: *const *const xmlChar,
pattern_out: *mut xmlPatternPtr,
) -> c_int {
if pattern_out.is_null() {
return 1;
}
if pattern.is_null() {
unsafe { *pattern_out = ptr::null_mut() };
return 1;
}
let mut ns_pairs: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
if !namespaces.is_null() {
let mut i = 0usize;
loop {
let uri = unsafe { *namespaces.add(2 * i) };
if uri.is_null() {
break;
}
let prefix = unsafe { *namespaces.add(2 * i + 1) };
let uri = unsafe { core::slice::from_raw_parts(uri, cstr_len(uri)) }.to_vec();
let prefix = if prefix.is_null() {
Vec::new()
} else {
unsafe { core::slice::from_raw_parts(prefix, cstr_len(prefix)) }.to_vec()
};
ns_pairs.push((uri, prefix));
i += 1;
}
}
let pat_bytes = unsafe { core::slice::from_raw_parts(pattern, cstr_len(pattern)) };
if pat_bytes.is_empty() {
unsafe { *pattern_out = ptr::null_mut() };
return 0;
}
let mut branches: Vec<CompiledPattern> = Vec::new();
let mut error: c_int = 0;
let mut streamable = 1;
let mut pat_type: c_int = 0;
for segment in pat_bytes.split(|&b| b == b'|') {
let mut ctxt = PatCtxt {
cur: 0,
base: segment,
error: 0,
namespaces: ns_pairs.clone(),
};
let mut cur = CompiledPattern {
flags,
steps: Vec::new(),
stream: None,
};
if (cur.flags & (XML_PATTERN_XSSEL | XML_PATTERN_XSFIELD)) != 0 {
compile_idc_xpath_path(&mut ctxt, &mut cur);
} else {
compile_path_pattern(&mut ctxt, &mut cur);
}
if ctxt.error != 0 {
error = ctxt.error;
break;
}
if streamable != 0 {
let t = cur.flags & (PAT_FROM_ROOT | PAT_FROM_CUR);
if pat_type == 0 {
pat_type = t;
} else if pat_type == PAT_FROM_ROOT {
if t & PAT_FROM_CUR != 0 {
streamable = 0;
}
} else if pat_type == PAT_FROM_CUR && t & PAT_FROM_ROOT != 0 {
streamable = 0;
}
}
if streamable != 0 {
match stream_compile(&mut cur) {
Ok(stream) => cur.stream = stream,
Err(()) => {
error = -1;
break;
}
}
}
reverse_pattern(&mut cur);
branches.push(cur);
}
if error != 0 {
unsafe { *pattern_out = ptr::null_mut() };
return error;
}
if streamable == 0 {
for branch in &mut branches {
branch.stream = None;
}
}
let key = {
let mut next = NEXT_PATTERN_KEY.lock();
let k = *next;
*next += 1;
k
};
PATTERNS.lock().insert(key, PatternState { branches });
unsafe { *pattern_out = key as xmlPatternPtr };
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlPatterncompile(
pattern: *const xmlChar,
dict: *mut c_void,
flags: c_int,
namespaces: *const *const xmlChar,
) -> xmlPatternPtr {
let mut out: xmlPatternPtr = ptr::null_mut();
let ret = pattern_compile_safe_impl(pattern, dict, flags, namespaces, &mut out);
if ret != 0 {
return ptr::null_mut();
}
out
}
#[no_mangle]
pub unsafe extern "C" fn xmlPatternCompileSafe(
pattern: *const xmlChar,
dict: *mut c_void,
flags: c_int,
namespaces: *const *const xmlChar,
patternOut: *mut xmlPatternPtr,
) -> c_int {
pattern_compile_safe_impl(pattern, dict, flags, namespaces, patternOut)
}
#[no_mangle]
pub unsafe extern "C" fn xmlPatternMatch(comp: xmlPatternPtr, node: *mut _xmlNode) -> c_int {
if comp.is_null() || node.is_null() {
return -1;
}
let key = comp as usize;
let reg = PATTERNS.lock();
let Some(ps) = reg.get(&key) else {
return -1;
};
for branch in &ps.branches {
let ret = pat_match(branch, node);
if ret != 0 {
return ret;
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlFreePattern(comp: xmlPatternPtr) {
xmlFreePatternList(comp);
}
#[no_mangle]
pub unsafe extern "C" fn xmlFreePatternList(comp: xmlPatternPtr) {
if comp.is_null() {
return;
}
PATTERNS.lock().remove(&(comp as usize));
}
#[no_mangle]
pub unsafe extern "C" fn xmlPatternStreamable(comp: xmlPatternPtr) -> c_int {
if comp.is_null() {
return -1;
}
let reg = PATTERNS.lock();
let Some(ps) = reg.get(&(comp as usize)) else {
return -1;
};
if ps.branches.iter().any(|b| b.stream.is_none()) {
return 0;
}
1
}
#[no_mangle]
pub unsafe extern "C" fn xmlPatternMaxDepth(comp: xmlPatternPtr) -> c_int {
if comp.is_null() {
return -1;
}
let reg = PATTERNS.lock();
let Some(ps) = reg.get(&(comp as usize)) else {
return -1;
};
let mut ret = 0;
for branch in &ps.branches {
let Some(stream) = &branch.stream else {
return -1;
};
for step in &stream.steps {
if step.flags & XML_STREAM_STEP_DESC != 0 {
return -2;
}
}
if stream.nb_step > ret as usize {
ret = stream.nb_step as c_int;
}
}
ret
}
#[no_mangle]
pub unsafe extern "C" fn xmlPatternMinDepth(comp: xmlPatternPtr) -> c_int {
if comp.is_null() {
return -1;
}
let reg = PATTERNS.lock();
let Some(ps) = reg.get(&(comp as usize)) else {
return -1;
};
let mut ret: c_int = 12345678;
for branch in &ps.branches {
let Some(stream) = &branch.stream else {
return -1;
};
if stream.nb_step < ret as usize {
ret = stream.nb_step as c_int;
}
if ret == 0 {
return 0;
}
}
ret
}
#[no_mangle]
pub unsafe extern "C" fn xmlPatternFromRoot(comp: xmlPatternPtr) -> c_int {
if comp.is_null() {
return -1;
}
let reg = PATTERNS.lock();
let Some(ps) = reg.get(&(comp as usize)) else {
return -1;
};
for branch in &ps.branches {
if branch.stream.is_none() {
return -1;
}
if branch.flags & PAT_FROM_ROOT != 0 {
return 1;
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlPatternGetStreamCtxt(comp: xmlPatternPtr) -> xmlStreamCtxtPtr {
if comp.is_null() {
return ptr::null_mut();
}
let key = comp as usize;
let reg = PATTERNS.lock();
let Some(ps) = reg.get(&key) else {
return ptr::null_mut();
};
let mut snapshot: Vec<(StreamComp, c_int)> = Vec::new();
for branch in &ps.branches {
match &branch.stream {
Some(stream) => snapshot.push((stream.clone(), branch.flags)),
None => return ptr::null_mut(),
}
}
drop(reg);
let mut head: Option<usize> = None;
let mut last: Option<usize> = None;
{
let mut stream_reg = STREAM_CTXTS.lock();
for (stream, flags) in snapshot {
let h = {
let mut next = NEXT_STREAM_KEY.lock();
let k = *next;
*next += 1;
k
};
stream_reg.insert(
h,
StreamCtxtState {
comp: stream,
next: None,
nb_state: 0,
states: Vec::new(),
level: 0,
flags,
block_level: -1,
},
);
match last {
None => head = Some(h),
Some(prev) => {
if let Some(prev_st) = stream_reg.get_mut(&prev) {
prev_st.next = Some(h);
}
}
}
last = Some(h);
}
}
match head {
Some(h) => h as xmlStreamCtxtPtr,
None => ptr::null_mut(),
}
}
fn stream_ctxt_free_chain(head: Option<usize>) {
let mut cur = head;
let mut registry = STREAM_CTXTS.lock();
while let Some(h) = cur {
let next = registry.get(&h).and_then(|s| s.next);
registry.remove(&h);
cur = next;
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlFreeStreamCtxt(stream: xmlStreamCtxtPtr) {
if stream.is_null() {
return;
}
stream_ctxt_free_chain(Some(stream as usize));
}
fn stream_ctxt_add_state(st: &mut StreamCtxtState, idx: i32, level: i32) -> c_int {
for i in 0..st.nb_state {
if st.states[i].0 < 0 {
st.states[i] = (idx, level);
return i as c_int;
}
}
st.states.push((idx, level));
st.nb_state += 1;
(st.nb_state - 1) as c_int
}
unsafe fn stream_push_internal(
head: usize,
name: *const xmlChar,
ns: *const xmlChar,
node_type: c_int,
) -> c_int {
let mut ret = 0;
let mut registry = STREAM_CTXTS.lock();
let mut cur = Some(head);
while let Some(h) = cur {
let next = registry.get(&h).and_then(|s| s.next);
let Some(st) = registry.get_mut(&h) else {
return -1;
};
if node_type == XML_ELEMENT_NODE && name.is_null() && ns.is_null() {
st.nb_state = 0;
st.level = 0;
st.block_level = -1;
if st.comp.flags & XML_STREAM_FROM_ROOT != 0 {
if st.comp.nb_step == 0
|| (st.comp.nb_step == 1
&& st.comp.steps[0].node_type == XML_STREAM_ANY_NODE
&& st.comp.steps[0].flags & XML_STREAM_STEP_DESC != 0)
{
ret = 1;
} else if st.comp.steps[0].flags & XML_STREAM_STEP_ROOT != 0
&& stream_ctxt_add_state(st, 0, 0) < 0
{
return -1;
}
}
cur = next;
continue;
}
if st.comp.nb_step == 0 {
if st.flags & XML_PATTERN_XPATH != 0 {
cur = next;
continue;
}
if node_type != XML_ATTRIBUTE_NODE
&& ((st.flags & XML_PATTERN_NOTPATTERN) == 0 || st.level == 0)
{
ret = 1;
}
st.level += 1;
cur = next;
continue;
}
if st.block_level != -1 {
st.level += 1;
cur = next;
continue;
}
if node_type != XML_ELEMENT_NODE
&& node_type != XML_ATTRIBUTE_NODE
&& (st.comp.flags & XML_STREAM_FINAL_IS_ANY_NODE) == 0
{
st.level += 1;
cur = next;
continue;
}
let mut i = 0usize;
let m = st.nb_state;
let mut final_: c_int = 0;
while i < m {
let step_nr: i32;
if (st.comp.flags & XML_STREAM_DESC) == 0 {
step_nr = st.states[st.nb_state - 1].0;
if st.states[st.nb_state - 1].1 < st.level {
return -1;
}
i = m; } else {
step_nr = st.states[i].0;
if step_nr < 0 {
i += 1;
continue;
}
let tmp = st.states[i].1;
if tmp > st.level {
i += 1;
continue;
}
if tmp < st.level
&& (st.comp.steps[step_nr as usize].flags & XML_STREAM_STEP_DESC) == 0
{
i += 1;
continue;
}
}
let Some(step) = st.comp.steps.get(step_nr as usize).cloned() else {
i += 1;
continue;
};
if step.node_type != node_type {
if step.node_type == XML_ATTRIBUTE_NODE {
if (st.comp.flags & XML_STREAM_DESC) == 0 {
st.block_level = st.level + 1;
}
i += 1;
continue;
} else if step.node_type != XML_STREAM_ANY_NODE {
i += 1;
continue;
}
}
let mut match_ = false;
if step.node_type == XML_STREAM_ANY_NODE {
match_ = true;
} else if step.name.is_none() {
if step.ns.is_none() {
match_ = true;
} else if !ns.is_null() {
match_ = cstr_eq_opt(step.ns.as_deref(), ns);
}
} else if (step.ns.is_some() == !ns.is_null())
&& !name.is_null()
&& step.name.as_ref().unwrap()[0] == *name
&& cstr_eq_opt(step.name.as_deref(), name)
&& (step.ns.is_none() || cstr_eq_opt(step.ns.as_deref(), ns))
{
match_ = true;
}
if match_ {
final_ = step.flags & XML_STREAM_STEP_FINAL;
if final_ != 0 {
ret = 1;
} else if stream_ctxt_add_state(st, step_nr + 1, st.level + 1) < 0 {
return -1;
}
if ret != 1 && step.flags & XML_STREAM_STEP_IN_SET != 0 {
ret = 1;
}
}
if (st.comp.flags & XML_STREAM_DESC) == 0 && (!match_ || final_ != 0) {
st.block_level = st.level + 1;
}
i += 1;
}
st.level += 1;
let step0 = st.comp.steps[0].clone();
if step0.flags & XML_STREAM_STEP_ROOT != 0 {
cur = next;
continue;
}
let desc = step0.flags & XML_STREAM_STEP_DESC != 0;
let do_compare = if st.flags & XML_PATTERN_NOTPATTERN != 0 {
if st.level == 1 {
if st.flags & (XML_PATTERN_XSSEL | XML_PATTERN_XSFIELD) != 0 {
cur = next;
continue;
}
true
} else if desc
|| (st.level == 2 && st.flags & (XML_PATTERN_XSSEL | XML_PATTERN_XSFIELD) != 0)
{
true
} else {
cur = next;
continue;
}
} else {
true
};
if do_compare {
if step0.node_type != node_type
&& (node_type == XML_ATTRIBUTE_NODE || step0.node_type != XML_STREAM_ANY_NODE)
{
cur = next;
continue;
}
let mut match_ = false;
if step0.node_type == XML_STREAM_ANY_NODE {
match_ = true;
} else if step0.name.is_none() {
if step0.ns.is_none() {
match_ = true;
} else if !ns.is_null() {
match_ = cstr_eq_opt(step0.ns.as_deref(), ns);
}
} else if (step0.ns.is_some() == !ns.is_null())
&& !name.is_null()
&& step0.name.as_ref().unwrap()[0] == *name
&& cstr_eq_opt(step0.name.as_deref(), name)
&& (step0.ns.is_none() || cstr_eq_opt(step0.ns.as_deref(), ns))
{
match_ = true;
}
final_ = step0.flags & XML_STREAM_STEP_FINAL;
if match_ {
if final_ != 0 {
ret = 1;
} else if stream_ctxt_add_state(st, 1, st.level) < 0 {
return -1;
}
if ret != 1 && step0.flags & XML_STREAM_STEP_IN_SET != 0 {
ret = 1;
}
}
if (st.comp.flags & XML_STREAM_DESC) == 0 && (!match_ || final_ != 0) {
st.block_level = st.level;
}
}
cur = next;
}
ret
}
#[no_mangle]
pub unsafe extern "C" fn xmlStreamPush(
stream: xmlStreamCtxtPtr,
name: *const xmlChar,
ns: *const xmlChar,
) -> c_int {
if stream.is_null() {
return -1;
}
stream_push_internal(stream as usize, name, ns, XML_ELEMENT_NODE)
}
#[no_mangle]
pub unsafe extern "C" fn xmlStreamPushAttr(
stream: xmlStreamCtxtPtr,
name: *const xmlChar,
ns: *const xmlChar,
) -> c_int {
if stream.is_null() {
return -1;
}
stream_push_internal(stream as usize, name, ns, XML_ATTRIBUTE_NODE)
}
#[no_mangle]
pub unsafe extern "C" fn xmlStreamPushNode(
stream: xmlStreamCtxtPtr,
name: *const xmlChar,
ns: *const xmlChar,
nodeType: c_int,
) -> c_int {
if stream.is_null() {
return -1;
}
stream_push_internal(stream as usize, name, ns, nodeType)
}
#[no_mangle]
pub unsafe extern "C" fn xmlStreamPop(stream: xmlStreamCtxtPtr) -> c_int {
if stream.is_null() {
return -1;
}
let mut registry = STREAM_CTXTS.lock();
let mut cur = Some(stream as usize);
while let Some(h) = cur {
let next = registry.get(&h).and_then(|s| s.next);
let Some(st) = registry.get_mut(&h) else {
return -1;
};
if st.block_level == st.level {
st.block_level = -1;
}
if st.level > 0 {
st.level -= 1;
}
let mut i = st.nb_state as isize - 1;
while i >= 0 {
let lev = st.states[i as usize].1;
if lev > st.level {
st.nb_state -= 1;
}
if lev <= st.level {
break;
}
i -= 1;
}
cur = next;
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlStreamWantsAnyNode(streamCtxt: xmlStreamCtxtPtr) -> c_int {
if streamCtxt.is_null() {
return -1;
}
let registry = STREAM_CTXTS.lock();
let mut cur = Some(streamCtxt as usize);
while let Some(h) = cur {
let Some(st) = registry.get(&h) else {
return -1;
};
if st.comp.flags & XML_STREAM_FINAL_IS_ANY_NODE != 0 {
return 1;
}
cur = st.next;
}
0
}