use std::collections::HashMap;
use std::sync::Arc;
use indexmap::IndexMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NsName {
pub namespace_uri: String,
pub local_name: String,
}
impl NsName {
pub fn new(namespace_uri: impl Into<String>, local_name: impl Into<String>) -> Self {
Self {
namespace_uri: namespace_uri.into(),
local_name: local_name.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ContentModelType {
#[default]
Sequence,
Choice,
All,
Empty,
}
#[derive(Debug, Clone)]
pub struct FlattenedChildren {
pub constraints: HashMap<String, (u32, Option<u32>)>,
pub content_model_type: ContentModelType,
pub ordered_elements: Arc<[String]>,
}
impl FlattenedChildren {
pub fn new() -> Self {
Self {
constraints: HashMap::new(),
content_model_type: ContentModelType::Empty,
ordered_elements: Arc::from([]),
}
}
pub fn with_content_model(content_model_type: ContentModelType) -> Self {
Self {
constraints: HashMap::new(),
content_model_type,
ordered_elements: Arc::from([]),
}
}
}
impl Default for FlattenedChildren {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CompiledSchema {
pub target_namespace: Option<String>,
pub elements: IndexMap<String, ElementDef>,
pub types: IndexMap<String, TypeDef>,
pub attributes: IndexMap<String, AttributeDef>,
pub imports: HashMap<String, CompiledSchema>,
pub substitution_groups: HashMap<String, Vec<String>>,
pub namespace_prefixes: HashMap<String, String>,
pub prefix_namespaces: HashMap<String, String>,
pub ns_type_children_cache: HashMap<NsName, Arc<FlattenedChildren>>,
pub type_children_cache: HashMap<String, Arc<FlattenedChildren>>,
pub transitive_substitution_groups: HashMap<String, Arc<Vec<String>>>,
pub substitution_group_heads: HashMap<String, String>,
}
impl CompiledSchema {
pub fn new() -> Self {
Self {
target_namespace: None,
elements: IndexMap::new(),
types: IndexMap::new(),
attributes: IndexMap::new(),
imports: HashMap::new(),
substitution_groups: HashMap::new(),
namespace_prefixes: HashMap::new(),
prefix_namespaces: HashMap::new(),
ns_type_children_cache: HashMap::new(),
type_children_cache: HashMap::new(),
transitive_substitution_groups: HashMap::new(),
substitution_group_heads: HashMap::new(),
}
}
pub fn with_namespace(namespace: impl Into<String>) -> Self {
Self {
target_namespace: Some(namespace.into()),
..Self::new()
}
}
pub fn get_element(&self, qname: &str) -> Option<&ElementDef> {
if let Some(elem) = self.elements.get(qname) {
return Some(elem);
}
if let Some((_prefix, local)) = qname.split_once(':') {
for schema in self.imports.values() {
if let Some(elem) = schema.elements.get(local) {
return Some(elem);
}
}
if let Some(elem) = self.elements.get(local) {
return Some(elem);
}
}
None
}
pub fn get_element_by_ns(&self, namespace_uri: &str, local_name: &str) -> Option<&ElementDef> {
if let Some(prefix) = self.namespace_prefixes.get(namespace_uri) {
let qname = if prefix.is_empty() {
local_name.to_string()
} else {
format!("{}:{}", prefix, local_name)
};
if let Some(elem) = self.elements.get(&qname) {
return Some(elem);
}
}
if let Some(elem) = self.elements.get(local_name) {
return Some(elem);
}
for (key, elem) in &self.elements {
if let Some((_prefix, local)) = key.split_once(':') {
if local == local_name {
return Some(elem);
}
}
}
None
}
pub fn get_type(&self, qname: &str) -> Option<&TypeDef> {
if let Some(typ) = self.types.get(qname) {
return Some(typ);
}
if let Some((_prefix, local)) = qname.split_once(':') {
for schema in self.imports.values() {
if let Some(typ) = schema.types.get(local) {
return Some(typ);
}
}
if let Some(typ) = self.types.get(local) {
return Some(typ);
}
}
None
}
pub fn resolve_type_ref_to_ns(&self, type_ref: &str) -> Option<NsName> {
if let Some((prefix, local)) = type_ref.split_once(':') {
let ns_uri = self.prefix_namespaces.get(prefix)?;
Some(NsName::new(ns_uri.clone(), local))
} else {
let ns = self.target_namespace.as_deref().unwrap_or("");
Some(NsName::new(ns, type_ref))
}
}
pub fn get_ns_type_children(
&self,
namespace_uri: &str,
local_name: &str,
) -> Option<&Arc<FlattenedChildren>> {
self.ns_type_children_cache
.get(&NsName::new(namespace_uri, local_name))
}
pub fn get_type_by_ns(&self, namespace_uri: &str, local_name: &str) -> Option<&TypeDef> {
if let Some(prefix) = self.namespace_prefixes.get(namespace_uri) {
let qname = if prefix.is_empty() {
local_name.to_string()
} else {
format!("{}:{}", prefix, local_name)
};
if let Some(typ) = self.types.get(&qname) {
return Some(typ);
}
}
self.types.get(local_name)
}
}
impl Default for CompiledSchema {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ElementDef {
pub name: String,
pub type_ref: Option<String>,
pub inline_type: Option<TypeDef>,
pub min_occurs: u32,
pub max_occurs: Option<u32>,
pub is_abstract: bool,
pub substitution_group: Option<String>,
pub nillable: bool,
pub constraints: Vec<CompiledConstraint>,
}
impl ElementDef {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
type_ref: None,
inline_type: None,
min_occurs: 1,
max_occurs: Some(1),
is_abstract: false,
substitution_group: None,
nillable: false,
constraints: Vec::new(),
}
}
pub fn with_type(mut self, type_ref: impl Into<String>) -> Self {
self.type_ref = Some(type_ref.into());
self
}
pub fn with_occurs(mut self, min: u32, max: Option<u32>) -> Self {
self.min_occurs = min;
self.max_occurs = max;
self
}
pub fn optional(mut self) -> Self {
self.min_occurs = 0;
self
}
pub fn unbounded(mut self) -> Self {
self.max_occurs = None;
self
}
}
#[derive(Debug, Clone)]
pub enum TypeDef {
Simple(SimpleType),
Complex(ComplexType),
}
#[derive(Debug, Clone)]
pub struct SimpleType {
pub name: String,
pub base_type: Option<String>,
pub enumeration: Vec<String>,
pub pattern: Option<String>,
pub min_length: Option<u32>,
pub max_length: Option<u32>,
pub min_inclusive: Option<String>,
pub max_inclusive: Option<String>,
}
impl SimpleType {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
base_type: None,
enumeration: Vec::new(),
pattern: None,
min_length: None,
max_length: None,
min_inclusive: None,
max_inclusive: None,
}
}
pub fn string() -> Self {
Self::new("string").with_base("xs:string")
}
pub fn integer() -> Self {
Self::new("integer").with_base("xs:integer")
}
pub fn with_base(mut self, base: impl Into<String>) -> Self {
self.base_type = Some(base.into());
self
}
}
#[derive(Debug, Clone)]
pub struct ComplexType {
pub name: String,
pub base_type: Option<String>,
pub content: ContentModel,
pub attributes: Vec<AttributeDef>,
pub is_abstract: bool,
pub mixed: bool,
}
impl ComplexType {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
base_type: None,
content: ContentModel::Empty,
attributes: Vec::new(),
is_abstract: false,
mixed: false,
}
}
pub fn sequence(name: impl Into<String>, elements: Vec<ElementDef>) -> Self {
Self {
name: name.into(),
base_type: None,
content: ContentModel::Sequence(elements),
attributes: Vec::new(),
is_abstract: false,
mixed: false,
}
}
}
#[derive(Debug, Clone)]
pub enum ContentModel {
Empty,
Sequence(Vec<ElementDef>),
Choice(Vec<ElementDef>),
All(Vec<ElementDef>),
SimpleContent {
base_type: String,
},
ComplexExtension {
base_type: String,
elements: Vec<ElementDef>,
},
Any {
namespace: Option<String>,
process_contents: ProcessContents,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessContents {
Strict,
Lax,
Skip,
}
#[derive(Debug, Clone)]
pub struct AttributeDef {
pub name: String,
pub type_ref: Option<String>,
pub required: bool,
pub default: Option<String>,
pub fixed: Option<String>,
}
impl AttributeDef {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
type_ref: None,
required: false,
default: None,
fixed: None,
}
}
pub fn required(name: impl Into<String>) -> Self {
Self {
required: true,
..Self::new(name)
}
}
pub fn with_type(mut self, type_ref: impl Into<String>) -> Self {
self.type_ref = Some(type_ref.into());
self
}
pub fn with_default(mut self, default: impl Into<String>) -> Self {
self.default = Some(default.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompiledConstraintType {
Unique,
Key,
KeyRef,
}
#[derive(Debug, Clone)]
pub struct CompiledConstraint {
pub name: String,
pub constraint_type: CompiledConstraintType,
pub selector_xpath: String,
pub field_xpaths: Vec<String>,
pub refer: Option<String>,
}
impl CompiledConstraint {
pub fn unique(name: impl Into<String>, selector: impl Into<String>) -> Self {
Self {
name: name.into(),
constraint_type: CompiledConstraintType::Unique,
selector_xpath: selector.into(),
field_xpaths: Vec::new(),
refer: None,
}
}
pub fn key(name: impl Into<String>, selector: impl Into<String>) -> Self {
Self {
name: name.into(),
constraint_type: CompiledConstraintType::Key,
selector_xpath: selector.into(),
field_xpaths: Vec::new(),
refer: None,
}
}
pub fn keyref(
name: impl Into<String>,
selector: impl Into<String>,
refer: impl Into<String>,
) -> Self {
Self {
name: name.into(),
constraint_type: CompiledConstraintType::KeyRef,
selector_xpath: selector.into(),
field_xpaths: Vec::new(),
refer: Some(refer.into()),
}
}
pub fn with_field(mut self, field: impl Into<String>) -> Self {
self.field_xpaths.push(field.into());
self
}
pub fn with_fields(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.field_xpaths.extend(fields.into_iter().map(Into::into));
self
}
}
pub mod builtin {
pub const STRING: &str = "xs:string";
pub const INTEGER: &str = "xs:integer";
pub const DECIMAL: &str = "xs:decimal";
pub const BOOLEAN: &str = "xs:boolean";
pub const DATE: &str = "xs:date";
pub const DATE_TIME: &str = "xs:dateTime";
pub const DOUBLE: &str = "xs:double";
pub const FLOAT: &str = "xs:float";
pub const ANY_URI: &str = "xs:anyURI";
pub const ID: &str = "xs:ID";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_element_with_local_name() {
let mut schema = CompiledSchema::new();
schema.elements.insert(
"ReliefFeature".to_string(),
ElementDef::new("ReliefFeature"),
);
assert!(schema.get_element("ReliefFeature").is_some());
}
#[test]
fn test_get_element_with_qualified_name() {
let mut schema = CompiledSchema::new();
schema.elements.insert(
"ReliefFeature".to_string(),
ElementDef::new("ReliefFeature"),
);
assert!(
schema.get_element("dem:ReliefFeature").is_some(),
"Should find 'ReliefFeature' when looking up 'dem:ReliefFeature'"
);
}
#[test]
fn test_get_type_with_local_name() {
let mut schema = CompiledSchema::new();
schema.types.insert(
"AbstractCityObjectType".to_string(),
TypeDef::Complex(ComplexType::new("AbstractCityObjectType")),
);
assert!(schema.get_type("AbstractCityObjectType").is_some());
}
#[test]
fn test_get_type_with_qualified_name() {
let mut schema = CompiledSchema::new();
schema.types.insert(
"AbstractCityObjectType".to_string(),
TypeDef::Complex(ComplexType::new("AbstractCityObjectType")),
);
assert!(
schema.get_type("core:AbstractCityObjectType").is_some(),
"Should find 'AbstractCityObjectType' when looking up 'core:AbstractCityObjectType'"
);
}
#[test]
fn test_get_type_not_found() {
let schema = CompiledSchema::new();
assert!(schema.get_type("NonExistentType").is_none());
assert!(schema.get_type("prefix:NonExistentType").is_none());
}
#[test]
fn test_get_element_not_found() {
let schema = CompiledSchema::new();
assert!(schema.get_element("NonExistentElement").is_none());
assert!(schema.get_element("prefix:NonExistentElement").is_none());
}
}