#![allow(
missing_docs,
non_snake_case,
non_camel_case_types,
non_upper_case_globals
)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
#![allow(missing_debug_implementations)]
use core::ffi::c_void;
use core::ptr;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_ulong};
use crate::abi::allocator::{xmlFreeImpl, xmlMemStrdupImpl};
use crate::abi::callbacks::{xmlStructuredErrorFunc, xmlValidityErrorFunc, xmlValidityWarningFunc};
use crate::abi::structs::{_xmlDoc, _xmlError, _xmlNode, _xmlParserInputBuffer, _xmlSAXHandler};
use crate::abi::types::{xmlChar, xmlCharEncoding, xmlErrorLevel, XML_FROM_SCHEMASV};
use crate::xml::schemas::{
xsd_parse, xsd_validate, xsd_validate_datatype, xsd_validate_facet, XsdDatatypeKind, XsdSchema,
XsdValidCtxt,
};
use crate::xml::schematron::schematron_parse;
pub enum xmlSchema {}
pub enum xmlSchemaParserCtxt {}
pub enum xmlSchemaValidCtxt {}
pub enum xmlSchemaVal {}
pub enum xmlSchemaFacet {}
pub enum xmlSchemaType {}
pub enum xmlSchemaWildcard {}
pub enum xmlSchemaSAXPlugStruct {}
pub enum xmlSchematronParserCtxt {}
pub enum xmlSchematronValidCtxt {}
pub type xmlSchemaValidityLocatorFunc =
unsafe extern "C" fn(ctx: *mut c_void, file: *mut *const c_char, line: *mut c_ulong) -> c_int;
#[repr(C)]
struct XsdVal {
val_type: c_int, value: *mut xmlChar, ns: *mut xmlChar, next: *mut XsdVal, }
#[repr(C)]
struct XsdFacet {
facet_type: c_int, value: *mut xmlChar,
next: *mut XsdFacet,
}
#[repr(C)]
struct XsdType {
val_type: c_int, item_type: *mut XsdType, name: *const c_char, }
#[derive(Default, Clone, Copy)]
struct ParserState {
err: Option<xmlValidityErrorFunc>,
warn: Option<xmlValidityWarningFunc>,
ctx: usize,
serror: Option<xmlStructuredErrorFunc>,
sctx: usize,
resource_loader: Option<crate::abi::callbacks::xmlResourceLoader>,
resource_ctxt: usize,
}
#[derive(Default, Clone, Copy)]
struct ValidState {
err: Option<xmlValidityErrorFunc>,
warn: Option<xmlValidityWarningFunc>,
ctx: usize,
serror: Option<xmlStructuredErrorFunc>,
sctx: usize,
options: c_int,
filename: usize, locator: Option<xmlSchemaValidityLocatorFunc>,
locator_ctx: usize,
}
#[derive(Default, Clone, Copy)]
struct SchematronValidState {
serror: Option<xmlStructuredErrorFunc>,
sctx: usize,
}
#[repr(C)]
struct XsdSaxPlug {
sax: *const _xmlSAXHandler,
user_data: *mut c_void,
}
static PARSER_STATES: Lazy<Mutex<HashMap<usize, ParserState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static VALID_STATES: Lazy<Mutex<HashMap<usize, ValidState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static SCHEMATRON_VALID_STATES: Lazy<Mutex<HashMap<usize, SchematronValidState>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static TYPE_REGISTRY: Lazy<Mutex<HashMap<c_int, usize>>> = Lazy::new(|| Mutex::new(HashMap::new()));
const VAL_UNKNOWN: c_int = 0;
const VAL_STRING: c_int = 1;
const VAL_NORMSTRING: c_int = 2;
const VAL_DECIMAL: c_int = 3;
const VAL_TIME: c_int = 4;
const VAL_GDAY: c_int = 5;
const VAL_GMONTH: c_int = 6;
const VAL_GMONTHDAY: c_int = 7;
const VAL_GYEAR: c_int = 8;
const VAL_GYEARMONTH: c_int = 9;
const VAL_DATE: c_int = 10;
const VAL_DATETIME: c_int = 11;
const VAL_DURATION: c_int = 12;
const VAL_FLOAT: c_int = 13;
const VAL_DOUBLE: c_int = 14;
const VAL_BOOLEAN: c_int = 15;
const VAL_TOKEN: c_int = 16;
const VAL_LANGUAGE: c_int = 17;
const VAL_NMTOKEN: c_int = 18;
const VAL_NMTOKENS: c_int = 19;
const VAL_NAME: c_int = 20;
const VAL_QNAME: c_int = 21;
const VAL_NCNAME: c_int = 22;
const VAL_ID: c_int = 23;
const VAL_IDREF: c_int = 24;
const VAL_IDREFS: c_int = 25;
const VAL_ENTITY: c_int = 26;
const VAL_ENTITIES: c_int = 27;
const VAL_NOTATION: c_int = 28;
const VAL_ANYURI: c_int = 29;
const VAL_INTEGER: c_int = 30;
const VAL_NPINTEGER: c_int = 31;
const VAL_NINTEGER: c_int = 32;
const VAL_NNINTEGER: c_int = 33;
const VAL_PINTEGER: c_int = 34;
const VAL_INT: c_int = 35;
const VAL_UINT: c_int = 36;
const VAL_LONG: c_int = 37;
const VAL_ULONG: c_int = 38;
const VAL_SHORT: c_int = 39;
const VAL_USHORT: c_int = 40;
const VAL_BYTE: c_int = 41;
const VAL_UBYTE: c_int = 42;
const VAL_HEXBINARY: c_int = 43;
const VAL_BASE64BINARY: c_int = 44;
const VAL_ANYTYPE: c_int = 45;
const VAL_ANYSIMPLETYPE: c_int = 46;
const FACET_MININCLUSIVE: c_int = 1000;
const FACET_MINEXCLUSIVE: c_int = 1001;
const FACET_MAXINCLUSIVE: c_int = 1002;
const FACET_MAXEXCLUSIVE: c_int = 1003;
const FACET_TOTALDIGITS: c_int = 1004;
const FACET_FRACTIONDIGITS: c_int = 1005;
const FACET_PATTERN: c_int = 1006;
const FACET_ENUMERATION: c_int = 1007;
const FACET_WHITESPACE: c_int = 1008;
const FACET_LENGTH: c_int = 1009;
const FACET_MAXLENGTH: c_int = 1010;
const FACET_MINLENGTH: c_int = 1011;
const WS_PRESERVE: c_int = 1;
const WS_REPLACE: c_int = 2;
const WS_COLLAPSE: c_int = 3;
const VAL_VC_I_CREATE: c_int = 1 << 0;
const VAL_XSI_ASSEMBLE: c_int = 1 << 1;
const VAL_OPTIONS_MASK: c_int = VAL_VC_I_CREATE | VAL_XSI_ASSEMBLE;
const XSD_NS: &[u8] = b"http://www.w3.org/2001/XMLSchema";
unsafe fn cstr_to_str(s: *const c_char) -> Option<String> {
if s.is_null() {
return None;
}
let c = unsafe { CStr::from_ptr(s) };
Some(String::from_utf8_lossy(c.to_bytes()).to_string())
}
const unsafe fn cstr_eq(s: *const c_char, bytes: &[u8]) -> bool {
if s.is_null() {
return false;
}
unsafe {
let mut i = 0usize;
while i < bytes.len() {
if *s.add(i) as u8 != bytes[i] {
return false;
}
i += 1;
}
*s.add(i) == 0
}
}
unsafe fn dup_cstr(s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
unsafe { xmlMemStrdupImpl(s) as *mut c_char }
}
const fn is_whitespace(c: char) -> bool {
c == ' ' || c == '\t' || c == '\n' || c == '\r'
}
fn whitespace_replace(s: &str) -> String {
s.chars()
.map(|c| if is_whitespace(c) { ' ' } else { c })
.collect()
}
fn whitespace_collapse(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut in_run = false;
for c in s.chars() {
if is_whitespace(c) {
in_run = true;
} else {
if in_run && !out.is_empty() {
out.push(' ');
}
in_run = false;
out.push(c);
}
}
out
}
fn apply_ws(s: &str, ws: c_int) -> String {
match ws {
WS_REPLACE => whitespace_replace(s),
WS_COLLAPSE => whitespace_collapse(s),
_ => s.to_string(),
}
}
const fn val_type_to_kind(val_type: c_int) -> Option<XsdDatatypeKind> {
Some(match val_type {
VAL_STRING | VAL_ANYTYPE | VAL_ANYSIMPLETYPE => XsdDatatypeKind::String,
VAL_NORMSTRING => XsdDatatypeKind::NormalizedString,
VAL_DECIMAL => XsdDatatypeKind::Decimal,
VAL_TIME => XsdDatatypeKind::Time,
VAL_GDAY => XsdDatatypeKind::GDay,
VAL_GMONTH => XsdDatatypeKind::GMonth,
VAL_GMONTHDAY => XsdDatatypeKind::GMonthDay,
VAL_GYEAR => XsdDatatypeKind::GYear,
VAL_GYEARMONTH => XsdDatatypeKind::GYearMonth,
VAL_DATE => XsdDatatypeKind::Date,
VAL_DATETIME => XsdDatatypeKind::DateTime,
VAL_DURATION => XsdDatatypeKind::Duration,
VAL_FLOAT => XsdDatatypeKind::Float,
VAL_DOUBLE => XsdDatatypeKind::Double,
VAL_BOOLEAN => XsdDatatypeKind::Boolean,
VAL_TOKEN => XsdDatatypeKind::Token,
VAL_LANGUAGE => XsdDatatypeKind::Language,
VAL_NMTOKEN => XsdDatatypeKind::Nmtoken,
VAL_NMTOKENS => XsdDatatypeKind::Nmtokens,
VAL_NAME => XsdDatatypeKind::Name,
VAL_QNAME => XsdDatatypeKind::QName,
VAL_NCNAME => XsdDatatypeKind::NCName,
VAL_ID => XsdDatatypeKind::Id,
VAL_IDREF => XsdDatatypeKind::Idref,
VAL_IDREFS => XsdDatatypeKind::Idrefs,
VAL_ENTITY => XsdDatatypeKind::Entity,
VAL_ENTITIES => XsdDatatypeKind::Entities,
VAL_NOTATION => XsdDatatypeKind::Notation,
VAL_ANYURI => XsdDatatypeKind::AnyURI,
VAL_INTEGER => XsdDatatypeKind::Integer,
VAL_NPINTEGER => XsdDatatypeKind::NonPositiveInteger,
VAL_NINTEGER => XsdDatatypeKind::NegativeInteger,
VAL_NNINTEGER => XsdDatatypeKind::NonNegativeInteger,
VAL_PINTEGER => XsdDatatypeKind::PositiveInteger,
VAL_INT => XsdDatatypeKind::Int,
VAL_UINT => XsdDatatypeKind::UnsignedInt,
VAL_LONG => XsdDatatypeKind::Long,
VAL_ULONG => XsdDatatypeKind::UnsignedLong,
VAL_SHORT => XsdDatatypeKind::Short,
VAL_USHORT => XsdDatatypeKind::UnsignedShort,
VAL_BYTE => XsdDatatypeKind::Byte,
VAL_UBYTE => XsdDatatypeKind::UnsignedByte,
VAL_HEXBINARY => XsdDatatypeKind::HexBinary,
VAL_BASE64BINARY => XsdDatatypeKind::Base64Binary,
_ => return None,
})
}
const fn facet_type_to_kind(facet_type: c_int) -> Option<XsdDatatypeKind> {
Some(match facet_type {
FACET_MININCLUSIVE => XsdDatatypeKind::FacetMinInclusive,
FACET_MINEXCLUSIVE => XsdDatatypeKind::FacetMinExclusive,
FACET_MAXINCLUSIVE => XsdDatatypeKind::FacetMaxInclusive,
FACET_MAXEXCLUSIVE => XsdDatatypeKind::FacetMaxExclusive,
FACET_TOTALDIGITS => XsdDatatypeKind::FacetTotalDigits,
FACET_FRACTIONDIGITS => XsdDatatypeKind::FacetFractionDigits,
FACET_PATTERN => XsdDatatypeKind::FacetPattern,
FACET_ENUMERATION => XsdDatatypeKind::FacetEnumeration,
FACET_WHITESPACE => XsdDatatypeKind::FacetWhiteSpace,
FACET_LENGTH => XsdDatatypeKind::FacetLength,
FACET_MAXLENGTH => XsdDatatypeKind::FacetMaxLength,
FACET_MINLENGTH => XsdDatatypeKind::FacetMinLength,
_ => return None,
})
}
const fn type_name(val_type: c_int) -> Option<&'static [u8]> {
Some(match val_type {
VAL_STRING => b"string\0",
VAL_NORMSTRING => b"normalizedString\0",
VAL_DECIMAL => b"decimal\0",
VAL_TIME => b"time\0",
VAL_GDAY => b"gDay\0",
VAL_GMONTH => b"gMonth\0",
VAL_GMONTHDAY => b"gMonthDay\0",
VAL_GYEAR => b"gYear\0",
VAL_GYEARMONTH => b"gYearMonth\0",
VAL_DATE => b"date\0",
VAL_DATETIME => b"dateTime\0",
VAL_DURATION => b"duration\0",
VAL_FLOAT => b"float\0",
VAL_DOUBLE => b"double\0",
VAL_BOOLEAN => b"boolean\0",
VAL_TOKEN => b"token\0",
VAL_LANGUAGE => b"language\0",
VAL_NMTOKEN => b"NMTOKEN\0",
VAL_NMTOKENS => b"NMTOKENS\0",
VAL_NAME => b"Name\0",
VAL_QNAME => b"QName\0",
VAL_NCNAME => b"NCName\0",
VAL_ID => b"ID\0",
VAL_IDREF => b"IDREF\0",
VAL_IDREFS => b"IDREFS\0",
VAL_ENTITY => b"ENTITY\0",
VAL_ENTITIES => b"ENTITIES\0",
VAL_NOTATION => b"NOTATION\0",
VAL_ANYURI => b"anyURI\0",
VAL_INTEGER => b"integer\0",
VAL_NPINTEGER => b"nonPositiveInteger\0",
VAL_NINTEGER => b"negativeInteger\0",
VAL_NNINTEGER => b"nonNegativeInteger\0",
VAL_PINTEGER => b"positiveInteger\0",
VAL_INT => b"int\0",
VAL_UINT => b"unsignedInt\0",
VAL_LONG => b"long\0",
VAL_ULONG => b"unsignedLong\0",
VAL_SHORT => b"short\0",
VAL_USHORT => b"unsignedShort\0",
VAL_BYTE => b"byte\0",
VAL_UBYTE => b"unsignedByte\0",
VAL_HEXBINARY => b"hexBinary\0",
VAL_BASE64BINARY => b"base64Binary\0",
VAL_ANYTYPE => b"anyType\0",
VAL_ANYSIMPLETYPE => b"anySimpleType\0",
_ => return None,
})
}
const fn is_numeric_type(val_type: c_int) -> bool {
matches!(
val_type,
VAL_DECIMAL
| VAL_FLOAT
| VAL_DOUBLE
| VAL_INTEGER
| VAL_NPINTEGER
| VAL_NINTEGER
| VAL_NNINTEGER
| VAL_PINTEGER
| VAL_INT
| VAL_UINT
| VAL_LONG
| VAL_ULONG
| VAL_SHORT
| VAL_USHORT
| VAL_BYTE
| VAL_UBYTE
)
}
const fn is_list_type(val_type: c_int) -> bool {
matches!(val_type, VAL_NMTOKENS | VAL_IDREFS | VAL_ENTITIES)
}
const fn list_item_type(val_type: c_int) -> c_int {
match val_type {
VAL_NMTOKENS => VAL_NMTOKEN,
VAL_IDREFS => VAL_IDREF,
VAL_ENTITIES => VAL_ENTITY,
other => other,
}
}
fn canonical_decimal(s: &str) -> String {
let mut s = s.trim();
let neg = s.starts_with('-');
if s.starts_with('+') || s.starts_with('-') {
s = &s[1..];
}
let (int_part, frac_part) = match s.find('.') {
Some(i) => (&s[..i], &s[i + 1..]),
None => (s, ""),
};
let int_trimmed = int_part.trim_start_matches('0');
let int_trimmed = if int_trimmed.is_empty() {
"0"
} else {
int_trimmed
};
let frac_trimmed = frac_part.trim_end_matches('0');
let mut out = String::new();
if neg && !(int_trimmed == "0" && frac_trimmed.is_empty()) {
out.push('-');
}
out.push_str(int_trimmed);
if !frac_trimmed.is_empty() {
out.push('.');
out.push_str(frac_trimmed);
}
out
}
unsafe fn new_val(val_type: c_int, value: *const c_char, ns: *const c_char) -> *mut XsdVal {
unsafe {
Box::into_raw(Box::new(XsdVal {
val_type,
value: dup_cstr(value) as *mut xmlChar,
ns: dup_cstr(ns) as *mut xmlChar,
next: ptr::null_mut(),
}))
}
}
unsafe fn free_val_chain(mut cur: *mut XsdVal) {
while !cur.is_null() {
let next = unsafe { (*cur).next };
unsafe {
if !(*cur).value.is_null() {
xmlFreeImpl((*cur).value as *mut c_void);
}
if !(*cur).ns.is_null() {
xmlFreeImpl((*cur).ns as *mut c_void);
}
drop(Box::from_raw(cur));
}
cur = next;
}
}
fn val_from_str(val_type: c_int, value: &str) -> *mut XsdVal {
if let Ok(c) = CString::new(value) {
unsafe { new_val(val_type, c.as_ptr(), ptr::null()) }
} else {
ptr::null_mut()
}
}
unsafe fn new_string_value(val_type: c_int, value: *const c_char) -> *mut XsdVal {
if value.is_null() {
return ptr::null_mut();
}
let s = unsafe { cstr_to_str(value).unwrap_or_default() };
if is_list_type(val_type) {
let item_type = list_item_type(val_type);
let tokens: Vec<&str> = s.split_whitespace().collect();
if tokens.is_empty() {
return ptr::null_mut();
}
let mut head: *mut XsdVal = ptr::null_mut();
let mut tail: *mut XsdVal = ptr::null_mut();
for tok in tokens {
let item = val_from_str(item_type, tok);
if item.is_null() {
continue;
}
if head.is_null() {
head = item;
} else {
unsafe { (*tail).next = item };
}
tail = item;
}
head
} else {
val_from_str(val_type, &s)
}
}
fn builtin_type(val_type: c_int) -> *mut XsdType {
if val_type <= VAL_UNKNOWN || val_type > VAL_ANYSIMPLETYPE {
return ptr::null_mut();
}
{
let reg = TYPE_REGISTRY.lock();
if let Some(addr) = reg.get(&val_type) {
return *addr as *mut XsdType;
}
}
let item = if is_list_type(val_type) {
builtin_type(list_item_type(val_type))
} else {
ptr::null_mut()
};
let name = type_name(val_type)
.map(|b| b.as_ptr() as *const c_char)
.unwrap_or(ptr::null());
let t = Box::into_raw(Box::new(XsdType {
val_type,
item_type: item,
name,
}));
let mut reg = TYPE_REGISTRY.lock();
if let Some(existing) = reg.get(&val_type) {
unsafe { drop(Box::from_raw(t)) };
return *existing as *mut XsdType;
}
reg.insert(val_type, t as usize);
t
}
unsafe fn dispatch_valid_errors(ctxt_addr: usize, errors: &[String]) {
let state = {
let guard = VALID_STATES.lock();
guard.get(&ctxt_addr).copied()
};
let Some(state) = state else { return };
if state.err.is_none() && state.serror.is_none() {
return;
}
for msg in errors {
let Ok(cmsg) = CString::new(msg.as_str()) else {
continue;
};
if let Some(err) = state.err {
unsafe { err(state.ctx as *mut c_void, cmsg.as_ptr()) };
}
if let Some(serror) = state.serror {
let mut e: _xmlError = unsafe { std::mem::zeroed() };
e.domain = XML_FROM_SCHEMASV;
e.code = 0;
e.message = cmsg.as_ptr() as *mut c_char;
e.level = xmlErrorLevel::XML_ERR_ERROR as c_int;
e.file = state.filename as *mut c_char;
e.line = 0;
unsafe { serror(state.sctx as *mut c_void, &e) };
}
}
}
unsafe fn dispatch_parser_error(ctxt_addr: usize, msg: &str) {
let state = {
let guard = PARSER_STATES.lock();
guard.get(&ctxt_addr).copied()
};
let Some(state) = state else { return };
if state.err.is_none() && state.serror.is_none() {
return;
}
let Ok(cmsg) = CString::new(msg) else { return };
if let Some(err) = state.err {
unsafe { err(state.ctx as *mut c_void, cmsg.as_ptr()) };
}
if let Some(serror) = state.serror {
let mut e: _xmlError = unsafe { std::mem::zeroed() };
e.domain = XML_FROM_SCHEMASV;
e.code = 0;
e.message = cmsg.as_ptr() as *mut c_char;
e.level = xmlErrorLevel::XML_ERR_ERROR as c_int;
unsafe { serror(state.sctx as *mut c_void, &e) };
}
}
unsafe fn doc_to_string(doc: *mut _xmlDoc) -> Option<String> {
if doc.is_null() {
return None;
}
let mut mem: *mut xmlChar = ptr::null_mut();
let mut size: c_int = 0;
unsafe { crate::xml::tree::xmlDocDumpFormatMemory(doc, &mut mem, &mut size, 0) };
if mem.is_null() {
return None;
}
let slice = unsafe { std::slice::from_raw_parts(mem as *const u8, size as usize) };
let out = String::from_utf8_lossy(slice).to_string();
unsafe { xmlFreeImpl(mem as *mut c_void) };
Some(out)
}
unsafe fn node_to_string(node: *mut _xmlNode) -> Option<String> {
if node.is_null() {
return None;
}
let buf = crate::abi::exports_xml2::xmlBufferCreate();
if buf.is_null() {
return None;
}
unsafe { crate::xml::tree::xmlNodeDump(buf, (*node).doc, node, 0, 0) };
let content = crate::abi::exports_xml2::xmlBufferContent(buf);
let len = crate::abi::exports_xml2::xmlBufferLength(buf);
if content.is_null() || len <= 0 {
crate::abi::exports_xml2::xmlBufferFree(buf);
return None;
}
let slice = unsafe { std::slice::from_raw_parts(content as *const u8, len as usize) };
let out = String::from_utf8_lossy(slice).to_string();
crate::abi::exports_xml2::xmlBufferFree(buf);
Some(out)
}
unsafe fn reset_valid_ctxt(ctxt: *mut xmlSchemaValidCtxt) {
let vc = unsafe { &mut *(ctxt as *mut XsdValidCtxt) };
vc.errors.clear();
vc.nb_errors = 0;
}
unsafe fn validate_doc_string(ctxt: *mut xmlSchemaValidCtxt, xml: &str) -> c_int {
if ctxt.is_null() {
return -1;
}
unsafe { reset_valid_ctxt(ctxt) };
let doc = unsafe {
crate::abi::exports_xml2::xmlReadMemory(
xml.as_ptr() as *const c_char,
xml.len() as c_int,
c"doc.xml".as_ptr() as *const c_char,
ptr::null(),
0,
)
};
if doc.is_null() {
unsafe {
dispatch_valid_errors(ctxt as usize, &["Document is not well-formed".to_string()]);
}
return -1;
}
let ret = unsafe { crate::xml::schemas::xmlSchemaValidateDoc(ctxt as *mut c_void, doc) };
unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
if ret != 0 {
let errors = unsafe { (*(ctxt as *mut XsdValidCtxt)).errors.clone() };
unsafe { dispatch_valid_errors(ctxt as usize, &errors) };
}
ret
}
#[no_mangle]
pub extern "C" fn xmlSchemaInitTypes() -> c_int {
for t in VAL_STRING..=VAL_ANYSIMPLETYPE {
builtin_type(t);
}
0
}
#[no_mangle]
pub const extern "C" fn xmlSchemaCleanupTypes() {
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaGetPredefinedType(
name: *const xmlChar,
ns: *const xmlChar,
) -> *mut xmlSchemaType {
if name.is_null() || ns.is_null() {
return ptr::null_mut();
}
let ns_str = unsafe { cstr_to_str(ns as *const c_char) }.unwrap_or_default();
if ns_str.as_bytes() != XSD_NS {
return ptr::null_mut();
}
for val_type in VAL_STRING..=VAL_ANYSIMPLETYPE {
if let Some(bytes) = type_name(val_type) {
if unsafe { cstr_eq(name as *const c_char, &bytes[..bytes.len() - 1]) } {
return builtin_type(val_type) as *mut xmlSchemaType;
}
}
}
ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn xmlSchemaGetBuiltInType(type_: c_int) -> *mut xmlSchemaType {
builtin_type(type_) as *mut xmlSchemaType
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaGetBuiltInListSimpleTypeItemType(
type_: *mut xmlSchemaType,
) -> *mut xmlSchemaType {
if type_.is_null() {
return ptr::null_mut();
}
unsafe { (*(type_ as *mut XsdType)).item_type as *mut xmlSchemaType }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaIsBuiltInTypeFacet(
type_: *mut xmlSchemaType,
facetType: c_int,
) -> c_int {
if type_.is_null() {
return 0;
}
if !(FACET_MININCLUSIVE..=FACET_MINLENGTH).contains(&facetType) {
return 0;
}
let val_type = unsafe { (*(type_ as *mut XsdType)).val_type };
let length_facets = matches!(
facetType,
FACET_LENGTH
| FACET_MINLENGTH
| FACET_MAXLENGTH
| FACET_PATTERN
| FACET_ENUMERATION
| FACET_WHITESPACE
);
let numeric_facets = matches!(
facetType,
FACET_MININCLUSIVE
| FACET_MINEXCLUSIVE
| FACET_MAXINCLUSIVE
| FACET_MAXEXCLUSIVE
| FACET_TOTALDIGITS
| FACET_FRACTIONDIGITS
| FACET_PATTERN
| FACET_ENUMERATION
| FACET_WHITESPACE
);
let universal = matches!(
facetType,
FACET_PATTERN | FACET_ENUMERATION | FACET_WHITESPACE
);
if is_numeric_type(val_type) {
if numeric_facets {
1
} else {
0
}
} else if matches!(val_type, VAL_STRING | VAL_NORMSTRING | VAL_TOKEN) || is_list_type(val_type)
{
if length_facets {
1
} else {
0
}
} else if universal {
1
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaWhiteSpaceReplace(value: *const xmlChar) -> *mut xmlChar {
if value.is_null() {
return ptr::null_mut();
}
let s = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
let out = whitespace_replace(&s);
if let Ok(c) = CString::new(out) {
unsafe { dup_cstr(c.as_ptr()) as *mut xmlChar }
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaCollapseString(value: *const xmlChar) -> *mut xmlChar {
if value.is_null() {
return ptr::null_mut();
}
let s = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
let out = whitespace_collapse(&s);
if let Ok(c) = CString::new(out) {
unsafe { dup_cstr(c.as_ptr()) as *mut xmlChar }
} else {
ptr::null_mut()
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaNewStringValue(
type_: c_int,
value: *const xmlChar,
) -> *mut xmlSchemaVal {
unsafe { new_string_value(type_, value as *const c_char) as *mut xmlSchemaVal }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaNewNOTATIONValue(
name: *const xmlChar,
ns: *const xmlChar,
) -> *mut xmlSchemaVal {
unsafe {
new_val(VAL_NOTATION, name as *const c_char, ns as *const c_char) as *mut xmlSchemaVal
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaNewQNameValue(
namespaceName: *const xmlChar,
localName: *const xmlChar,
) -> *mut xmlSchemaVal {
unsafe {
new_val(
VAL_QNAME,
localName as *const c_char,
namespaceName as *const c_char,
) as *mut xmlSchemaVal
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaCopyValue(val: *mut xmlSchemaVal) -> *mut xmlSchemaVal {
if val.is_null() {
return ptr::null_mut();
}
let mut src = val as *mut XsdVal;
let mut head: *mut XsdVal = ptr::null_mut();
let mut tail: *mut XsdVal = ptr::null_mut();
while !src.is_null() {
let (vtype, vvalue, vns) = unsafe { ((*src).val_type, (*src).value, (*src).ns) };
let node = unsafe { new_val(vtype, vvalue as *const c_char, vns as *const c_char) };
if node.is_null() {
unsafe { free_val_chain(head) };
return ptr::null_mut();
}
if head.is_null() {
head = node;
} else {
unsafe { (*tail).next = node };
}
tail = node;
src = unsafe { (*src).next };
}
head as *mut xmlSchemaVal
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaFreeValue(val: *mut xmlSchemaVal) {
if val.is_null() {
return;
}
unsafe { free_val_chain(val as *mut XsdVal) };
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaGetValType(val: *mut xmlSchemaVal) -> c_int {
if val.is_null() {
return VAL_UNKNOWN;
}
unsafe { (*(val as *mut XsdVal)).val_type }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValueGetNext(cur: *mut xmlSchemaVal) -> *mut xmlSchemaVal {
if cur.is_null() {
return ptr::null_mut();
}
unsafe { (*(cur as *mut XsdVal)).next as *mut xmlSchemaVal }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValueGetAsString(val: *mut xmlSchemaVal) -> *const xmlChar {
if val.is_null() {
return ptr::null_mut();
}
unsafe { (*(val as *mut XsdVal)).value as *const xmlChar }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValueGetAsBoolean(val: *mut xmlSchemaVal) -> c_int {
if val.is_null() {
return -1;
}
let v = unsafe { &*(val as *mut XsdVal) };
if v.val_type != VAL_BOOLEAN {
return -1;
}
let s = unsafe { cstr_to_str(v.value as *const c_char) }.unwrap_or_default();
match s.as_str() {
"true" | "1" => 1,
"false" | "0" => 0,
_ => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValueAppend(
prev: *mut xmlSchemaVal,
cur: *mut xmlSchemaVal,
) -> c_int {
if prev.is_null() || cur.is_null() {
return -1;
}
let mut tail = prev as *mut XsdVal;
while !unsafe { (*tail).next }.is_null() {
tail = unsafe { (*tail).next };
}
unsafe { (*tail).next = cur as *mut XsdVal };
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaGetCanonValue(
val: *mut xmlSchemaVal,
retValue: *mut *const xmlChar,
) -> c_int {
unsafe { xmlSchemaGetCanonValueWhtsp(val, retValue, WS_COLLAPSE) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaGetCanonValueWhtsp(
val: *mut xmlSchemaVal,
retValue: *mut *const xmlChar,
ws: c_int,
) -> c_int {
if val.is_null() || retValue.is_null() {
return -1;
}
let v = unsafe { &*(val as *mut XsdVal) };
if v.next.is_null() && !is_list_type(v.val_type) {
let s = unsafe { cstr_to_str(v.value as *const c_char) }.unwrap_or_default();
let norm = apply_ws(&s, ws);
let canon = match v.val_type {
t if is_numeric_type(t) => {
if matches!(t, VAL_FLOAT | VAL_DOUBLE) {
match norm.as_str() {
"NaN" => "NaN".to_string(),
"INF" => "INF".to_string(),
"-INF" => "-INF".to_string(),
_ => match norm.parse::<f64>() {
Ok(f) if f.is_nan() => "NaN".to_string(),
Ok(f) if f.is_infinite() && f.is_sign_negative() => "-INF".to_string(),
Ok(f) if f.is_infinite() => "INF".to_string(),
Ok(f) => f.to_string(),
Err(_) => canonical_decimal(&norm),
},
}
} else if matches!(t, VAL_DECIMAL) {
canonical_decimal(&norm)
} else {
match norm.parse::<i128>() {
Ok(i) => i.to_string(),
Err(_) => canonical_decimal(&norm),
}
}
}
VAL_BOOLEAN => match norm.as_str() {
"true" | "1" => "true".to_string(),
"false" | "0" => "false".to_string(),
_ => return -1,
},
_ => norm,
};
let Ok(c) = CString::new(canon) else {
return -1;
};
let out = unsafe { dup_cstr(c.as_ptr()) } as *const xmlChar;
if out.is_null() {
return -1;
}
unsafe { *retValue = out };
0
} else {
let mut parts: Vec<String> = Vec::new();
let mut cur = val as *mut XsdVal;
while !cur.is_null() {
let s = unsafe { cstr_to_str((*cur).value as *const c_char) }.unwrap_or_default();
parts.push(apply_ws(&s, ws));
cur = unsafe { (*cur).next };
}
let Ok(c) = CString::new(parts.join(" ")) else {
return -1;
};
let out = unsafe { dup_cstr(c.as_ptr()) } as *const xmlChar;
if out.is_null() {
return -1;
}
unsafe { *retValue = out };
0
}
}
#[no_mangle]
pub extern "C" fn xmlSchemaNewFacet() -> *mut xmlSchemaFacet {
Box::into_raw(Box::new(XsdFacet {
facet_type: 0,
value: ptr::null_mut(),
next: ptr::null_mut(),
})) as *mut xmlSchemaFacet
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaFreeFacet(facet: *mut xmlSchemaFacet) {
if facet.is_null() {
return;
}
let f = unsafe { Box::from_raw(facet as *mut XsdFacet) };
if !f.value.is_null() {
unsafe { xmlFreeImpl(f.value as *mut c_void) };
}
drop(f);
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaGetFacetValueAsULong(facet: *mut xmlSchemaFacet) -> c_ulong {
if facet.is_null() {
return 0;
}
let s = unsafe { cstr_to_str((*(facet as *mut XsdFacet)).value as *const c_char) }
.unwrap_or_default();
s.trim().parse::<u64>().unwrap_or(0) as c_ulong
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaCheckFacet(
facet: *mut xmlSchemaFacet,
typeDecl: *mut xmlSchemaType,
ctxt: *mut xmlSchemaParserCtxt,
name: *const xmlChar,
) -> c_int {
if facet.is_null() {
return -1;
}
let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
if !(FACET_MININCLUSIVE..=FACET_MINLENGTH).contains(&facet_type) {
unsafe {
dispatch_parser_error(ctxt as usize, "Invalid facet type");
}
return -1;
}
if !typeDecl.is_null() {
let valid = xmlSchemaIsBuiltInTypeFacet(typeDecl, facet_type);
if valid == 0 {
let label = unsafe { cstr_to_str(name as *const c_char) }
.unwrap_or_else(|| "facet".to_string());
unsafe {
dispatch_parser_error(
ctxt as usize,
&format!("Facet '{}' is not valid for this type", label),
);
}
return -1;
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateFacet(
base: *mut xmlSchemaType,
facet: *mut xmlSchemaFacet,
value: *const xmlChar,
val: *mut xmlSchemaVal,
) -> c_int {
if facet.is_null() {
return -1;
}
let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
let Some(facet_kind) = facet_type_to_kind(facet_type) else {
return -1;
};
let facet_value = unsafe { cstr_to_str((*(facet as *mut XsdFacet)).value as *const c_char) }
.unwrap_or_default();
let base_type = if !base.is_null() {
unsafe { (*(base as *mut XsdType)).val_type }
} else if !val.is_null() {
unsafe { (*(val as *mut XsdVal)).val_type }
} else {
VAL_STRING
};
let Some(kind) = val_type_to_kind(base_type) else {
return -1;
};
let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
let norm = whitespace_collapse(&raw);
if xsd_validate_facet(&kind, &norm, &facet_kind, &facet_value) {
0
} else {
-1
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateFacetWhtsp(
facet: *mut xmlSchemaFacet,
_fws: c_int,
valType: c_int,
value: *const xmlChar,
val: *mut xmlSchemaVal,
ws: c_int,
) -> c_int {
if facet.is_null() {
return -1;
}
let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
let Some(facet_kind) = facet_type_to_kind(facet_type) else {
return -1;
};
let facet_value = unsafe { cstr_to_str((*(facet as *mut XsdFacet)).value as *const c_char) }
.unwrap_or_default();
let kind = if val.is_null() {
val_type_to_kind(valType)
} else {
val_type_to_kind(unsafe { (*(val as *mut XsdVal)).val_type })
};
let Some(kind) = kind else {
return -1;
};
let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
let norm = apply_ws(&raw, ws);
if xsd_validate_facet(&kind, &norm, &facet_kind, &facet_value) {
0
} else {
-1
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateLengthFacet(
type_: *mut xmlSchemaType,
facet: *mut xmlSchemaFacet,
value: *const xmlChar,
val: *mut xmlSchemaVal,
length: *mut c_ulong,
) -> c_int {
let val_type = if !type_.is_null() {
unsafe { (*(type_ as *mut XsdType)).val_type }
} else if !val.is_null() {
unsafe { (*(val as *mut XsdVal)).val_type }
} else {
VAL_STRING
};
unsafe { xmlSchemaValidateLengthFacetWhtsp(facet, val_type, value, val, length, WS_PRESERVE) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateLengthFacetWhtsp(
facet: *mut xmlSchemaFacet,
valType: c_int,
value: *const xmlChar,
val: *mut xmlSchemaVal,
length: *mut c_ulong,
ws: c_int,
) -> c_int {
if facet.is_null() {
return -1;
}
let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
let facet_ulong = xmlSchemaGetFacetValueAsULong(facet);
let len: c_ulong = if is_list_type(valType) {
if val.is_null() {
0
} else {
let mut count: c_ulong = 0;
let mut cur = val as *mut XsdVal;
while !cur.is_null() {
count += 1;
cur = unsafe { (*cur).next };
}
count
}
} else {
let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
let norm = apply_ws(&raw, ws);
norm.chars().count() as c_ulong
};
if !length.is_null() {
unsafe { *length = len as c_ulong };
}
let ok = match facet_type {
FACET_LENGTH => len == facet_ulong,
FACET_MINLENGTH => len >= facet_ulong,
FACET_MAXLENGTH => len <= facet_ulong,
_ => {
return -1;
}
};
if ok {
0
} else {
-1
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateListSimpleTypeFacet(
facet: *mut xmlSchemaFacet,
_value: *const xmlChar,
actualLen: c_ulong,
expectedLen: *mut c_ulong,
) -> c_int {
if facet.is_null() {
return -1;
}
let facet_type = unsafe { (*(facet as *mut XsdFacet)).facet_type };
let facet_ulong = xmlSchemaGetFacetValueAsULong(facet);
if !expectedLen.is_null() {
unsafe { *expectedLen = facet_ulong };
}
let ok = match facet_type {
FACET_LENGTH => actualLen == facet_ulong,
FACET_MINLENGTH => actualLen >= facet_ulong,
FACET_MAXLENGTH => actualLen <= facet_ulong,
_ => return -1,
};
if ok {
0
} else {
-1
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidatePredefinedType(
type_: *mut xmlSchemaType,
value: *const xmlChar,
val: *mut *mut xmlSchemaVal,
) -> c_int {
unsafe { xmlSchemaValPredefTypeNode(type_, value, val, ptr::null_mut()) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValPredefTypeNode(
type_: *mut xmlSchemaType,
value: *const xmlChar,
val: *mut *mut xmlSchemaVal,
_node: *mut _xmlNode,
) -> c_int {
if type_.is_null() || value.is_null() {
return -1;
}
let val_type = unsafe { (*(type_ as *mut XsdType)).val_type };
let Some(kind) = val_type_to_kind(val_type) else {
return -1;
};
let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
let norm = whitespace_collapse(&raw);
if !xsd_validate_datatype(&kind, &norm, &[]) {
return -1;
}
if !val.is_null() {
*val = unsafe { new_string_value(val_type, value as *const c_char) } as *mut xmlSchemaVal;
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValPredefTypeNodeNoNorm(
type_: *mut xmlSchemaType,
value: *const xmlChar,
val: *mut *mut xmlSchemaVal,
_node: *mut _xmlNode,
) -> c_int {
if type_.is_null() || value.is_null() {
return -1;
}
let val_type = unsafe { (*(type_ as *mut XsdType)).val_type };
let Some(kind) = val_type_to_kind(val_type) else {
return -1;
};
let raw = unsafe { cstr_to_str(value as *const c_char) }.unwrap_or_default();
if !xsd_validate_datatype(&kind, &raw, &[]) {
return -1;
}
if !val.is_null() {
*val = unsafe { new_string_value(val_type, value as *const c_char) } as *mut xmlSchemaVal;
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaCompareValues(
x: *mut xmlSchemaVal,
y: *mut xmlSchemaVal,
) -> c_int {
unsafe { xmlSchemaCompareValuesWhtsp(x, WS_COLLAPSE, y, WS_COLLAPSE) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaCompareValuesWhtsp(
x: *mut xmlSchemaVal,
xws: c_int,
y: *mut xmlSchemaVal,
yws: c_int,
) -> c_int {
if x.is_null() || y.is_null() {
return -2;
}
let (xt, xv, yt, yv) = unsafe {
(
(*(x as *mut XsdVal)).val_type,
cstr_to_str((*(x as *mut XsdVal)).value as *const c_char).unwrap_or_default(),
(*(y as *mut XsdVal)).val_type,
cstr_to_str((*(y as *mut XsdVal)).value as *const c_char).unwrap_or_default(),
)
};
let xs = apply_ws(&xv, xws);
let ys = apply_ws(&yv, yws);
if is_numeric_type(xt) && is_numeric_type(yt) {
let to_f64 = |s: &str| -> Option<f64> {
match s {
"INF" => Some(f64::INFINITY),
"-INF" => Some(f64::NEG_INFINITY),
"NaN" => Some(f64::NAN),
_ => s.parse::<f64>().ok(),
}
};
match (to_f64(&xs), to_f64(&ys)) {
(Some(a), Some(b)) => {
if a.is_nan() || b.is_nan() {
return -2;
}
if a < b {
-1
} else if a > b {
1
} else {
0
}
}
_ => -2,
}
} else if xt == VAL_BOOLEAN && yt == VAL_BOOLEAN {
let b = |s: &str| -> Option<i32> {
match s {
"true" | "1" => Some(1),
"false" | "0" => Some(0),
_ => None,
}
};
match (b(&xs), b(&ys)) {
(Some(a), Some(c)) => a.cmp(&c) as c_int,
_ => -2,
}
} else {
match xs.cmp(&ys) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaFreeType(type_: *mut xmlSchemaType) {
if type_.is_null() {
return;
}
let addr = type_ as usize;
let registered = {
let reg = TYPE_REGISTRY.lock();
reg.values().any(|&v| v == addr)
};
if registered {
return;
}
unsafe { drop(Box::from_raw(type_ as *mut XsdType)) };
}
#[no_mangle]
pub const unsafe extern "C" fn xmlSchemaFreeWildcard(_wildcard: *mut xmlSchemaWildcard) {
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaNewDocParserCtxt(doc: *mut _xmlDoc) -> *mut xmlSchemaParserCtxt {
let Some(xml) = (unsafe { doc_to_string(doc) }) else {
return ptr::null_mut();
};
match xsd_parse(&xml) {
Ok(schema) => Box::into_raw(Box::new(schema)) as *mut xmlSchemaParserCtxt,
Err(_) => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaSetParserErrors(
ctxt: *mut xmlSchemaParserCtxt,
err: Option<xmlValidityErrorFunc>,
warn: Option<xmlValidityWarningFunc>,
ctx: *mut c_void,
) {
if ctxt.is_null() {
return;
}
let mut guard = PARSER_STATES.lock();
let e = guard.entry(ctxt as usize).or_default();
e.err = err;
e.warn = warn;
e.ctx = ctx as usize;
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaSetParserStructuredErrors(
ctxt: *mut xmlSchemaParserCtxt,
serror: Option<xmlStructuredErrorFunc>,
ctx: *mut c_void,
) {
if ctxt.is_null() {
return;
}
let mut guard = PARSER_STATES.lock();
let e = guard.entry(ctxt as usize).or_default();
e.serror = serror;
e.sctx = ctx as usize;
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaGetParserErrors(
ctxt: *mut xmlSchemaParserCtxt,
err: *mut Option<xmlValidityErrorFunc>,
warn: *mut Option<xmlValidityWarningFunc>,
ctx: *mut *mut c_void,
) -> c_int {
if ctxt.is_null() {
return -1;
}
let state = {
let guard = PARSER_STATES.lock();
guard.get(&(ctxt as usize)).copied().unwrap_or_default()
};
unsafe {
if !err.is_null() {
*err = state.err;
}
if !warn.is_null() {
*warn = state.warn;
}
if !ctx.is_null() {
*ctx = state.ctx as *mut c_void;
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaSetValidErrors(
ctxt: *mut xmlSchemaValidCtxt,
err: Option<xmlValidityErrorFunc>,
warn: Option<xmlValidityWarningFunc>,
ctx: *mut c_void,
) {
if ctxt.is_null() {
return;
}
let mut guard = VALID_STATES.lock();
let e = guard.entry(ctxt as usize).or_default();
e.err = err;
e.warn = warn;
e.ctx = ctx as usize;
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaGetValidErrors(
ctxt: *mut xmlSchemaValidCtxt,
err: *mut Option<xmlValidityErrorFunc>,
warn: *mut Option<xmlValidityWarningFunc>,
ctx: *mut *mut c_void,
) -> c_int {
if ctxt.is_null() {
return -1;
}
let state = {
let guard = VALID_STATES.lock();
guard.get(&(ctxt as usize)).copied().unwrap_or_default()
};
unsafe {
if !err.is_null() {
*err = state.err;
}
if !warn.is_null() {
*warn = state.warn;
}
if !ctx.is_null() {
*ctx = state.ctx as *mut c_void;
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaSetValidStructuredErrors(
ctxt: *mut xmlSchemaValidCtxt,
serror: Option<xmlStructuredErrorFunc>,
ctx: *mut c_void,
) {
if ctxt.is_null() {
return;
}
let mut guard = VALID_STATES.lock();
let e = guard.entry(ctxt as usize).or_default();
e.serror = serror;
e.sctx = ctx as usize;
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaSetValidOptions(
ctxt: *mut xmlSchemaValidCtxt,
options: c_int,
) -> c_int {
if ctxt.is_null() {
return -1;
}
if options & !VAL_OPTIONS_MASK != 0 {
return -1;
}
VALID_STATES
.lock()
.entry(ctxt as usize)
.or_default()
.options = options;
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidCtxtGetOptions(ctxt: *mut xmlSchemaValidCtxt) -> c_int {
if ctxt.is_null() {
return 0;
}
let guard = VALID_STATES.lock();
guard.get(&(ctxt as usize)).map(|s| s.options).unwrap_or(0)
}
#[no_mangle]
pub const unsafe extern "C" fn xmlSchemaValidCtxtGetParserCtxt(
_ctxt: *mut xmlSchemaValidCtxt,
) -> *mut c_void {
ptr::null_mut()
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateSetFilename(
vctxt: *mut xmlSchemaValidCtxt,
filename: *const c_char,
) {
if vctxt.is_null() {
return;
}
VALID_STATES
.lock()
.entry(vctxt as usize)
.or_default()
.filename = filename as usize;
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateSetLocator(
vctxt: *mut xmlSchemaValidCtxt,
f: Option<xmlSchemaValidityLocatorFunc>,
ctxt: *mut c_void,
) {
if vctxt.is_null() {
return;
}
let mut guard = VALID_STATES.lock();
let e = guard.entry(vctxt as usize).or_default();
e.locator = f;
e.locator_ctx = ctxt as usize;
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaIsValid(ctxt: *mut xmlSchemaValidCtxt) -> c_int {
if ctxt.is_null() {
return 0;
}
let nb = unsafe { (*(ctxt as *mut XsdValidCtxt)).nb_errors };
if nb == 0 {
1
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateFile(
ctxt: *mut xmlSchemaValidCtxt,
filename: *const c_char,
options: c_int,
) -> c_int {
if ctxt.is_null() || filename.is_null() {
return -1;
}
unsafe { reset_valid_ctxt(ctxt) };
let doc = unsafe { crate::abi::exports_xml2::xmlReadFile(filename, ptr::null(), options) };
if doc.is_null() {
unsafe {
dispatch_valid_errors(ctxt as usize, &["Failed to parse document".to_string()]);
}
return -1;
}
let ret = unsafe { crate::xml::schemas::xmlSchemaValidateDoc(ctxt as *mut c_void, doc) };
unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
if ret != 0 {
let errors = unsafe { (*(ctxt as *mut XsdValidCtxt)).errors.clone() };
unsafe { dispatch_valid_errors(ctxt as usize, &errors) };
}
ret
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateOneElement(
ctxt: *mut xmlSchemaValidCtxt,
elem: *mut _xmlNode,
) -> c_int {
if ctxt.is_null() || elem.is_null() {
return -1;
}
unsafe { reset_valid_ctxt(ctxt) };
let valid_ctxt = unsafe { &mut *(ctxt as *mut XsdValidCtxt) };
let Some(schema) = valid_ctxt.schema.clone() else {
return -1;
};
let Some(xml) = (unsafe { node_to_string(elem) }) else {
return -1;
};
match xsd_validate(&schema, &xml) {
Ok(()) => 0,
Err(errors) => {
valid_ctxt.errors = errors.clone();
valid_ctxt.nb_errors = errors.len() as i32;
unsafe { dispatch_valid_errors(ctxt as usize, &errors) };
errors.len() as c_int
}
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateStream(
ctxt: *mut xmlSchemaValidCtxt,
input: *mut _xmlParserInputBuffer,
_enc: xmlCharEncoding,
_sax: *const _xmlSAXHandler,
_user_data: *mut c_void,
) -> c_int {
if ctxt.is_null() || input.is_null() {
return -1;
}
let ib = unsafe { &*input };
let Some(read) = ib.readcallback else {
return -1;
};
let mut content: Vec<u8> = Vec::new();
let mut chunk = [0u8; 4096];
loop {
let n = unsafe { read(ib.context, chunk.as_mut_ptr() as *mut c_char, 4096) };
if n <= 0 {
break;
}
content.extend_from_slice(&chunk[..n as usize]);
}
let xml_str = String::from_utf8_lossy(&content).to_string();
unsafe { validate_doc_string(ctxt, &xml_str) }
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaSAXPlug(
ctxt: *mut xmlSchemaValidCtxt,
sax: *mut *mut _xmlSAXHandler,
user_data: *mut *mut c_void,
) -> *mut xmlSchemaSAXPlugStruct {
if ctxt.is_null() || sax.is_null() {
return ptr::null_mut();
}
let original_sax = unsafe {
if sax.is_null() {
ptr::null_mut()
} else {
*sax
}
};
let original_ud = unsafe {
if user_data.is_null() {
ptr::null_mut()
} else {
*user_data
}
};
let plug = Box::new(XsdSaxPlug {
sax: original_sax as *const _xmlSAXHandler,
user_data: original_ud,
});
Box::into_raw(plug) as *mut xmlSchemaSAXPlugStruct
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaSAXUnplug(plug: *mut xmlSchemaSAXPlugStruct) -> c_int {
if plug.is_null() {
return -1;
}
unsafe { drop(Box::from_raw(plug as *mut XsdSaxPlug)) };
0
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaDump(output: *mut c_void, schema: *mut xmlSchema) {
if output.is_null() || schema.is_null() {
return;
}
let s = unsafe { &*(schema as *const XsdSchema) };
let mut out = String::new();
out.push_str("Schema dump\n");
if let Some(ref tns) = s.target_namespace {
out.push_str(&format!(" target namespace: {}\n", tns));
} else {
out.push_str(" target namespace: (none)\n");
}
out.push_str(&format!(
" element form default: {}\n",
s.element_form_default.as_deref().unwrap_or("unqualified")
));
out.push_str(&format!(
" attribute form default: {}\n",
s.attribute_form_default.as_deref().unwrap_or("unqualified")
));
out.push_str(" components:\n");
for c in &s.components {
dump_component(&mut out, c, 2);
}
unsafe { libc::fputs(out.as_ptr() as *const c_char, output as *mut libc::FILE) };
}
fn dump_component(out: &mut String, c: &crate::xml::schemas::XsdComponent, depth: usize) {
let indent = " ".repeat(depth);
let ctype = format!("{:?}", c.component_type).to_lowercase();
let mut line = format!("{}{}", indent, ctype);
if let Some(ref name) = c.name {
line.push_str(&format!(" name='{}'", name));
}
if let Some(ref dtype) = c.datatype {
line.push_str(&format!(" type={:?}", dtype));
}
if c.min_occurs != 1 || c.max_occurs != 1 {
line.push_str(&format!(
" minOccurs={} maxOccurs={}",
c.min_occurs,
if c.max_occurs == -1 {
"unbounded".to_string()
} else {
c.max_occurs.to_string()
}
));
}
if !c.facets.is_empty() {
let facets: Vec<String> = c
.facets
.iter()
.map(|(k, v)| format!("{:?}={}", k, v))
.collect();
line.push_str(&format!(" facets=[{}]", facets.join(", ")));
}
out.push_str(&line);
out.push('\n');
for child in &c.children {
dump_component(out, child, depth + 1);
}
for attr in &c.attributes {
dump_component(out, attr, depth + 1);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchematronNewDocParserCtxt(
doc: *mut _xmlDoc,
) -> *mut xmlSchematronParserCtxt {
let Some(xml) = (unsafe { doc_to_string(doc) }) else {
return ptr::null_mut();
};
match schematron_parse(&xml) {
Ok(schema) => Box::into_raw(Box::new(schema)) as *mut xmlSchematronParserCtxt,
Err(_) => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchematronSetValidStructuredErrors(
ctxt: *mut xmlSchematronValidCtxt,
serror: Option<xmlStructuredErrorFunc>,
ctx: *mut c_void,
) {
if ctxt.is_null() {
return;
}
let mut guard = SCHEMATRON_VALID_STATES.lock();
let e = guard.entry(ctxt as usize).or_default();
e.serror = serror;
e.sctx = ctx as usize;
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaSetResourceLoader(
ctxt: *mut c_void,
loader: Option<crate::abi::callbacks::xmlResourceLoader>,
data: *mut c_void,
) {
if ctxt.is_null() {
return;
}
let mut map = PARSER_STATES.lock();
let st = map.entry(ctxt as usize).or_default();
st.resource_loader = loader;
st.resource_ctxt = data as usize;
}
#[no_mangle]
pub unsafe extern "C" fn xmlXIncludeSetResourceLoader(
ctxt: crate::abi::exports_xinclude::xmlXIncludeCtxtPtr,
loader: Option<crate::abi::callbacks::xmlResourceLoader>,
data: *mut c_void,
) {
if ctxt.is_null() {
return;
}
let mut map = PARSER_STATES.lock();
let st = map.entry(ctxt as usize).or_default();
st.resource_loader = loader;
st.resource_ctxt = data as usize;
}