use core::ffi::c_void;
use core::ptr;
use std::collections::HashMap;
use std::os::raw::{c_char, c_int};
use crate::abi::allocator;
use crate::abi::structs::*;
use crate::abi::types::xmlElementType::*;
use crate::abi::types::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XsdComponentType {
Schema,
Element,
Attribute,
ComplexType,
SimpleType,
SimpleContent,
ComplexContent,
Sequence,
Choice,
All,
Restriction,
Extension,
List,
Union,
Annotation,
Any,
AnyAttribute,
Group,
AttributeGroup,
Notation,
Unique,
Key,
KeyRef,
Selector,
Field,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum XsdDatatypeKind {
String,
Boolean,
Decimal,
Float,
Double,
Duration,
DateTime,
Time,
Date,
GYearMonth,
GYear,
GMonthDay,
GDay,
GMonth,
HexBinary,
Base64Binary,
AnyURI,
QName,
Notation,
NormalizedString,
Token,
Language,
Nmtoken,
Nmtokens,
Name,
NCName,
Id,
Idref,
Idrefs,
Entity,
Entities,
Integer,
NonPositiveInteger,
NegativeInteger,
Long,
Int,
Short,
Byte,
NonNegativeInteger,
UnsignedLong,
UnsignedInt,
UnsignedShort,
UnsignedByte,
PositiveInteger,
FacetPattern,
FacetEnumeration,
FacetMinInclusive,
FacetMaxInclusive,
FacetMinExclusive,
FacetMaxExclusive,
FacetMinLength,
FacetMaxLength,
FacetLength,
FacetWhiteSpace,
FacetFractionDigits,
FacetTotalDigits,
}
#[derive(Debug, Clone)]
pub struct XsdComponent {
pub component_type: XsdComponentType,
pub name: Option<String>,
pub target_namespace: Option<String>,
pub children: Vec<XsdComponent>,
pub attributes: Vec<XsdComponent>,
pub datatype: Option<XsdDatatypeKind>,
pub facets: Vec<(XsdDatatypeKind, String)>,
pub base: Option<String>,
pub min_occurs: i32,
pub max_occurs: i32, pub ref_name: Option<String>,
pub substitution_group: Option<String>,
pub is_abstract: bool,
pub is_final: bool,
pub block: Vec<String>,
pub mixed: bool,
pub form: Option<String>,
}
impl XsdComponent {
pub fn new(component_type: XsdComponentType) -> Self {
Self {
component_type,
name: None,
target_namespace: None,
children: Vec::new(),
attributes: Vec::new(),
datatype: None,
facets: Vec::new(),
base: None,
min_occurs: 1,
max_occurs: 1,
ref_name: None,
substitution_group: None,
is_abstract: false,
is_final: false,
block: Vec::new(),
mixed: false,
form: None,
}
}
}
#[derive(Debug, Clone)]
pub struct XsdSchema {
pub components: Vec<XsdComponent>,
pub target_namespace: Option<String>,
pub element_form_default: Option<String>,
pub attribute_form_default: Option<String>,
pub errors: Vec<String>,
}
impl XsdSchema {
pub fn new() -> Self {
Self {
components: Vec::new(),
target_namespace: None,
element_form_default: None,
attribute_form_default: None,
errors: Vec::new(),
}
}
}
impl Default for XsdSchema {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct XsdValidCtxt {
pub schema: Option<XsdSchema>,
pub errors: Vec<String>,
pub nb_errors: i32,
}
impl XsdValidCtxt {
pub fn new() -> Self {
Self {
schema: None,
errors: Vec::new(),
nb_errors: 0,
}
}
}
impl Default for XsdValidCtxt {
fn default() -> Self {
Self::new()
}
}
unsafe fn get_node_text(node: *mut _xmlNode) -> String {
if node.is_null() {
return String::new();
}
let mut result = String::new();
unsafe {
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_TEXT_NODE as c_int
|| (*child).type_ == XML_CDATA_SECTION_NODE as c_int
{
if !(*child).content.is_null() {
let content = (*child).content;
let mut len = 0;
while *content.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts(content, len);
result.push_str(&String::from_utf8_lossy(slice));
}
}
child = (*child).next;
}
}
result
}
unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
if node.is_null() {
return None;
}
unsafe {
let mut prop = (*node).properties;
while !prop.is_null() {
let prop_name = (*prop).name;
if !prop_name.is_null() {
let mut len = 0;
while *prop_name.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts(prop_name, len);
if let Ok(s) = std::str::from_utf8(slice) {
if s == name {
return Some(get_node_text(prop as *mut _xmlNode));
}
}
}
prop = (*prop).next;
}
}
None
}
unsafe fn get_attr_bool(node: *mut _xmlNode, name: &str) -> bool {
unsafe {
match get_attr(node, name) {
Some(v) => v == "true" || v == "1",
None => false,
}
}
}
unsafe fn get_attr_int(node: *mut _xmlNode, name: &str, default: i32) -> i32 {
unsafe {
match get_attr(node, name) {
Some(v) => v.parse::<i32>().unwrap_or(default),
None => default,
}
}
}
unsafe fn get_attr_occurs(node: *mut _xmlNode, name: &str, default: i32) -> i32 {
unsafe {
match get_attr(node, name) {
Some(v) => {
if v == "unbounded" {
-1
} else {
v.parse::<i32>().unwrap_or(default)
}
}
None => default,
}
}
}
unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
if node.is_null() {
return false;
}
unsafe {
let name = (*node).name;
if name.is_null() {
return false;
}
let mut len = 0;
while *name.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts(name, len);
if let Ok(s) = std::str::from_utf8(slice) {
let local = if let Some(pos) = s.find(':') {
&s[pos + 1..]
} else {
s
};
return local == local_name;
}
}
false
}
fn parse_datatype_kind(name: &str) -> Option<XsdDatatypeKind> {
let local = if let Some(pos) = name.find(':') {
&name[pos + 1..]
} else {
name
};
match local {
"string" => Some(XsdDatatypeKind::String),
"boolean" => Some(XsdDatatypeKind::Boolean),
"decimal" => Some(XsdDatatypeKind::Decimal),
"float" => Some(XsdDatatypeKind::Float),
"double" => Some(XsdDatatypeKind::Double),
"duration" => Some(XsdDatatypeKind::Duration),
"dateTime" => Some(XsdDatatypeKind::DateTime),
"time" => Some(XsdDatatypeKind::Time),
"date" => Some(XsdDatatypeKind::Date),
"gYearMonth" => Some(XsdDatatypeKind::GYearMonth),
"gYear" => Some(XsdDatatypeKind::GYear),
"gMonthDay" => Some(XsdDatatypeKind::GMonthDay),
"gDay" => Some(XsdDatatypeKind::GDay),
"gMonth" => Some(XsdDatatypeKind::GMonth),
"hexBinary" => Some(XsdDatatypeKind::HexBinary),
"base64Binary" => Some(XsdDatatypeKind::Base64Binary),
"anyURI" => Some(XsdDatatypeKind::AnyURI),
"QName" => Some(XsdDatatypeKind::QName),
"NOTATION" => Some(XsdDatatypeKind::Notation),
"normalizedString" => Some(XsdDatatypeKind::NormalizedString),
"token" => Some(XsdDatatypeKind::Token),
"language" => Some(XsdDatatypeKind::Language),
"NMTOKEN" => Some(XsdDatatypeKind::Nmtoken),
"NMTOKENS" => Some(XsdDatatypeKind::Nmtokens),
"Name" => Some(XsdDatatypeKind::Name),
"NCName" => Some(XsdDatatypeKind::NCName),
"ID" => Some(XsdDatatypeKind::Id),
"IDREF" => Some(XsdDatatypeKind::Idref),
"IDREFS" => Some(XsdDatatypeKind::Idrefs),
"ENTITY" => Some(XsdDatatypeKind::Entity),
"ENTITIES" => Some(XsdDatatypeKind::Entities),
"integer" => Some(XsdDatatypeKind::Integer),
"nonPositiveInteger" => Some(XsdDatatypeKind::NonPositiveInteger),
"negativeInteger" => Some(XsdDatatypeKind::NegativeInteger),
"long" => Some(XsdDatatypeKind::Long),
"int" => Some(XsdDatatypeKind::Int),
"short" => Some(XsdDatatypeKind::Short),
"byte" => Some(XsdDatatypeKind::Byte),
"nonNegativeInteger" => Some(XsdDatatypeKind::NonNegativeInteger),
"unsignedLong" => Some(XsdDatatypeKind::UnsignedLong),
"unsignedInt" => Some(XsdDatatypeKind::UnsignedInt),
"unsignedShort" => Some(XsdDatatypeKind::UnsignedShort),
"unsignedByte" => Some(XsdDatatypeKind::UnsignedByte),
"positiveInteger" => Some(XsdDatatypeKind::PositiveInteger),
_ => None,
}
}
fn parse_facet_kind(name: &str) -> Option<XsdDatatypeKind> {
match name {
"pattern" => Some(XsdDatatypeKind::FacetPattern),
"enumeration" => Some(XsdDatatypeKind::FacetEnumeration),
"minInclusive" => Some(XsdDatatypeKind::FacetMinInclusive),
"maxInclusive" => Some(XsdDatatypeKind::FacetMaxInclusive),
"minExclusive" => Some(XsdDatatypeKind::FacetMinExclusive),
"maxExclusive" => Some(XsdDatatypeKind::FacetMaxExclusive),
"minLength" => Some(XsdDatatypeKind::FacetMinLength),
"maxLength" => Some(XsdDatatypeKind::FacetMaxLength),
"length" => Some(XsdDatatypeKind::FacetLength),
"whiteSpace" => Some(XsdDatatypeKind::FacetWhiteSpace),
"fractionDigits" => Some(XsdDatatypeKind::FacetFractionDigits),
"totalDigits" => Some(XsdDatatypeKind::FacetTotalDigits),
_ => None,
}
}
pub fn xsd_parse(xml_doc: &str) -> Result<XsdSchema, String> {
let doc_ptr = unsafe {
crate::abi::exports_xml2::xmlReadMemory(
xml_doc.as_ptr() as *const c_char,
xml_doc.len() as c_int,
b"schema.xsd\0".as_ptr() as *const c_char,
ptr::null(),
0,
)
};
if doc_ptr.is_null() {
return Err("Failed to parse schema XML document".to_string());
}
let result = unsafe { xsd_parse_schema_doc(doc_ptr) };
unsafe {
crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
}
result
}
unsafe fn xsd_parse_schema_doc(doc: *mut _xmlDoc) -> Result<XsdSchema, String> {
unsafe {
let root = (*doc).children;
if root.is_null() {
return Err("Schema document has no root element".to_string());
}
let mut schema_node = root;
while !schema_node.is_null() && !node_is(schema_node, "schema") {
schema_node = (*schema_node).next;
}
if schema_node.is_null() {
return Err("Schema document root is not <schema>".to_string());
}
Ok(xsd_parse_schema_node(schema_node))
}
}
unsafe fn xsd_parse_schema_node(node: *mut _xmlNode) -> XsdSchema {
unsafe {
let mut schema = XsdSchema::new();
schema.target_namespace = get_attr(node, "targetNamespace");
schema.element_form_default = get_attr(node, "elementFormDefault");
schema.attribute_form_default = get_attr(node, "attributeFormDefault");
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let comp = xsd_parse_component(child, &schema);
if comp.component_type != XsdComponentType::Annotation {
schema.components.push(comp);
}
}
child = (*child).next;
}
schema
}
}
unsafe fn xsd_parse_component(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let name_str = if !(*node).name.is_null() {
let mut len = 0;
while *(*node).name.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts((*node).name, len);
if let Ok(s) = std::str::from_utf8(slice) {
if let Some(pos) = s.find(':') {
s[pos + 1..].to_string()
} else {
s.to_string()
}
} else {
String::new()
}
} else {
String::new()
};
match name_str.as_str() {
"element" => xsd_parse_element(node, schema),
"attribute" => xsd_parse_attribute_node(node, schema),
"complexType" => xsd_parse_complex_type(node, schema),
"simpleType" => xsd_parse_simple_type(node, schema),
"sequence" => xsd_parse_model_group(node, XsdComponentType::Sequence, schema),
"choice" => xsd_parse_model_group(node, XsdComponentType::Choice, schema),
"all" => xsd_parse_model_group(node, XsdComponentType::All, schema),
"restriction" => xsd_parse_restriction(node, schema),
"extension" => xsd_parse_extension(node, schema),
"list" => xsd_parse_list(node, schema),
"union" => xsd_parse_union(node, schema),
"annotation" => xsd_parse_annotation(node),
"any" => xsd_parse_any(node, schema),
"anyAttribute" => {
let mut comp = XsdComponent::new(XsdComponentType::AnyAttribute);
comp
}
"group" => xsd_parse_group(node, schema),
"attributeGroup" => xsd_parse_attribute_group(node, schema),
"unique" => xsd_parse_identity_constraint(node, XsdComponentType::Unique, schema),
"key" => xsd_parse_identity_constraint(node, XsdComponentType::Key, schema),
"keyref" => xsd_parse_identity_constraint(node, XsdComponentType::KeyRef, schema),
"pattern" | "enumeration" | "minInclusive" | "maxInclusive" | "minExclusive"
| "maxExclusive" | "minLength" | "maxLength" | "length" | "whiteSpace"
| "fractionDigits" | "totalDigits" => xsd_parse_facet(node),
"simpleContent" => xsd_parse_simple_content(node, schema),
"complexContent" => xsd_parse_complex_content(node, schema),
_ => {
let mut comp = XsdComponent::new(XsdComponentType::Schema);
if let Ok(s) = std::str::from_utf8(std::slice::from_raw_parts((*node).name, {
let mut len = 0;
while *(*node).name.add(len) != 0 {
len += 1;
}
len
})) {
comp.name = Some(s.to_string());
}
comp
}
}
}
}
unsafe fn xsd_parse_element(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Element);
comp.name = get_attr(node, "name");
comp.ref_name = get_attr(node, "ref");
comp.min_occurs = get_attr_occurs(node, "minOccurs", 1);
comp.max_occurs = get_attr_occurs(node, "maxOccurs", 1);
comp.is_abstract = get_attr_bool(node, "abstract");
comp.is_final = get_attr_bool(node, "final");
comp.substitution_group = get_attr(node, "substitutionGroup");
comp.form = get_attr(node, "form");
if let Some(type_name) = get_attr(node, "type") {
comp.datatype = parse_datatype_kind(&type_name);
if comp.datatype.is_none() {
comp.base = Some(type_name);
}
}
let _default = get_attr(node, "default");
let _fixed = get_attr(node, "fixed");
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::ComplexType | XsdComponentType::SimpleType => {
if let Some(ref name) = child_comp.name {
comp.base = Some(name.clone());
}
comp.children.push(child_comp);
}
XsdComponentType::Annotation => {
}
_ => {
comp.children.push(child_comp);
}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_attribute_node(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Attribute);
comp.name = get_attr(node, "name");
comp.ref_name = get_attr(node, "ref");
comp.form = get_attr(node, "form");
if let Some(type_name) = get_attr(node, "type") {
comp.datatype = parse_datatype_kind(&type_name);
if comp.datatype.is_none() {
comp.base = Some(type_name);
}
}
let use_attr = get_attr(node, "use");
if let Some(ref use_val) = use_attr {
if use_val == "required" {
comp.min_occurs = 1;
} else if use_val == "prohibited" {
comp.min_occurs = 0;
comp.max_occurs = 0;
} else {
comp.min_occurs = 0;
}
} else {
comp.min_occurs = 0; }
let _default = get_attr(node, "default");
let _fixed = get_attr(node, "fixed");
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::SimpleType => {
if let Some(ref name) = child_comp.name {
comp.base = Some(name.clone());
}
comp.children.push(child_comp);
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_complex_type(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::ComplexType);
comp.name = get_attr(node, "name");
comp.mixed = get_attr_bool(node, "mixed");
comp.is_abstract = get_attr_bool(node, "abstract");
comp.is_final = get_attr_bool(node, "final");
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::SimpleContent
| XsdComponentType::ComplexContent
| XsdComponentType::Sequence
| XsdComponentType::Choice
| XsdComponentType::All
| XsdComponentType::Group
| XsdComponentType::Any
| XsdComponentType::Annotation => {
if child_comp.component_type == XsdComponentType::SimpleContent {
comp.children.extend(child_comp.children);
} else if child_comp.component_type == XsdComponentType::ComplexContent {
comp.children.extend(child_comp.children);
} else {
comp.children.push(child_comp);
}
}
XsdComponentType::Attribute | XsdComponentType::AnyAttribute => {
comp.attributes.push(child_comp);
}
_ => {
comp.children.push(child_comp);
}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_simple_type(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::SimpleType);
comp.name = get_attr(node, "name");
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Restriction
| XsdComponentType::List
| XsdComponentType::Union => {
comp.datatype = child_comp.datatype;
comp.base = child_comp.base;
comp.facets = child_comp.facets;
comp.children.extend(child_comp.children);
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_model_group(
node: *mut _xmlNode,
ctype: XsdComponentType,
schema: &XsdSchema,
) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(ctype);
comp.min_occurs = get_attr_occurs(node, "minOccurs", 1);
comp.max_occurs = get_attr_occurs(node, "maxOccurs", 1);
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Annotation => {}
_ => {
comp.children.push(child_comp);
}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_restriction(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Restriction);
comp.base = get_attr(node, "base");
if let Some(ref base_name) = comp.base {
comp.datatype = parse_datatype_kind(base_name);
}
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Sequence
| XsdComponentType::Choice
| XsdComponentType::All
| XsdComponentType::Group
| XsdComponentType::Any
| XsdComponentType::Annotation => {
comp.children.push(child_comp);
}
XsdComponentType::Attribute | XsdComponentType::AnyAttribute => {
comp.attributes.push(child_comp);
}
XsdComponentType::SimpleType => {
comp.children.push(child_comp);
}
_ => {
if let Some(facet_kind) =
parse_facet_kind(&format!("{:?}", child_comp.component_type))
{
if let Some(val) = get_attr(child, "value") {
comp.facets.push((facet_kind, val));
}
}
let name_str = if !(*child).name.is_null() {
let mut len = 0;
while *(*child).name.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts((*child).name, len);
std::str::from_utf8(slice)
.ok()
.map(|s| {
if let Some(pos) = s.find(':') {
s[pos + 1..].to_string()
} else {
s.to_string()
}
})
.unwrap_or_default()
} else {
String::new()
};
if !name_str.is_empty() {
if let Some(facet_kind) = parse_facet_kind(&name_str) {
if let Some(val) = get_attr(child, "value") {
comp.facets.push((facet_kind, val));
}
}
}
}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_extension(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Extension);
comp.base = get_attr(node, "base");
if let Some(ref base_name) = comp.base {
comp.datatype = parse_datatype_kind(base_name);
}
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Sequence
| XsdComponentType::Choice
| XsdComponentType::All
| XsdComponentType::Group
| XsdComponentType::Any
| XsdComponentType::Annotation => {
comp.children.push(child_comp);
}
XsdComponentType::Attribute | XsdComponentType::AnyAttribute => {
comp.attributes.push(child_comp);
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_list(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::List);
if let Some(item_type) = get_attr(node, "itemType") {
comp.base = Some(item_type.clone());
comp.datatype = parse_datatype_kind(&item_type);
}
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::SimpleType => {
comp.datatype = child_comp.datatype;
comp.base = child_comp.base;
comp.facets = child_comp.facets;
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_union(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Union);
if let Some(member_types) = get_attr(node, "memberTypes") {
comp.base = Some(member_types);
}
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::SimpleType => {
comp.children.push(child_comp);
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_annotation(node: *mut _xmlNode) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Annotation);
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
if node_is(child, "documentation") || node_is(child, "appinfo") {
let text = get_node_text(child);
if !text.is_empty() {
comp.facets.push((XsdDatatypeKind::String, text));
}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_any(node: *mut _xmlNode, _schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Any);
comp.min_occurs = get_attr_occurs(node, "minOccurs", 1);
comp.max_occurs = get_attr_occurs(node, "maxOccurs", 1);
let namespace_attr = get_attr(node, "namespace");
if let Some(ref ns) = namespace_attr {
if ns != "##any" {
comp.target_namespace = Some(ns.clone());
}
}
comp
}
}
unsafe fn xsd_parse_group(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Group);
comp.name = get_attr(node, "name");
comp.ref_name = get_attr(node, "ref");
comp.min_occurs = get_attr_occurs(node, "minOccurs", 1);
comp.max_occurs = get_attr_occurs(node, "maxOccurs", 1);
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Annotation => {}
_ => {
comp.children.push(child_comp);
}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_attribute_group(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::AttributeGroup);
comp.name = get_attr(node, "name");
comp.ref_name = get_attr(node, "ref");
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Attribute | XsdComponentType::AnyAttribute => {
comp.attributes.push(child_comp);
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_facet(node: *mut _xmlNode) -> XsdComponent {
unsafe {
let name_str = if !(*node).name.is_null() {
let mut len = 0;
while *(*node).name.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts((*node).name, len);
std::str::from_utf8(slice)
.ok()
.map(|s| {
if let Some(pos) = s.find(':') {
s[pos + 1..].to_string()
} else {
s.to_string()
}
})
.unwrap_or_default()
} else {
String::new()
};
let facet_kind = parse_facet_kind(&name_str).unwrap_or(XsdDatatypeKind::String);
let mut comp = XsdComponent::new(XsdComponentType::Schema);
let val = get_attr(node, "value").unwrap_or_default();
comp.facets.push((facet_kind, val));
comp.datatype = Some(facet_kind);
comp
}
}
unsafe fn xsd_parse_simple_content(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Schema);
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Restriction | XsdComponentType::Extension => {
comp.children.push(child_comp);
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_complex_content(node: *mut _xmlNode, schema: &XsdSchema) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(XsdComponentType::Schema);
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Restriction | XsdComponentType::Extension => {
comp.children.push(child_comp);
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
unsafe fn xsd_parse_identity_constraint(
node: *mut _xmlNode,
ctype: XsdComponentType,
schema: &XsdSchema,
) -> XsdComponent {
unsafe {
let mut comp = XsdComponent::new(ctype);
comp.name = get_attr(node, "name");
if ctype == XsdComponentType::KeyRef {
comp.ref_name = get_attr(node, "refer");
}
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_comp = xsd_parse_component(child, schema);
match child_comp.component_type {
XsdComponentType::Selector | XsdComponentType::Field => {
comp.children.push(child_comp);
}
_ => {}
}
}
child = (*child).next;
}
comp
}
}
pub fn xsd_validate_datatype(
kind: &XsdDatatypeKind,
value: &str,
facets: &[(XsdDatatypeKind, String)],
) -> bool {
if !validate_base_type(kind, value) {
return false;
}
let mut has_enumeration = false;
let mut enumeration_match = false;
for (facet_kind, facet_value) in facets {
if *facet_kind == XsdDatatypeKind::FacetEnumeration {
has_enumeration = true;
if xsd_validate_facet(kind, value, facet_kind, facet_value) {
enumeration_match = true;
}
} else if !xsd_validate_facet(kind, value, facet_kind, facet_value) {
return false;
}
}
if has_enumeration && !enumeration_match {
return false;
}
true
}
pub fn xsd_validate_facet(
_kind: &XsdDatatypeKind,
value: &str,
facet_kind: &XsdDatatypeKind,
facet_value: &str,
) -> bool {
match facet_kind {
XsdDatatypeKind::FacetPattern => {
match facet_value {
r"\d+" => value.chars().all(|c| c.is_ascii_digit()),
r"\d*" => value.is_empty() || value.chars().all(|c| c.is_ascii_digit()),
r"[a-zA-Z]+" => value.chars().all(|c| c.is_ascii_alphabetic()),
r"[a-zA-Z]*" => value.is_empty() || value.chars().all(|c| c.is_ascii_alphabetic()),
r"[a-zA-Z0-9]+" => value.chars().all(|c| c.is_ascii_alphanumeric()),
r"[a-zA-Z0-9_\-]+" => value
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-'),
r"[a-zA-Z_][a-zA-Z0-9_\-\.]*" => {
if value.is_empty() {
return false;
}
let first = value.chars().next().unwrap();
if !first.is_ascii_alphabetic() && first != '_' {
return false;
}
value
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
}
r"[a-zA-Z_][\w\-\.]*" => {
if value.is_empty() {
return false;
}
let first = value.chars().next().unwrap();
if !first.is_ascii_alphabetic() && first != '_' {
return false;
}
value
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')
}
r"\i\c*" => {
if value.is_empty() {
return false;
}
let first = value.chars().next().unwrap();
if !is_name_start_char(first) {
return false;
}
value.chars().skip(1).all(is_name_char)
}
r"\c+" => {
if value.is_empty() {
return false;
}
value.chars().all(is_name_char)
}
_ => {
if facet_value.starts_with('^') && facet_value.ends_with('$') {
let inner = &facet_value[1..facet_value.len() - 1];
simple_glob_match(inner, value)
} else {
true
}
}
}
}
XsdDatatypeKind::FacetEnumeration => {
value == facet_value
}
XsdDatatypeKind::FacetMinInclusive => {
compare_strings(value, facet_value) != std::cmp::Ordering::Less
}
XsdDatatypeKind::FacetMaxInclusive => {
compare_strings(value, facet_value) != std::cmp::Ordering::Greater
}
XsdDatatypeKind::FacetMinExclusive => {
compare_strings(value, facet_value) == std::cmp::Ordering::Greater
}
XsdDatatypeKind::FacetMaxExclusive => {
compare_strings(value, facet_value) == std::cmp::Ordering::Less
}
XsdDatatypeKind::FacetMinLength => {
let min = facet_value.parse::<usize>().unwrap_or(0);
value.chars().count() >= min
}
XsdDatatypeKind::FacetMaxLength => {
let max = facet_value.parse::<usize>().unwrap_or(usize::MAX);
value.chars().count() <= max
}
XsdDatatypeKind::FacetLength => {
let len = facet_value.parse::<usize>().unwrap_or(0);
value.chars().count() == len
}
XsdDatatypeKind::FacetWhiteSpace => {
match facet_value {
"replace" => {
true
}
"collapse" => {
true
}
_ => true,
}
}
XsdDatatypeKind::FacetFractionDigits | XsdDatatypeKind::FacetTotalDigits => {
value.parse::<f64>().is_ok()
}
_ => true,
}
}
fn simple_glob_match(pattern: &str, value: &str) -> bool {
let pattern_chars: Vec<char> = pattern.chars().collect();
let value_chars: Vec<char> = value.chars().collect();
let mut pi = 0;
let mut vi = 0;
let mut backtrack_p = None;
let mut backtrack_v = 0;
while vi < value_chars.len() {
if pi < pattern_chars.len()
&& (pattern_chars[pi] == value_chars[vi] || pattern_chars[pi] == '.')
{
pi += 1;
vi += 1;
} else if pi < pattern_chars.len() && pattern_chars[pi] == '*' {
backtrack_p = Some(pi);
backtrack_v = vi + 1;
pi += 1;
} else if pi < pattern_chars.len() && pattern_chars[pi] == '+' {
if pi + 1 < pattern_chars.len() && pattern_chars[pi + 1] == value_chars[vi] {
pi += 1;
vi += 1;
while vi < value_chars.len() && value_chars[vi] == pattern_chars[pi] {
vi += 1;
}
pi += 1;
} else {
return false;
}
} else if let Some(bp) = backtrack_p {
pi = bp + 1;
vi = backtrack_v;
backtrack_v += 1;
} else {
return false;
}
}
while pi < pattern_chars.len() && (pattern_chars[pi] == '*' || pattern_chars[pi] == '+') {
if pattern_chars[pi] == '+' && vi == value_chars.len() {
return false; }
pi += 1;
}
pi == pattern_chars.len()
}
fn is_name_start_char(c: char) -> bool {
c.is_ascii_alphabetic()
|| c == '_'
|| c == ':'
|| (c >= '\u{00C0}' && c <= '\u{00D6}')
|| (c >= '\u{00D8}' && c <= '\u{00F6}')
|| (c >= '\u{00F8}' && c <= '\u{02FF}')
|| (c >= '\u{0370}' && c <= '\u{037D}')
|| (c >= '\u{037F}' && c <= '\u{1FFF}')
|| (c >= '\u{200C}' && c <= '\u{200D}')
|| (c >= '\u{2070}' && c <= '\u{218F}')
|| (c >= '\u{2C00}' && c <= '\u{2FEF}')
|| (c >= '\u{3001}' && c <= '\u{D7FF}')
|| (c >= '\u{F900}' && c <= '\u{FDCF}')
|| (c >= '\u{FDF0}' && c <= '\u{FFFD}')
}
fn is_name_char(c: char) -> bool {
is_name_start_char(c)
|| c.is_ascii_digit()
|| c == '-'
|| c == '.'
|| c == '\u{00B7}'
|| (c >= '\u{0300}' && c <= '\u{036F}')
|| (c >= '\u{203F}' && c <= '\u{2040}')
}
fn compare_strings(a: &str, b: &str) -> std::cmp::Ordering {
if let (Ok(na), Ok(nb)) = (a.parse::<f64>(), b.parse::<f64>()) {
return na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal);
}
if let (Ok(na), Ok(nb)) = (a.parse::<i64>(), b.parse::<i64>()) {
return na.cmp(&nb);
}
a.cmp(b)
}
fn validate_base_type(kind: &XsdDatatypeKind, value: &str) -> bool {
match kind {
XsdDatatypeKind::String => true,
XsdDatatypeKind::NormalizedString => {
!value.contains('\t') && !value.contains('\n') && !value.contains('\r')
}
XsdDatatypeKind::Token => {
if value.is_empty() {
return true;
}
if value.starts_with(' ') || value.ends_with(' ') {
return false;
}
!value.contains(" ")
&& !value.contains('\t')
&& !value.contains('\n')
&& !value.contains('\r')
}
XsdDatatypeKind::Language => {
if value.is_empty() {
return false;
}
let segments: Vec<&str> = value.split('-').collect();
if segments.is_empty() {
return false;
}
if segments[0].is_empty() || !segments[0].chars().all(|c| c.is_ascii_alphabetic()) {
return false;
}
if segments[0].len() > 8 {
return false;
}
for seg in &segments[1..] {
if seg.is_empty() || seg.len() > 8 {
return false;
}
if !seg.chars().all(|c| c.is_ascii_alphanumeric()) {
return false;
}
}
true
}
XsdDatatypeKind::Name => {
if value.is_empty() {
return false;
}
let mut chars = value.chars();
let first = chars.next().unwrap();
if !is_name_start_char(first) {
return false;
}
chars.all(is_name_char)
}
XsdDatatypeKind::NCName
| XsdDatatypeKind::Id
| XsdDatatypeKind::Idref
| XsdDatatypeKind::Entity => {
if value.is_empty() || value.contains(':') {
return false;
}
let mut chars = value.chars();
let first = chars.next().unwrap();
if !is_name_start_char(first) {
return false;
}
chars.all(is_name_char)
}
XsdDatatypeKind::Boolean => {
matches!(value, "true" | "false" | "1" | "0")
}
XsdDatatypeKind::Decimal
| XsdDatatypeKind::Integer
| XsdDatatypeKind::NonPositiveInteger
| XsdDatatypeKind::NegativeInteger
| XsdDatatypeKind::Long
| XsdDatatypeKind::Int
| XsdDatatypeKind::Short
| XsdDatatypeKind::Byte
| XsdDatatypeKind::NonNegativeInteger
| XsdDatatypeKind::UnsignedLong
| XsdDatatypeKind::UnsignedInt
| XsdDatatypeKind::UnsignedShort
| XsdDatatypeKind::UnsignedByte
| XsdDatatypeKind::PositiveInteger => {
if value.is_empty() {
return false;
}
let mut chars = value.chars().peekable();
if *chars.peek().unwrap_or(&'\0') == '-' || *chars.peek().unwrap_or(&'\0') == '+' {
chars.next();
}
let mut has_dot = false;
let mut has_digit = false;
for c in chars {
if c == '.' {
if has_dot {
return false;
}
has_dot = true;
} else if c.is_ascii_digit() {
has_digit = true;
} else {
return false;
}
}
if !has_digit {
return false;
}
if has_dot && *kind != XsdDatatypeKind::Decimal {
return false;
}
match kind {
XsdDatatypeKind::NonPositiveInteger => {
if let Ok(v) = value.parse::<i64>() {
v <= 0
} else {
false
}
}
XsdDatatypeKind::NegativeInteger => {
if let Ok(v) = value.parse::<i64>() {
v < 0
} else {
false
}
}
XsdDatatypeKind::NonNegativeInteger => {
if let Ok(v) = value.parse::<i64>() {
v >= 0
} else {
false
}
}
XsdDatatypeKind::PositiveInteger => {
if let Ok(v) = value.parse::<i64>() {
v > 0
} else {
false
}
}
XsdDatatypeKind::UnsignedLong
| XsdDatatypeKind::UnsignedInt
| XsdDatatypeKind::UnsignedShort
| XsdDatatypeKind::UnsignedByte => {
if let Ok(v) = value.parse::<u64>() {
match kind {
XsdDatatypeKind::UnsignedInt => v <= u64::from(u32::MAX),
XsdDatatypeKind::UnsignedShort => v <= u64::from(u16::MAX),
XsdDatatypeKind::UnsignedByte => v <= u64::from(u8::MAX),
_ => true,
}
} else {
false
}
}
XsdDatatypeKind::Long => value.parse::<i64>().is_ok(),
XsdDatatypeKind::Int => value.parse::<i32>().is_ok(),
XsdDatatypeKind::Short => value.parse::<i16>().is_ok(),
XsdDatatypeKind::Byte => value.parse::<i8>().is_ok(),
_ => true,
}
}
XsdDatatypeKind::Float | XsdDatatypeKind::Double => {
matches!(value, "INF" | "-INF" | "NaN") || value.parse::<f64>().is_ok()
}
XsdDatatypeKind::Duration => {
if !value.starts_with('-') && !value.starts_with('P') {
return false;
}
let dur = if value.starts_with('-') {
&value[1..]
} else {
value
};
if !dur.starts_with('P') {
return false;
}
let rest = &dur[1..];
if rest.is_empty() {
return false;
}
let has_t = rest.contains('T');
let date_part = if has_t {
&rest[..rest.find('T').unwrap()]
} else {
rest
};
if has_t {
let time_part = &rest[rest.find('T').unwrap() + 1..];
if time_part.is_empty() {
return false;
}
}
true
}
XsdDatatypeKind::DateTime => {
if value.len() < 19 {
return false;
}
let chars: Vec<char> = value.chars().collect();
chars[4] == '-'
&& chars[7] == '-'
&& chars[10] == 'T'
&& chars[13] == ':'
&& chars[16] == ':'
}
XsdDatatypeKind::Date => {
if value.len() < 10 {
return false;
}
let chars: Vec<char> = value.chars().collect();
chars[4] == '-' && chars[7] == '-'
}
XsdDatatypeKind::Time => {
if value.len() < 8 {
return false;
}
let chars: Vec<char> = value.chars().collect();
chars[2] == ':' && chars[5] == ':'
}
XsdDatatypeKind::GYear => {
if value.len() < 4 {
return false;
}
value.chars().take(4).all(|c| c.is_ascii_digit())
}
XsdDatatypeKind::GYearMonth => {
if value.len() < 7 {
return false;
}
let chars: Vec<char> = value.chars().collect();
chars[4] == '-'
}
XsdDatatypeKind::GMonthDay => {
if value.len() < 6 || !value.starts_with("--") {
return false;
}
let chars: Vec<char> = value.chars().collect();
chars[4] == '-'
}
XsdDatatypeKind::GDay => {
value.starts_with("---") && value.len() >= 4
}
XsdDatatypeKind::GMonth => {
value.starts_with("--") && value.len() >= 3
}
XsdDatatypeKind::HexBinary => {
if value.len() % 2 != 0 {
return false;
}
value.chars().all(|c| c.is_ascii_hexdigit())
}
XsdDatatypeKind::Base64Binary => {
if value.is_empty() {
return true;
}
let valid_chars =
|c: char| c.is_ascii_alphanumeric() || c == '+' || c == '/' || c == '=';
value.chars().all(valid_chars)
}
XsdDatatypeKind::AnyURI => {
!value.is_empty()
}
XsdDatatypeKind::QName => {
if let Some(pos) = value.find(':') {
let prefix = &value[..pos];
let local = &value[pos + 1..];
is_ncname(prefix) && is_ncname(local)
} else {
is_ncname(value)
}
}
XsdDatatypeKind::Notation => {
!value.is_empty()
}
XsdDatatypeKind::NormalizedString => {
!value.contains('\t') && !value.contains('\n') && !value.contains('\r')
}
XsdDatatypeKind::Token => {
validate_base_type(&XsdDatatypeKind::NormalizedString, value)
&& !value.starts_with(' ')
&& !value.ends_with(' ')
&& !value.contains(" ")
}
XsdDatatypeKind::Language => {
if value.is_empty() {
return false;
}
let parts: Vec<&str> = value.split('-').collect();
if parts.is_empty() {
return false;
}
if parts[0].len() > 8 || !parts[0].chars().all(|c| c.is_ascii_alphabetic()) {
return false;
}
parts.iter().skip(1).all(|p| {
!p.is_empty() && p.len() <= 8 && p.chars().all(|c| c.is_ascii_alphanumeric())
})
}
XsdDatatypeKind::Nmtoken => !value.is_empty() && value.chars().all(is_name_char),
XsdDatatypeKind::Nmtokens => {
!value.is_empty()
&& value
.split_whitespace()
.all(|t| !t.is_empty() && t.chars().all(is_name_char))
}
XsdDatatypeKind::Name => {
!value.is_empty() && {
let first = value.chars().next().unwrap();
is_name_start_char(first) && value.chars().skip(1).all(is_name_char)
}
}
XsdDatatypeKind::NCName => is_ncname(value),
XsdDatatypeKind::Id | XsdDatatypeKind::Idref | XsdDatatypeKind::Entity => is_ncname(value),
XsdDatatypeKind::Idrefs | XsdDatatypeKind::Entities => {
!value.is_empty() && value.split_whitespace().all(|t| is_ncname(t))
}
XsdDatatypeKind::FacetPattern
| XsdDatatypeKind::FacetEnumeration
| XsdDatatypeKind::FacetMinInclusive
| XsdDatatypeKind::FacetMaxInclusive
| XsdDatatypeKind::FacetMinExclusive
| XsdDatatypeKind::FacetMaxExclusive
| XsdDatatypeKind::FacetMinLength
| XsdDatatypeKind::FacetMaxLength
| XsdDatatypeKind::FacetLength
| XsdDatatypeKind::FacetWhiteSpace
| XsdDatatypeKind::FacetFractionDigits
| XsdDatatypeKind::FacetTotalDigits => true,
}
}
fn is_ncname(value: &str) -> bool {
if value.is_empty() {
return false;
}
let first = value.chars().next().unwrap();
if !is_name_start_char(first) || first == ':' {
return false;
}
value.chars().skip(1).all(|c| is_name_char(c) && c != ':')
}
pub fn xsd_validate(schema: &XsdSchema, doc: &str) -> Result<(), Vec<String>> {
let doc_ptr = unsafe {
crate::abi::exports_xml2::xmlReadMemory(
doc.as_ptr() as *const c_char,
doc.len() as c_int,
b"doc.xml\0".as_ptr() as *const c_char,
ptr::null(),
0,
)
};
if doc_ptr.is_null() {
return Err(vec!["Failed to parse XML document".to_string()]);
}
let mut ctxt = XsdValidCtxt::new();
ctxt.schema = Some(schema.clone());
let result = unsafe { xsd_validate_doc(schema, doc_ptr, &mut ctxt) };
unsafe {
crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
}
if result {
Ok(())
} else {
Err(ctxt.errors)
}
}
unsafe fn xsd_validate_doc(schema: &XsdSchema, doc: *mut _xmlDoc, ctxt: &mut XsdValidCtxt) -> bool {
unsafe {
let root = (*doc).children;
if root.is_null() {
ctxt.errors.push("Document has no root element".to_string());
ctxt.nb_errors += 1;
return false;
}
let mut root_elem = root;
while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
root_elem = (*root_elem).next;
}
if root_elem.is_null() {
ctxt.errors.push("Document has no root element".to_string());
ctxt.nb_errors += 1;
return false;
}
let root_name = get_node_qname(root_elem);
let global_elem = schema.components.iter().find(|c| {
c.component_type == XsdComponentType::Element && c.name.as_deref() == Some(&root_name)
});
if let Some(global) = global_elem {
xsd_validate_element(global, root_elem, schema, ctxt)
} else {
let mut valid = true;
for component in &schema.components {
if component.component_type == XsdComponentType::Element {
if let Some(ref name) = component.name {
if *name == root_name {
valid = xsd_validate_element(component, root_elem, schema, ctxt);
break;
}
}
}
}
valid
}
}
}
fn xsd_validate_element(
component: &XsdComponent,
node: *mut _xmlNode,
schema: &XsdSchema,
ctxt: &mut XsdValidCtxt,
) -> bool {
unsafe {
let mut valid = true;
let node_name = get_node_qname(node);
if let Some(ref comp_name) = component.name {
if comp_name != &node_name {
ctxt.errors.push(format!(
"Element '{}' does not match expected '{}'",
node_name, comp_name
));
ctxt.nb_errors += 1;
return false;
}
}
let type_comp = component.children.iter().find(|c| {
c.component_type == XsdComponentType::ComplexType
|| c.component_type == XsdComponentType::SimpleType
});
if let Some(tc) = type_comp {
match tc.component_type {
XsdComponentType::ComplexType => {
valid &= xsd_validate_complex_type(tc, node, schema, ctxt);
}
XsdComponentType::SimpleType => {
let text = get_node_text(node);
if let Some(ref dt) = tc.datatype {
if !xsd_validate_datatype(dt, &text, &tc.facets) {
ctxt.errors.push(format!(
"Element '{}' has invalid value '{}' for type '{:?}'",
node_name, text, dt
));
ctxt.nb_errors += 1;
valid = false;
}
}
}
_ => {}
}
} else if let Some(ref dt) = component.datatype {
let text = get_node_text(node);
if !xsd_validate_datatype(dt, &text, &component.facets) {
ctxt.errors.push(format!(
"Element '{}' has invalid value '{}' for type '{:?}'",
node_name, text, dt
));
ctxt.nb_errors += 1;
valid = false;
}
} else {
valid &= xsd_validate_content(component, node, schema, ctxt);
}
valid
}
}
fn xsd_validate_complex_type(
component: &XsdComponent,
node: *mut _xmlNode,
schema: &XsdSchema,
ctxt: &mut XsdValidCtxt,
) -> bool {
unsafe {
let mut valid = true;
for attr in &component.attributes {
match attr.component_type {
XsdComponentType::Attribute => {
valid &= xsd_validate_attribute(attr, node, schema, ctxt);
}
XsdComponentType::AnyAttribute => {
}
_ => {}
}
}
for child in &component.children {
match child.component_type {
XsdComponentType::Sequence | XsdComponentType::Choice | XsdComponentType::All => {
valid &= xsd_validate_model_group(child, node, schema, ctxt);
}
XsdComponentType::Restriction | XsdComponentType::Extension => {
valid &= xsd_validate_restriction_extension(child, node, schema, ctxt);
}
XsdComponentType::Any => {
}
_ => {}
}
}
valid
}
}
fn xsd_validate_model_group(
component: &XsdComponent,
node: *mut _xmlNode,
schema: &XsdSchema,
ctxt: &mut XsdValidCtxt,
) -> bool {
unsafe {
let mut valid = true;
let mut child_nodes: Vec<*mut _xmlNode> = Vec::new();
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
child_nodes.push(child);
}
child = (*child).next;
}
match component.component_type {
XsdComponentType::Sequence => {
let mut child_idx = 0;
for part in &component.children {
let min = part.min_occurs;
let max = part.max_occurs;
let match_name = part.name.as_deref().unwrap_or("");
let match_ref = part.ref_name.as_deref().unwrap_or("");
let mut count = 0;
while child_idx < child_nodes.len() && (max == -1 || count < max) {
let child_node = child_nodes[child_idx];
let child_name = get_node_qname(child_node);
if part.component_type == XsdComponentType::Any {
count += 1;
child_idx += 1;
} else if !match_name.is_empty() && child_name == match_name {
count += 1;
child_idx += 1;
} else if !match_ref.is_empty() && child_name == match_ref {
count += 1;
child_idx += 1;
} else if count >= min {
break;
} else {
ctxt.errors.push(format!(
"Expected element '{}' but found '{}'",
match_name, child_name
));
ctxt.nb_errors += 1;
valid = false;
child_idx += 1;
break;
}
}
if count < min {
ctxt.errors.push(format!(
"Element '{}' occurs {} times, minimum is {}",
if match_name.is_empty() {
"?"
} else {
match_name
},
count,
min
));
ctxt.nb_errors += 1;
valid = false;
}
}
if child_idx < child_nodes.len() {
let extra = get_node_qname(child_nodes[child_idx]);
ctxt.errors
.push(format!("Unexpected element '{}' in sequence", extra));
ctxt.nb_errors += 1;
valid = false;
}
}
XsdComponentType::Choice => {
let mut matched = false;
for child_node in &child_nodes {
let child_name = get_node_qname(*child_node);
for part in &component.children {
let match_name = part.name.as_deref().unwrap_or("");
let match_ref = part.ref_name.as_deref().unwrap_or("");
if part.component_type == XsdComponentType::Any {
matched = true;
} else if (!match_name.is_empty() && child_name == match_name)
|| (!match_ref.is_empty() && child_name == match_ref)
{
matched = true;
break;
}
}
if !matched {
ctxt.errors
.push(format!("Element '{}' is not valid in choice", child_name));
ctxt.nb_errors += 1;
valid = false;
}
matched = false; }
}
XsdComponentType::All => {
for child_node in &child_nodes {
let child_name = get_node_qname(*child_node);
let mut matched = false;
for part in &component.children {
let match_name = part.name.as_deref().unwrap_or("");
if !match_name.is_empty() && child_name == match_name {
matched = true;
break;
}
}
if !matched {
ctxt.errors.push(format!(
"Element '{}' is not valid in all group",
child_name
));
ctxt.nb_errors += 1;
valid = false;
}
}
}
_ => {}
}
valid
}
}
fn xsd_validate_restriction_extension(
component: &XsdComponent,
node: *mut _xmlNode,
schema: &XsdSchema,
ctxt: &mut XsdValidCtxt,
) -> bool {
unsafe {
let mut valid = true;
for attr in &component.attributes {
match attr.component_type {
XsdComponentType::Attribute => {
valid &= xsd_validate_attribute(attr, node, schema, ctxt);
}
_ => {}
}
}
for child in &component.children {
match child.component_type {
XsdComponentType::Sequence | XsdComponentType::Choice | XsdComponentType::All => {
valid &= xsd_validate_model_group(child, node, schema, ctxt);
}
_ => {}
}
}
if let Some(ref dt) = component.datatype {
let text = get_node_text(node);
if !xsd_validate_datatype(dt, &text, &component.facets) {
let node_name = get_node_qname(node);
ctxt.errors.push(format!(
"Element '{}' has invalid value '{}' for type '{:?}'",
node_name, text, dt
));
ctxt.nb_errors += 1;
valid = false;
}
}
valid
}
}
fn xsd_validate_attribute(
component: &XsdComponent,
node: *mut _xmlNode,
_schema: &XsdSchema,
ctxt: &mut XsdValidCtxt,
) -> bool {
unsafe {
let attr_name = component.name.as_deref().unwrap_or("");
if attr_name.is_empty() {
return true;
}
let attr_value = get_attr(node, attr_name);
let is_required = component.min_occurs > 0;
match attr_value {
Some(ref val) => {
if let Some(ref dt) = component.datatype {
if !xsd_validate_datatype(dt, val, &component.facets) {
ctxt.errors.push(format!(
"Attribute '{}' has invalid value '{}' for type '{:?}'",
attr_name, val, dt
));
ctxt.nb_errors += 1;
return false;
}
}
true
}
None => {
if is_required {
ctxt.errors
.push(format!("Required attribute '{}' is missing", attr_name));
ctxt.nb_errors += 1;
false
} else {
true
}
}
}
}
}
fn xsd_validate_content(
component: &XsdComponent,
node: *mut _xmlNode,
schema: &XsdSchema,
ctxt: &mut XsdValidCtxt,
) -> bool {
unsafe {
let mut valid = true;
for child_comp in &component.children {
match child_comp.component_type {
XsdComponentType::Sequence | XsdComponentType::Choice | XsdComponentType::All => {
valid &= xsd_validate_model_group(child_comp, node, schema, ctxt);
}
XsdComponentType::Element => {
valid &= xsd_validate_element_inline(child_comp, node, schema, ctxt);
}
_ => {}
}
}
valid
}
}
fn xsd_validate_element_inline(
component: &XsdComponent,
node: *mut _xmlNode,
schema: &XsdSchema,
ctxt: &mut XsdValidCtxt,
) -> bool {
unsafe {
let mut valid = true;
let mut child = (*node).children;
while !child.is_null() {
if (*child).type_ == XML_ELEMENT_NODE as c_int {
let child_name = get_node_qname(child);
let match_name = component.name.as_deref().unwrap_or("");
let match_ref = component.ref_name.as_deref().unwrap_or("");
if (!match_name.is_empty() && child_name == match_name)
|| (!match_ref.is_empty() && child_name == match_ref)
{
let type_comp = component.children.iter().find(|c| {
c.component_type == XsdComponentType::ComplexType
|| c.component_type == XsdComponentType::SimpleType
});
if let Some(tc) = type_comp {
match tc.component_type {
XsdComponentType::ComplexType => {
valid &= xsd_validate_complex_type(tc, child, schema, ctxt);
}
XsdComponentType::SimpleType => {
let text = get_node_text(child);
if let Some(ref dt) = tc.datatype {
if !xsd_validate_datatype(dt, &text, &tc.facets) {
ctxt.errors.push(format!(
"Element '{}' has invalid value '{}'",
child_name, text
));
ctxt.nb_errors += 1;
valid = false;
}
}
}
_ => {}
}
}
}
}
child = (*child).next;
}
valid
}
}
unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
if node.is_null() {
return String::new();
}
unsafe {
let ns = (*node).ns;
let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
let mut len = 0;
while *(*ns).prefix.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts((*ns).prefix, len);
if let Ok(s) = std::str::from_utf8(slice) {
format!("{}:", s)
} else {
String::new()
}
} else {
String::new()
};
let name = (*node).name;
if name.is_null() {
return String::new();
}
let mut len = 0;
while *name.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts(name, len);
if let Ok(s) = std::str::from_utf8(slice) {
format!("{}{}", prefix, s)
} else {
String::new()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaNewParserCtxt(url: *const c_char) -> *mut c_void {
if url.is_null() {
let ctxt = allocator::xmlMallocZero(size_of::<XsdSchema>() as usize);
return ctxt;
}
let url_str = unsafe {
if url.is_null() {
String::new()
} else {
let mut len = 0;
while *url.add(len) != 0 {
len += 1;
}
let slice = std::slice::from_raw_parts(url as *const u8, len);
String::from_utf8_lossy(slice).to_string()
}
};
let ctxt = allocator::xmlMallocZero(size_of::<XsdSchema>() as usize);
if !url_str.is_empty() {
let _ = url_str;
}
ctxt
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaNewMemParserCtxt(
buffer: *const c_char,
size: c_int,
) -> *mut c_void {
if buffer.is_null() || size <= 0 {
return ptr::null_mut();
}
let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
let xml_str = String::from_utf8_lossy(buf_slice).to_string();
match xsd_parse(&xml_str) {
Ok(schema) => {
let schema_box = Box::new(schema);
Box::into_raw(schema_box) as *mut c_void
}
Err(_) => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaParse(ctxt: *mut c_void) -> *mut c_void {
if ctxt.is_null() {
return ptr::null_mut();
}
ctxt
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaFree(schema: *mut c_void) {
if schema.is_null() {
return;
}
unsafe {
let _ = Box::from_raw(schema as *mut XsdSchema);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
if ctxt.is_null() || doc.is_null() {
return -1;
}
unsafe {
let valid_ctxt = &mut *(ctxt as *mut XsdValidCtxt);
let schema = match &valid_ctxt.schema {
Some(s) => s,
None => return -1,
};
let mut temp_ctxt = XsdValidCtxt::new();
temp_ctxt.schema = Some(schema.clone());
let valid = xsd_validate_doc(schema, doc, &mut temp_ctxt);
if valid {
0
} else {
valid_ctxt.errors = temp_ctxt.errors;
valid_ctxt.nb_errors = temp_ctxt.nb_errors;
temp_ctxt.nb_errors
}
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaFreeParserCtxt(ctxt: *mut c_void) {
if ctxt.is_null() {
return;
}
unsafe {
let _ = Box::from_raw(ctxt as *mut XsdSchema);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaFreeValidCtxt(ctxt: *mut c_void) {
if ctxt.is_null() {
return;
}
unsafe {
let _ = Box::from_raw(ctxt as *mut XsdValidCtxt);
}
}
#[no_mangle]
pub unsafe extern "C" fn xmlSchemaNewValidCtxt(schema: *mut c_void) -> *mut c_void {
let mut ctxt = XsdValidCtxt::new();
if !schema.is_null() {
unsafe {
let schema_ref = &*(schema as *const XsdSchema);
ctxt.schema = Some(schema_ref.clone());
}
}
let boxed = Box::new(ctxt);
Box::into_raw(boxed) as *mut c_void
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_string() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::String,
"hello",
&[]
));
assert!(xsd_validate_datatype(&XsdDatatypeKind::String, "", &[]));
}
#[test]
fn test_validate_boolean() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Boolean,
"true",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Boolean,
"false",
&[]
));
assert!(xsd_validate_datatype(&XsdDatatypeKind::Boolean, "1", &[]));
assert!(xsd_validate_datatype(&XsdDatatypeKind::Boolean, "0", &[]));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Boolean,
"yes",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Boolean, "no", &[]));
}
#[test]
fn test_validate_integer() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::Integer, "42", &[]));
assert!(xsd_validate_datatype(&XsdDatatypeKind::Integer, "-42", &[]));
assert!(xsd_validate_datatype(&XsdDatatypeKind::Integer, "+42", &[]));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Integer,
"12.5",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Integer, "", &[]));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Integer,
"abc",
&[]
));
}
#[test]
fn test_validate_decimal() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::Decimal, "42", &[]));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Decimal,
"12.5",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Decimal,
"-3.14",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Decimal, "", &[]));
}
#[test]
fn test_validate_float() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::Float, "3.14", &[]));
assert!(xsd_validate_datatype(&XsdDatatypeKind::Float, "INF", &[]));
assert!(xsd_validate_datatype(&XsdDatatypeKind::Float, "-INF", &[]));
assert!(xsd_validate_datatype(&XsdDatatypeKind::Float, "NaN", &[]));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Float, "", &[]));
}
#[test]
fn test_validate_positive_integer() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::PositiveInteger,
"1",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::PositiveInteger,
"100",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::PositiveInteger,
"0",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::PositiveInteger,
"-1",
&[]
));
}
#[test]
fn test_validate_non_negative_integer() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::NonNegativeInteger,
"0",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::NonNegativeInteger,
"42",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::NonNegativeInteger,
"-1",
&[]
));
}
#[test]
fn test_validate_int_range() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Int,
"2147483647",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Int,
"-2147483648",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Int,
"2147483648",
&[]
));
}
#[test]
fn test_validate_short_range() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::Short, "32767", &[]));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Short,
"-32768",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Short,
"32768",
&[]
));
}
#[test]
fn test_validate_byte_range() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::Byte, "127", &[]));
assert!(xsd_validate_datatype(&XsdDatatypeKind::Byte, "-128", &[]));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Byte, "128", &[]));
}
#[test]
fn test_validate_date_time() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::DateTime,
"2023-01-15T10:30:00",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::DateTime,
"not-a-date",
&[]
));
}
#[test]
fn test_validate_date() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Date,
"2023-01-15",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Date,
"2023/01/15",
&[]
));
}
#[test]
fn test_validate_time() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Time,
"10:30:00",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Time, "10:30", &[]));
}
#[test]
fn test_validate_hex_binary() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::HexBinary,
"0FA1",
&[]
));
assert!(xsd_validate_datatype(&XsdDatatypeKind::HexBinary, "", &[]));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::HexBinary,
"0FG1",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::HexBinary,
"0FA",
&[]
));
}
#[test]
fn test_validate_base64() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Base64Binary,
"SGVsbG8=",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Base64Binary,
"",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Base64Binary,
"Hello World!",
&[]
));
}
#[test]
fn test_validate_ncname() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::NCName,
"myElement",
&[]
));
assert!(xsd_validate_datatype(&XsdDatatypeKind::NCName, "_foo", &[]));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::NCName,
"123abc",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::NCName, "", &[]));
}
#[test]
fn test_validate_qname() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::QName,
"ns:local",
&[]
));
assert!(xsd_validate_datatype(&XsdDatatypeKind::QName, "local", &[]));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::QName, "", &[]));
}
#[test]
fn test_validate_token() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::Token, "hello", &[]));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Token,
" hello",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Token,
"hello ",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Token,
"hello world",
&[]
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Token,
"hello\tworld",
&[]
));
}
#[test]
fn test_validate_language() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::Language, "en", &[]));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Language,
"en-US",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Language,
"zh-CN",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Language, "", &[]));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Language,
"123",
&[]
));
}
#[test]
fn test_validate_name() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Name,
"myElement",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Name,
"ns:local",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Name, "", &[]));
}
#[test]
fn test_validate_nmtoken() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Nmtoken,
"token123",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Nmtoken,
"123token",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Nmtoken, "", &[]));
}
#[test]
fn test_validate_duration() {
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Duration,
"P1Y2M3DT4H5M6S",
&[]
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Duration,
"P1Y",
&[]
));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::Duration, "", &[]));
}
#[test]
fn test_validate_g_year() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::GYear, "2023", &[]));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::GYear, "", &[]));
}
#[test]
fn test_validate_g_month() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::GMonth, "--05", &[]));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::GMonth, "", &[]));
}
#[test]
fn test_validate_g_day() {
assert!(xsd_validate_datatype(&XsdDatatypeKind::GDay, "---15", &[]));
assert!(!xsd_validate_datatype(&XsdDatatypeKind::GDay, "", &[]));
}
#[test]
fn test_facet_min_length() {
let facets = vec![(XsdDatatypeKind::FacetMinLength, "3".to_string())];
assert!(xsd_validate_datatype(
&XsdDatatypeKind::String,
"hello",
&facets
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::String,
"abc",
&facets
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::String,
"ab",
&facets
));
}
#[test]
fn test_facet_max_length() {
let facets = vec![(XsdDatatypeKind::FacetMaxLength, "3".to_string())];
assert!(xsd_validate_datatype(
&XsdDatatypeKind::String,
"ab",
&facets
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::String,
"abc",
&facets
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::String,
"abcd",
&facets
));
}
#[test]
fn test_facet_length() {
let facets = vec![(XsdDatatypeKind::FacetLength, "3".to_string())];
assert!(xsd_validate_datatype(
&XsdDatatypeKind::String,
"abc",
&facets
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::String,
"ab",
&facets
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::String,
"abcd",
&facets
));
}
#[test]
fn test_facet_min_inclusive() {
let facets = vec![(XsdDatatypeKind::FacetMinInclusive, "5".to_string())];
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Integer,
"5",
&facets
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Integer,
"10",
&facets
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Integer,
"3",
&facets
));
}
#[test]
fn test_facet_max_inclusive() {
let facets = vec![(XsdDatatypeKind::FacetMaxInclusive, "10".to_string())];
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Integer,
"10",
&facets
));
assert!(xsd_validate_datatype(
&XsdDatatypeKind::Integer,
"5",
&facets
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::Integer,
"15",
&facets
));
}
#[test]
fn test_facet_pattern_digits() {
let facets = vec![(XsdDatatypeKind::FacetPattern, r"\d+".to_string())];
assert!(xsd_validate_datatype(
&XsdDatatypeKind::String,
"123",
&facets
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::String,
"abc",
&facets
));
}
#[test]
fn test_facet_pattern_alpha() {
let facets = vec![(XsdDatatypeKind::FacetPattern, r"[a-zA-Z]+".to_string())];
assert!(xsd_validate_datatype(
&XsdDatatypeKind::String,
"hello",
&facets
));
assert!(!xsd_validate_datatype(
&XsdDatatypeKind::String,
"123",
&facets
));
}
#[test]
fn test_parse_empty_schema() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert!(schema.components.is_empty());
}
#[test]
fn test_parse_schema_with_target_namespace() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.com/ns">
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(
schema.target_namespace,
Some("http://example.com/ns".to_string())
);
}
#[test]
fn test_parse_simple_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="name" type="xs:string"/>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(schema.components.len(), 1);
assert_eq!(
schema.components[0].component_type,
XsdComponentType::Element
);
assert_eq!(schema.components[0].name, Some("name".to_string()));
assert_eq!(schema.components[0].datatype, Some(XsdDatatypeKind::String));
}
#[test]
fn test_parse_integer_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="age" type="xs:integer"/>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(schema.components.len(), 1);
assert_eq!(schema.components[0].name, Some("age".to_string()));
assert_eq!(
schema.components[0].datatype,
Some(XsdDatatypeKind::Integer)
);
}
#[test]
fn test_parse_element_with_attributes() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="product">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="price" type="xs:decimal"/>
</xs:sequence>
<xs:attribute name="id" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(schema.components.len(), 1);
assert_eq!(schema.components[0].name, Some("product".to_string()));
let ct = &schema.components[0].children;
let complex_type = ct
.iter()
.find(|c| c.component_type == XsdComponentType::ComplexType);
assert!(complex_type.is_some());
if let Some(ctc) = complex_type {
assert_eq!(ctc.attributes.len(), 1);
assert_eq!(ctc.attributes[0].name, Some("id".to_string()));
assert_eq!(ctc.attributes[0].datatype, Some(XsdDatatypeKind::Integer));
}
}
#[test]
fn test_parse_complex_type_with_sequence() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="AddressType">
<xs:sequence>
<xs:element name="street" type="xs:string"/>
<xs:element name="city" type="xs:string"/>
<xs:element name="zip" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(schema.components.len(), 1);
assert_eq!(
schema.components[0].component_type,
XsdComponentType::ComplexType
);
assert_eq!(schema.components[0].name, Some("AddressType".to_string()));
}
#[test]
fn test_parse_restriction() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="AgeType">
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="150"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(schema.components.len(), 1);
assert_eq!(
schema.components[0].component_type,
XsdComponentType::SimpleType
);
let st = &schema.components[0];
assert_eq!(st.datatype, Some(XsdDatatypeKind::Integer));
assert!(!st.facets.is_empty());
}
#[test]
fn test_parse_enumeration() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="ColorType">
<xs:restriction base="xs:string">
<xs:enumeration value="red"/>
<xs:enumeration value="green"/>
<xs:enumeration value="blue"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(schema.components.len(), 1);
assert_eq!(schema.components[0].name, Some("ColorType".to_string()));
}
#[test]
fn test_parse_min_max_occurs() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="items">
<xs:complexType>
<xs:sequence>
<xs:element name="item" type="xs:string"
minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(schema.components.len(), 1);
let elem = &schema.components[0];
let ct = elem
.children
.iter()
.find(|c| c.component_type == XsdComponentType::ComplexType);
assert!(ct.is_some());
if let Some(ctc) = ct {
let seq = ctc
.children
.iter()
.find(|c| c.component_type == XsdComponentType::Sequence);
assert!(seq.is_some());
if let Some(seqc) = seq {
assert!(!seqc.children.is_empty());
let item = &seqc.children[0];
assert_eq!(item.min_occurs, 0);
assert_eq!(item.max_occurs, -1);
}
}
}
#[test]
fn test_parse_attribute_default() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="book">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string"/>
</xs:sequence>
<xs:attribute name="lang" type="xs:string" default="en"/>
<xs:attribute name="id" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
let elem = &schema.components[0];
let ct = elem
.children
.iter()
.find(|c| c.component_type == XsdComponentType::ComplexType);
assert!(ct.is_some());
if let Some(ctc) = ct {
let lang_attr = ctc
.attributes
.iter()
.find(|a| a.name.as_deref() == Some("lang"));
assert!(lang_attr.is_some());
if let Some(la) = lang_attr {
assert_eq!(la.min_occurs, 0); }
let id_attr = ctc
.attributes
.iter()
.find(|a| a.name.as_deref() == Some("id"));
assert!(id_attr.is_some());
}
}
#[test]
fn test_parse_element_with_ref() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element ref="child" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="child" type="xs:string"/>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert_eq!(schema.components.len(), 2);
let root = &schema.components[0];
let ct = root
.children
.iter()
.find(|c| c.component_type == XsdComponentType::ComplexType);
assert!(ct.is_some());
if let Some(ctc) = ct {
let seq = ctc
.children
.iter()
.find(|c| c.component_type == XsdComponentType::Sequence);
assert!(seq.is_some());
if let Some(seqc) = seq {
assert!(!seqc.children.is_empty());
let ref_elem = &seqc.children[0];
assert_eq!(ref_elem.ref_name, Some("child".to_string()));
}
}
}
#[test]
fn test_validate_simple_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="name" type="xs:string"/>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
let doc = r#"<?xml version="1.0"?>
<name>John Doe</name>"#;
assert!(xsd_validate(&schema, doc).is_ok());
}
#[test]
fn test_validate_integer_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="age" type="xs:integer"/>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>25</age>"#).is_ok());
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>not-a-number</age>"#).is_err());
}
#[test]
fn test_validate_complex_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="product">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="price" type="xs:decimal"/>
</xs:sequence>
<xs:attribute name="id" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
let valid_doc = r#"<?xml version="1.0"?>
<product id="123">
<name>Widget</name>
<price>9.99</price>
</product>"#;
assert!(xsd_validate(&schema, valid_doc).is_ok());
}
#[test]
fn test_validate_missing_required_attribute() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="product">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
<xs:attribute name="id" type="xs:integer" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
let invalid_doc = r#"<?xml version="1.0"?>
<product>
<name>Widget</name>
</product>"#;
assert!(xsd_validate(&schema, invalid_doc).is_err());
}
#[test]
fn test_validate_enumeration_facet() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="color">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="red"/>
<xs:enumeration value="green"/>
<xs:enumeration value="blue"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><color>red</color>"#).is_ok());
}
#[test]
fn test_validate_boolean_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="active" type="xs:boolean"/>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><active>true</active>"#).is_ok());
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><active>false</active>"#).is_ok());
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><active>1</active>"#).is_ok());
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><active>yes</active>"#).is_err());
}
#[test]
fn test_validate_element_with_range_constraint() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="age">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="150"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>25</age>"#).is_ok());
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>0</age>"#).is_ok());
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><age>150</age>"#).is_ok());
}
#[test]
fn test_validate_optional_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="person">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="nickname" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
let doc_with_nick = r#"<?xml version="1.0"?>
<person>
<name>John</name>
<nickname>Johnny</nickname>
</person>"#;
let doc_without_nick = r#"<?xml version="1.0"?>
<person>
<name>John</name>
</person>"#;
assert!(xsd_validate(&schema, doc_with_nick).is_ok());
assert!(xsd_validate(&schema, doc_without_nick).is_ok());
}
#[test]
fn test_validate_unbounded_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="items">
<xs:complexType>
<xs:sequence>
<xs:element name="item" type="xs:string"
minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
let doc = r#"<?xml version="1.0"?>
<items>
<item>one</item>
<item>two</item>
<item>three</item>
</items>"#;
assert!(xsd_validate(&schema, doc).is_ok());
}
#[test]
fn test_validate_date_element() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="birthDate" type="xs:date"/>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert!(xsd_validate(
&schema,
r#"<?xml version="1.0"?><birthDate>1990-01-15</birthDate>"#
)
.is_ok());
assert!(xsd_validate(
&schema,
r#"<?xml version="1.0"?><birthDate>not-a-date</birthDate>"#
)
.is_err());
}
#[test]
fn test_validate_choice() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="contact">
<xs:complexType>
<xs:choice>
<xs:element name="email" type="xs:string"/>
<xs:element name="phone" type="xs:string"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert!(xsd_validate(
&schema,
r#"<?xml version="1.0"?><contact><email>a@b.com</email></contact>"#
)
.is_ok());
assert!(xsd_validate(
&schema,
r#"<?xml version="1.0"?><contact><phone>555-1234</phone></contact>"#
)
.is_ok());
}
#[test]
fn test_validate_positive_integer_constraint() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="quantity" type="xs:positiveInteger"/>
</xs:schema>"#;
let schema = xsd_parse(schema_xml).expect("Failed to parse schema");
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><quantity>1</quantity>"#).is_ok());
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><quantity>0</quantity>"#).is_err());
assert!(xsd_validate(&schema, r#"<?xml version="1.0"?><quantity>-1</quantity>"#).is_err());
}
#[test]
fn test_xml_schema_new_mem_parser_ctxt() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="name" type="xs:string"/>
</xs:schema>"#;
let ctxt = unsafe {
xmlSchemaNewMemParserCtxt(
schema_xml.as_ptr() as *const c_char,
schema_xml.len() as c_int,
)
};
assert!(!ctxt.is_null());
let schema = unsafe { xmlSchemaParse(ctxt) };
assert!(!schema.is_null());
unsafe {
xmlSchemaFree(schema);
}
}
#[test]
fn test_xml_schema_validate_doc() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="name" type="xs:string"/>
</xs:schema>"#;
let doc_xml = r#"<?xml version="1.0"?>
<name>John Doe</name>"#;
let ctxt = unsafe {
xmlSchemaNewMemParserCtxt(
schema_xml.as_ptr() as *const c_char,
schema_xml.len() as c_int,
)
};
let schema = unsafe { xmlSchemaParse(ctxt) };
let valid_ctxt = unsafe { xmlSchemaNewValidCtxt(schema) };
let doc = unsafe {
crate::abi::exports_xml2::xmlReadMemory(
doc_xml.as_ptr() as *const c_char,
doc_xml.len() as c_int,
b"test.xml\0".as_ptr() as *const c_char,
ptr::null(),
0,
)
};
let result = unsafe { xmlSchemaValidateDoc(valid_ctxt, doc) };
assert_eq!(result, 0);
unsafe {
xmlSchemaFreeValidCtxt(valid_ctxt);
xmlSchemaFree(schema);
crate::abi::exports_xml2::xmlFreeDoc(doc);
}
}
#[test]
fn test_xml_schema_validate_invalid_doc() {
let schema_xml = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="age" type="xs:integer"/>
</xs:schema>"#;
let doc_xml = r#"<?xml version="1.0"?>
<age>not-a-number</age>"#;
let ctxt = unsafe {
xmlSchemaNewMemParserCtxt(
schema_xml.as_ptr() as *const c_char,
schema_xml.len() as c_int,
)
};
let schema = unsafe { xmlSchemaParse(ctxt) };
let valid_ctxt = unsafe { xmlSchemaNewValidCtxt(schema) };
let doc = unsafe {
crate::abi::exports_xml2::xmlReadMemory(
doc_xml.as_ptr() as *const c_char,
doc_xml.len() as c_int,
b"test.xml\0".as_ptr() as *const c_char,
ptr::null(),
0,
)
};
let result = unsafe { xmlSchemaValidateDoc(valid_ctxt, doc) };
assert_ne!(result, 0);
unsafe {
xmlSchemaFreeValidCtxt(valid_ctxt);
xmlSchemaFree(schema);
crate::abi::exports_xml2::xmlFreeDoc(doc);
}
}
#[test]
fn test_xml_schema_new_valid_ctxt_null() {
let ctxt = unsafe { xmlSchemaNewValidCtxt(ptr::null_mut()) };
assert!(!ctxt.is_null());
unsafe { xmlSchemaFreeValidCtxt(ctxt) };
}
#[test]
fn test_xml_schema_free_null() {
unsafe {
xmlSchemaFree(ptr::null_mut());
xmlSchemaFreeParserCtxt(ptr::null_mut());
xmlSchemaFreeValidCtxt(ptr::null_mut());
}
}
#[test]
fn test_xml_schema_new_parser_ctxt_null() {
let ctxt = unsafe { xmlSchemaNewParserCtxt(ptr::null()) };
assert!(!ctxt.is_null());
unsafe {
allocator::xmlFree(ctxt);
}
}
#[test]
fn test_datatype_parse_kind() {
assert_eq!(
parse_datatype_kind("xs:string"),
Some(XsdDatatypeKind::String)
);
assert_eq!(parse_datatype_kind("string"), Some(XsdDatatypeKind::String));
assert_eq!(
parse_datatype_kind("xs:integer"),
Some(XsdDatatypeKind::Integer)
);
assert_eq!(
parse_datatype_kind("xs:boolean"),
Some(XsdDatatypeKind::Boolean)
);
assert_eq!(
parse_datatype_kind("xs:decimal"),
Some(XsdDatatypeKind::Decimal)
);
assert_eq!(
parse_datatype_kind("xs:float"),
Some(XsdDatatypeKind::Float)
);
assert_eq!(
parse_datatype_kind("xs:double"),
Some(XsdDatatypeKind::Double)
);
assert_eq!(parse_datatype_kind("xs:date"), Some(XsdDatatypeKind::Date));
assert_eq!(
parse_datatype_kind("xs:dateTime"),
Some(XsdDatatypeKind::DateTime)
);
assert_eq!(parse_datatype_kind("xs:time"), Some(XsdDatatypeKind::Time));
assert_eq!(
parse_datatype_kind("xs:hexBinary"),
Some(XsdDatatypeKind::HexBinary)
);
assert_eq!(
parse_datatype_kind("xs:base64Binary"),
Some(XsdDatatypeKind::Base64Binary)
);
assert_eq!(
parse_datatype_kind("xs:anyURI"),
Some(XsdDatatypeKind::AnyURI)
);
assert_eq!(
parse_datatype_kind("xs:QName"),
Some(XsdDatatypeKind::QName)
);
assert_eq!(
parse_datatype_kind("xs:normalizedString"),
Some(XsdDatatypeKind::NormalizedString)
);
assert_eq!(
parse_datatype_kind("xs:token"),
Some(XsdDatatypeKind::Token)
);
assert_eq!(
parse_datatype_kind("xs:language"),
Some(XsdDatatypeKind::Language)
);
assert_eq!(parse_datatype_kind("xs:Name"), Some(XsdDatatypeKind::Name));
assert_eq!(
parse_datatype_kind("xs:NCName"),
Some(XsdDatatypeKind::NCName)
);
assert_eq!(parse_datatype_kind("xs:ID"), Some(XsdDatatypeKind::Id));
assert_eq!(
parse_datatype_kind("xs:IDREF"),
Some(XsdDatatypeKind::Idref)
);
assert_eq!(
parse_datatype_kind("xs:integer"),
Some(XsdDatatypeKind::Integer)
);
assert_eq!(parse_datatype_kind("xs:long"), Some(XsdDatatypeKind::Long));
assert_eq!(parse_datatype_kind("xs:int"), Some(XsdDatatypeKind::Int));
assert_eq!(
parse_datatype_kind("xs:short"),
Some(XsdDatatypeKind::Short)
);
assert_eq!(parse_datatype_kind("xs:byte"), Some(XsdDatatypeKind::Byte));
assert_eq!(
parse_datatype_kind("xs:positiveInteger"),
Some(XsdDatatypeKind::PositiveInteger)
);
assert_eq!(
parse_datatype_kind("xs:negativeInteger"),
Some(XsdDatatypeKind::NegativeInteger)
);
assert_eq!(parse_datatype_kind("unknown"), None);
}
}