use std::{
borrow::Cow,
ptr::{drop_in_place, null_mut},
};
use crate::{
chvalid::XmlCharValid,
globals::{GLOBAL_STATE, GenericError, GenericErrorContext, StructuredError},
libxml::{
globals::{xml_free, xml_malloc},
relaxng::{
XmlRelaxNGGrammarPtr, XmlRelaxNGPtr, XmlRelaxNGValidErr, XmlRelaxNGValidError,
xml_relaxng_add_states_uniq, xml_relaxng_validate_progressive_callback,
},
xmlregexp::XmlRegExecCtxt,
},
relaxng::{VALID_ERR, VALID_ERR2},
tree::{NodeCommon, XmlAttrPtr, XmlDocPtr, XmlElementType, XmlGenericNodePtr, XmlNodePtr},
};
use super::{XmlRelaxNGDefinePtr, xml_rng_verr_memory};
const MAX_ATTR: usize = 20;
pub type XmlRelaxNGStatesPtr = *mut XmlRelaxNGStates;
#[doc(alias = "xmlRelaxNGStates")]
#[derive(Default)]
#[repr(C)]
pub struct XmlRelaxNGStates {
pub(crate) tab_state: Vec<XmlRelaxNGValidStatePtr>,
}
pub type XmlRelaxNGValidStatePtr = *mut XmlRelaxNGValidState;
#[doc(alias = "xmlRelaxNGValidState")]
#[repr(C)]
pub struct XmlRelaxNGValidState {
pub(crate) node: Option<XmlGenericNodePtr>, pub(crate) seq: Option<XmlGenericNodePtr>, pub(crate) nb_attr_left: i32, pub(crate) value: *mut u8, pub(crate) endvalue: *mut u8, pub(crate) attrs: Vec<Option<XmlAttrPtr>>, }
impl Default for XmlRelaxNGValidState {
fn default() -> Self {
Self {
node: None,
seq: None,
nb_attr_left: 0,
value: null_mut(),
endvalue: null_mut(),
attrs: vec![],
}
}
}
pub type XmlRelaxNGValidCtxtPtr = *mut XmlRelaxNGValidCtxt;
#[doc(alias = "xmlRelaxNGValidCtxt")]
#[repr(C)]
pub struct XmlRelaxNGValidCtxt {
pub(crate) user_data: Option<GenericErrorContext>, pub(crate) error: Option<GenericError>, pub(crate) warning: Option<GenericError>, pub(crate) serror: Option<StructuredError>,
pub(crate) nb_errors: i32,
pub(crate) schema: XmlRelaxNGPtr, pub(crate) doc: Option<XmlDocPtr>, pub(crate) flags: i32, pub(crate) depth: i32, pub(crate) idref: i32, pub(crate) err_no: i32,
pub(crate) err_tab: Vec<XmlRelaxNGValidError>,
pub(crate) state: XmlRelaxNGValidStatePtr, pub(crate) states: XmlRelaxNGStatesPtr,
pub(crate) free_state: XmlRelaxNGStatesPtr, free_states: Vec<XmlRelaxNGStatesPtr>,
pub(crate) elem_tab: Vec<XmlRegExecCtxt>, pub(crate) pstate: i32, pub(crate) pnode: Option<XmlNodePtr>, pub(crate) pdef: XmlRelaxNGDefinePtr, pub(crate) perr: i32, }
impl XmlRelaxNGValidCtxt {
pub(crate) fn elem(&self) -> Option<&XmlRegExecCtxt> {
self.elem_tab.last()
}
pub(crate) fn elem_mut(&mut self) -> Option<&mut XmlRegExecCtxt> {
self.elem_tab.last_mut()
}
#[doc(alias = "xmlRelaxNGElemPush")]
pub(crate) fn elem_push(&mut self, exec: XmlRegExecCtxt) -> i32 {
self.elem_tab.push(exec);
0
}
#[doc(alias = "xmlRelaxNGElemPop")]
pub(crate) fn elem_pop(&mut self) -> Option<XmlRegExecCtxt> {
self.elem_tab.pop()
}
#[doc(alias = "xmlRelaxNGValidatePushElement")]
pub unsafe fn push_element(&mut self, _doc: Option<XmlDocPtr>, elem: XmlNodePtr) -> i32 {
unsafe {
let mut ret: i32;
if self.elem().is_none() {
let schema: XmlRelaxNGPtr = self.schema;
if schema.is_null() {
VALID_ERR!(self, XmlRelaxNGValidErr::XmlRelaxngErrNogrammar);
return -1;
}
let grammar: XmlRelaxNGGrammarPtr = (*schema).topgrammar;
if grammar.is_null() || (*grammar).start.is_null() {
VALID_ERR!(self, XmlRelaxNGValidErr::XmlRelaxngErrNogrammar);
return -1;
}
let define: XmlRelaxNGDefinePtr = (*grammar).start;
let Some(cont_model) = (*define).cont_model.clone() else {
self.pdef = define;
return 0;
};
let exec = XmlRegExecCtxt::new(
cont_model,
Some(xml_relaxng_validate_progressive_callback),
self as *mut Self as _,
);
self.elem_push(exec);
}
self.pnode = Some(elem);
self.pstate = 0;
if let Some(ns) = elem.ns {
let data = self as *mut Self as _;
ret = self.elem_mut().unwrap().push_string2(
elem.name().as_deref().unwrap(),
ns.href().as_deref(),
data,
);
} else {
let data = self as *mut Self as _;
ret = self
.elem_mut()
.unwrap()
.push_string(elem.name().as_deref(), data);
}
if ret < 0 {
VALID_ERR2!(
self,
XmlRelaxNGValidErr::XmlRelaxngErrElemwrong,
elem.name().as_deref()
);
} else if self.pstate == 0 {
ret = 0;
} else if self.pstate < 0 {
ret = -1;
} else {
ret = 1;
}
ret
}
}
#[doc(alias = "xmlRelaxNGValidatePopElement")]
pub unsafe fn pop_element(&mut self, _doc: Option<XmlDocPtr>, _elem: XmlNodePtr) -> i32 {
unsafe {
let mut ret: i32;
let Some(mut exec) = self.elem_pop() else {
return -1;
};
ret = exec.push_string(None, null_mut());
match ret.cmp(&0) {
std::cmp::Ordering::Equal => {
VALID_ERR2!(self, XmlRelaxNGValidErr::XmlRelaxngErrNoelem, Some(""));
ret = -1;
}
std::cmp::Ordering::Less => {
ret = -1;
}
std::cmp::Ordering::Greater => {
ret = 1;
}
}
ret
}
}
}
impl Default for XmlRelaxNGValidCtxt {
fn default() -> Self {
Self {
user_data: None,
error: None,
warning: None,
serror: None,
nb_errors: 0,
schema: null_mut(),
doc: None,
flags: 0,
depth: 0,
idref: 0,
err_no: 0,
err_tab: vec![],
state: null_mut(),
states: null_mut(),
free_state: null_mut(),
free_states: vec![],
elem_tab: vec![],
pstate: 0,
pnode: None,
pdef: null_mut(),
perr: 0,
}
}
}
#[doc(alias = "xmlRelaxNGNewValidCtxt")]
pub unsafe fn xml_relaxng_new_valid_ctxt(schema: XmlRelaxNGPtr) -> XmlRelaxNGValidCtxtPtr {
unsafe {
let ret: XmlRelaxNGValidCtxtPtr = xml_malloc(size_of::<XmlRelaxNGValidCtxt>()) as _;
if ret.is_null() {
xml_rng_verr_memory(null_mut(), "building context\n");
return null_mut();
}
std::ptr::write(&mut *ret, XmlRelaxNGValidCtxt::default());
(*ret).schema = schema;
GLOBAL_STATE.with_borrow(|state| {
(*ret).error = Some(state.generic_error);
(*ret).user_data = state.generic_error_context.clone();
});
if !schema.is_null() {
(*ret).idref = (*schema).idref;
}
(*ret).states = null_mut();
(*ret).free_state = null_mut();
(*ret).err_no = XmlRelaxNGValidErr::XmlRelaxngOk as i32;
ret
}
}
#[doc(alias = "xmlRelaxNGFreeValidCtxt")]
pub unsafe fn xml_relaxng_free_valid_ctxt(ctxt: XmlRelaxNGValidCtxtPtr) {
unsafe {
if ctxt.is_null() {
return;
}
if !(*ctxt).states.is_null() {
xml_relaxng_free_states(null_mut(), (*ctxt).states);
}
if !(*ctxt).free_state.is_null() {
for state in (*(*ctxt).free_state).tab_state.drain(..) {
xml_relaxng_free_valid_state(null_mut(), state);
}
xml_relaxng_free_states(null_mut(), (*ctxt).free_state);
}
for state in (*ctxt).free_states.drain(..) {
xml_relaxng_free_states(null_mut(), state);
}
drop_in_place(ctxt);
xml_free(ctxt as _);
}
}
#[doc(alias = "xmlRelaxNGNewStates")]
pub(crate) unsafe fn xml_relaxng_new_states(
ctxt: XmlRelaxNGValidCtxtPtr,
mut size: i32,
) -> XmlRelaxNGStatesPtr {
unsafe {
if !ctxt.is_null() {
if let Some(ret) = (*ctxt).free_states.pop() {
(*ret).tab_state.clear();
return ret;
}
}
if size < 16 {
size = 16;
}
let ret: XmlRelaxNGStatesPtr = xml_malloc(
size_of::<XmlRelaxNGStates>()
+ (size as usize - 1) * size_of::<XmlRelaxNGValidStatePtr>(),
) as _;
if ret.is_null() {
xml_rng_verr_memory(ctxt, "allocating states\n");
return null_mut();
}
std::ptr::write(&mut *ret, XmlRelaxNGStates::default());
(*ret).tab_state.reserve(size as usize);
ret
}
}
#[doc(alias = "xmlRelaxNGFreeStates")]
pub(crate) unsafe fn xml_relaxng_free_states(
ctxt: XmlRelaxNGValidCtxtPtr,
states: XmlRelaxNGStatesPtr,
) {
unsafe {
if states.is_null() {
return;
}
if ctxt.is_null() {
drop_in_place(states);
xml_free(states as _);
} else {
(*ctxt).free_states.push(states);
}
}
}
#[doc(alias = "xmlRelaxNGNewValidState")]
pub(crate) unsafe fn xml_relaxng_new_valid_state(
ctxt: XmlRelaxNGValidCtxtPtr,
node: Option<XmlGenericNodePtr>,
) -> XmlRelaxNGValidStatePtr {
unsafe {
let ret: XmlRelaxNGValidStatePtr;
let mut attrs: [Option<XmlAttrPtr>; MAX_ATTR] = [None; MAX_ATTR];
let mut nb_attrs: usize = 0;
let mut root = None;
if let Some(node) = node {
if node.element_type() != XmlElementType::XmlDocumentNode {
let node = XmlNodePtr::try_from(node).unwrap();
let mut attr = node.properties;
while let Some(now) = attr {
if nb_attrs < MAX_ATTR {
attrs[nb_attrs] = Some(now);
nb_attrs += 1;
} else {
nb_attrs += 1;
}
attr = now.next;
}
}
} else {
root = (*ctxt).doc.and_then(|doc| doc.get_root_element());
if root.is_none() {
return null_mut();
}
}
if !(*ctxt).free_state.is_null() && !(*(*ctxt).free_state).tab_state.is_empty() {
ret = (*(*ctxt).free_state).tab_state.pop().unwrap();
} else {
ret = xml_malloc(size_of::<XmlRelaxNGValidState>()) as _;
if ret.is_null() {
xml_rng_verr_memory(ctxt, "allocating states\n");
return null_mut();
}
std::ptr::write(&mut *ret, XmlRelaxNGValidState::default());
}
(*ret).value = null_mut();
(*ret).endvalue = null_mut();
if let Some(node) = node {
(*ret).node = Some(node);
(*ret).seq = node.children();
} else {
(*ret).node = (*ctxt).doc.map(|doc| doc.into());
(*ret).seq = root.map(|root| root.into());
}
(*ret).attrs.clear();
if nb_attrs > 0 {
if nb_attrs < MAX_ATTR {
(*ret).attrs.extend(attrs.iter().copied().take(nb_attrs));
} else {
let node = XmlNodePtr::try_from(node.unwrap()).unwrap();
let mut attr = node.properties;
while let Some(now) = attr {
(*ret).attrs.push(Some(now));
attr = now.next;
}
}
}
(*ret).nb_attr_left = (*ret).attrs.len() as i32;
ret
}
}
#[doc(alias = "xmlRelaxNGFreeValidState")]
pub(crate) unsafe fn xml_relaxng_free_valid_state(
ctxt: XmlRelaxNGValidCtxtPtr,
state: XmlRelaxNGValidStatePtr,
) {
unsafe {
if state.is_null() {
return;
}
if !ctxt.is_null() && (*ctxt).free_state.is_null() {
(*ctxt).free_state = xml_relaxng_new_states(ctxt, 40);
}
if ctxt.is_null() || (*ctxt).free_state.is_null() {
drop_in_place(state);
xml_free(state as _);
} else {
xml_relaxng_add_states_uniq(ctxt, (*ctxt).free_state, state);
}
}
}
#[doc(alias = "xmlRelaxNGNormalize")]
pub(crate) fn relaxng_normalize(mut s: &str) -> Cow<'_, str> {
s = s.trim_start_matches(|c: char| c.is_xml_blank_char());
s = s.trim_end_matches(|c: char| c.is_xml_blank_char());
let mut chars = s.chars().peekable();
let mut pending = 0;
let mut buf = None::<String>;
while let Some(c) = chars.next() {
if c.is_xml_blank_char() {
if chars.next_if(|c| c.is_xml_blank_char()).is_some() {
while chars.next_if(|c| c.is_xml_blank_char()).is_some() {}
let buf = buf.get_or_insert_with(String::new);
if pending > 0 {
buf.push_str(&s[..pending]);
pending = 0;
}
buf.push(' ');
} else if let Some(buf) = buf.as_mut() {
buf.push(c);
} else {
pending += c.len_utf8();
}
} else if let Some(buf) = buf.as_mut() {
buf.push(c);
} else {
pending += c.len_utf8();
}
}
buf.map(Cow::Owned).unwrap_or(Cow::Borrowed(s))
}