use std::sync::Arc;
use indexmap::IndexMap as IndexMapBase;
pub type IndexMap<K, V> = IndexMapBase<K, V, rustc_hash::FxBuildHasher>;
type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NsName {
pub namespace_uri: Arc<str>,
pub local_name: Arc<str>,
}
impl NsName {
pub fn new(namespace_uri: impl Into<Arc<str>>, local_name: impl Into<Arc<str>>) -> Self {
Self {
namespace_uri: namespace_uri.into(),
local_name: local_name.into(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct NsNameRef<'a> {
pub namespace_uri: &'a str,
pub local_name: &'a str,
}
impl std::hash::Hash for NsNameRef<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.namespace_uri.hash(state);
self.local_name.hash(state);
}
}
impl indexmap::Equivalent<NsName> for NsNameRef<'_> {
fn equivalent(&self, key: &NsName) -> bool {
*key.namespace_uri == *self.namespace_uri && *key.local_name == *self.local_name
}
}
#[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]>,
pub wildcard: Option<WildcardConstraint>,
pub automaton: Option<Arc<crate::schema::xsd::content_automaton::ContentAutomaton>>,
}
impl FlattenedChildren {
pub fn new() -> Self {
Self {
constraints: HashMap::default(),
content_model_type: ContentModelType::Empty,
ordered_elements: Arc::from([]),
wildcard: None,
automaton: None,
}
}
pub fn with_content_model(content_model_type: ContentModelType) -> Self {
Self {
constraints: HashMap::default(),
content_model_type,
ordered_elements: Arc::from([]),
wildcard: None,
automaton: None,
}
}
}
impl Default for FlattenedChildren {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CompiledSchema {
pub target_namespace: Option<String>,
pub elements_ns: IndexMap<NsName, ElementDef>,
pub types_ns: IndexMap<NsName, TypeDef>,
pub attributes_ns: IndexMap<NsName, AttributeDef>,
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 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_ns: IndexMap::default(),
types_ns: IndexMap::default(),
attributes_ns: IndexMap::default(),
substitution_groups: HashMap::default(),
namespace_prefixes: HashMap::default(),
prefix_namespaces: HashMap::default(),
ns_type_children_cache: HashMap::default(),
transitive_substitution_groups: HashMap::default(),
substitution_group_heads: HashMap::default(),
}
}
pub fn with_namespace(namespace: impl Into<String>) -> Self {
Self {
target_namespace: Some(namespace.into()),
..Self::new()
}
}
#[doc(hidden)]
pub fn get_element(&self, qname: &str) -> Option<&ElementDef> {
if let Some((prefix, local)) = qname.split_once(':') {
if let Some(uri) = self.prefix_namespaces.get(prefix)
&& let Some(elem) = self.element_ns(uri, local)
{
return Some(elem);
}
return self.element_ns_any(local);
}
if let Some(tns) = self.target_namespace.as_deref()
&& let Some(elem) = self.element_ns(tns, qname)
{
return Some(elem);
}
if let Some(elem) = self.element_ns("", qname) {
return Some(elem);
}
self.element_ns_any(qname)
}
#[doc(hidden)]
pub fn get_element_by_ns(&self, namespace_uri: &str, local_name: &str) -> Option<&ElementDef> {
if let Some(elem) = self.element_ns(namespace_uri, local_name) {
return Some(elem);
}
self.element_ns_any(local_name)
}
#[doc(hidden)]
pub fn get_type(&self, qname: &str) -> Option<&TypeDef> {
if let Some((prefix, local)) = qname.split_once(':') {
if let Some(uri) = self.prefix_namespaces.get(prefix)
&& let Some(typ) = self.type_ns(uri, local)
{
return Some(typ);
}
return self.type_ns_any(local);
}
if let Some(tns) = self.target_namespace.as_deref()
&& let Some(typ) = self.type_ns(tns, qname)
{
return Some(typ);
}
if let Some(typ) = self.type_ns("", qname) {
return Some(typ);
}
self.type_ns_any(qname)
}
pub fn element_ns(&self, namespace_uri: &str, local_name: &str) -> Option<&ElementDef> {
if let Some(e) = self.elements_ns.get(&NsNameRef {
namespace_uri,
local_name,
}) {
return Some(e);
}
if !namespace_uri.is_empty()
&& let Some(e) = self.elements_ns.get(&NsNameRef {
namespace_uri: "",
local_name,
})
{
return Some(e);
}
None
}
pub fn element_ns_any(&self, local_name: &str) -> Option<&ElementDef> {
self.elements_ns
.iter()
.find(|(k, _)| &*k.local_name == local_name)
.map(|(_, e)| e)
}
pub fn type_ns(&self, namespace_uri: &str, local_name: &str) -> Option<&TypeDef> {
if let Some(t) = self.types_ns.get(&NsNameRef {
namespace_uri,
local_name,
}) {
return Some(t);
}
if !namespace_uri.is_empty()
&& let Some(t) = self.types_ns.get(&NsNameRef {
namespace_uri: "",
local_name,
})
{
return Some(t);
}
None
}
pub fn type_ns_any(&self, local_name: &str) -> Option<&TypeDef> {
self.types_ns
.iter()
.find(|(k, _)| &*k.local_name == local_name)
.map(|(_, t)| t)
}
pub fn attribute_ns(&self, namespace_uri: &str, local_name: &str) -> Option<&AttributeDef> {
if let Some(a) = self.attributes_ns.get(&NsNameRef {
namespace_uri,
local_name,
}) {
return Some(a);
}
if !namespace_uri.is_empty()
&& let Some(a) = self.attributes_ns.get(&NsNameRef {
namespace_uri: "",
local_name,
})
{
return Some(a);
}
None
}
pub fn type_by_ref(&self, ns: Option<&NsName>, name: &str) -> Option<&TypeDef> {
if let Some(n) = ns
&& let Some(t) = self.type_ns(&n.namespace_uri, &n.local_name)
{
return Some(t);
}
self.get_type(name)
}
pub fn complex_base_def(&self, c: &ComplexType) -> Option<&TypeDef> {
let base = match &c.content {
ContentModel::ComplexExtension { base_type, .. } => Some(base_type.as_str()),
ContentModel::SimpleContent { base_type } => Some(base_type.as_str()),
_ => c.base_type.as_deref(),
}?;
let ns = c
.base_ns
.as_ref()
.filter(|_| c.base_type.as_deref() == Some(base));
self.type_by_ref(ns, base)
}
pub fn simple_base_def(&self, s: &SimpleType) -> Option<&TypeDef> {
let base = s.base_type.as_deref()?;
self.type_by_ref(s.base_ns.as_ref(), base)
}
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(typ) = self.type_ns(namespace_uri, local_name) {
return Some(typ);
}
self.type_ns_any(local_name)
}
pub fn display_name(&self, name: &NsName) -> String {
if !name.namespace_uri.is_empty()
&& let Some(prefix) = self.namespace_prefixes.get(&*name.namespace_uri)
&& !prefix.is_empty()
{
return format!("{}:{}", prefix, name.local_name);
}
name.local_name.to_string()
}
}
impl Default for CompiledSchema {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ElementDef {
pub name: String,
pub type_ref: Option<String>,
pub type_ns: Option<NsName>,
pub ref_ns: Option<NsName>,
pub inline_type: Option<TypeDef>,
pub min_occurs: u32,
pub max_occurs: Option<u32>,
pub is_abstract: bool,
pub substitution_group: Option<String>,
pub substitution_ns: Option<NsName>,
pub nillable: bool,
pub default: Option<String>,
pub fixed: Option<String>,
pub constraints: Vec<CompiledConstraint>,
}
impl ElementDef {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
type_ref: None,
type_ns: None,
ref_ns: None,
inline_type: None,
min_occurs: 1,
max_occurs: Some(1),
is_abstract: false,
substitution_group: None,
substitution_ns: None,
nillable: false,
default: None,
fixed: None,
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 base_ns: Option<NsName>,
pub enumeration: Vec<String>,
pub pattern: Option<String>,
pub length: Option<u32>,
pub min_length: Option<u32>,
pub max_length: Option<u32>,
pub min_inclusive: Option<String>,
pub max_inclusive: Option<String>,
pub min_exclusive: Option<String>,
pub max_exclusive: Option<String>,
pub total_digits: Option<u32>,
pub fraction_digits: Option<u32>,
pub item_type: Option<String>,
pub item_ns: Option<NsName>,
pub member_types: Vec<String>,
pub member_ns: Vec<Option<NsName>>,
pub white_space: Option<WhiteSpace>,
pub explicit_timezone: Option<ExplicitTimezone>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExplicitTimezone {
Required,
Prohibited,
Optional,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WhiteSpace {
Preserve,
Replace,
Collapse,
}
impl SimpleType {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
base_type: None,
base_ns: None,
enumeration: Vec::new(),
pattern: None,
length: None,
min_length: None,
max_length: None,
min_inclusive: None,
max_inclusive: None,
min_exclusive: None,
max_exclusive: None,
total_digits: None,
fraction_digits: None,
item_type: None,
item_ns: None,
member_types: Vec::new(),
member_ns: Vec::new(),
white_space: None,
explicit_timezone: 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
}
pub fn with_white_space(mut self, ws: WhiteSpace) -> Self {
self.white_space = Some(ws);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DerivationMethod {
Extension,
Restriction,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BlockSet {
pub extension: bool,
pub restriction: bool,
}
impl BlockSet {
pub fn blocks(&self, method: DerivationMethod) -> bool {
match method {
DerivationMethod::Extension => self.extension,
DerivationMethod::Restriction => self.restriction,
}
}
}
#[derive(Debug, Clone)]
pub struct ComplexType {
pub name: String,
pub base_type: Option<String>,
pub base_ns: Option<NsName>,
pub derivation: Option<DerivationMethod>,
pub block: BlockSet,
pub content: ContentModel,
pub attributes: Vec<AttributeDef>,
pub wildcard: Option<WildcardConstraint>,
pub attr_wildcard: Option<WildcardConstraint>,
pub is_abstract: bool,
pub mixed: bool,
pub particle: Option<std::sync::Arc<Particle>>,
}
impl ComplexType {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
base_type: None,
base_ns: None,
derivation: None,
block: BlockSet::default(),
content: ContentModel::Empty,
attributes: Vec::new(),
wildcard: None,
attr_wildcard: None,
is_abstract: false,
mixed: false,
particle: None,
}
}
pub fn sequence(name: impl Into<String>, elements: Vec<ElementDef>) -> Self {
Self {
name: name.into(),
base_type: None,
base_ns: None,
derivation: None,
block: BlockSet::default(),
content: ContentModel::Sequence(elements),
attributes: Vec::new(),
wildcard: None,
attr_wildcard: None,
is_abstract: false,
mixed: false,
particle: None,
}
}
}
#[derive(Debug, Clone)]
pub enum Particle {
Element(ElementDef),
Wildcard(WildcardConstraint),
Sequence {
min: u32,
max: Option<u32>,
items: Vec<Particle>,
},
Choice {
min: u32,
max: Option<u32>,
items: Vec<Particle>,
},
All {
min: u32,
elements: Vec<ElementDef>,
},
}
#[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)]
pub struct WildcardConstraint {
pub namespace: WildcardNamespace,
pub process_contents: ProcessContents,
pub min_occurs: u32,
pub max_occurs: Option<u32>,
pub target_namespace: Option<String>,
}
impl WildcardConstraint {
pub fn matches(&self, ns: Option<&str>) -> bool {
match &self.namespace {
WildcardNamespace::Any => true,
WildcardNamespace::Other => {
match ns {
Some(ns) => Some(ns) != self.target_namespace.as_deref(),
None => false,
}
}
WildcardNamespace::List(uris) => uris
.iter()
.any(|u| (u.is_empty() && ns.is_none()) || Some(u.as_str()) == ns),
}
}
}
#[derive(Debug, Clone)]
pub enum WildcardNamespace {
Any,
Other,
List(Vec<String>),
}
#[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 type_ns: Option<NsName>,
pub ref_ns: Option<NsName>,
pub inline_type: Option<Box<SimpleType>>,
pub required: bool,
pub default: Option<String>,
pub fixed: Option<String>,
pub is_ref: bool,
}
impl AttributeDef {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
type_ref: None,
type_ns: None,
ref_ns: None,
inline_type: None,
required: false,
default: None,
fixed: None,
is_ref: false,
}
}
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_ns.insert(
crate::schema::types::NsName::new("", "ReliefFeature"),
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_ns.insert(
crate::schema::types::NsName::new("", "ReliefFeature"),
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_ns.insert(
crate::schema::types::NsName::new("", "AbstractCityObjectType"),
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_ns.insert(
crate::schema::types::NsName::new("", "AbstractCityObjectType"),
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());
}
#[test]
fn ns_maps_resolve_same_local_in_different_namespaces() {
const NS_A: &str = "http://example.com/a";
const NS_B: &str = "http://example.com/b";
let mut schema = CompiledSchema::new();
let mut a = ElementDef::new("value");
a.type_ref = Some("a:AType".into());
let mut b = ElementDef::new("value");
b.type_ref = Some("b:BType".into());
schema.elements_ns.insert(NsName::new(NS_A, "value"), a);
schema.elements_ns.insert(NsName::new(NS_B, "value"), b);
assert_eq!(
schema
.element_ns(NS_A, "value")
.unwrap()
.type_ref
.as_deref(),
Some("a:AType")
);
assert_eq!(
schema
.element_ns(NS_B, "value")
.unwrap()
.type_ref
.as_deref(),
Some("b:BType")
);
}
#[test]
fn ns_element_lookup_does_not_leak_across_namespaces() {
const NS_A: &str = "http://example.com/a";
const NS_C: &str = "http://example.com/c";
let mut schema = CompiledSchema::new();
schema
.elements_ns
.insert(NsName::new(NS_A, "value"), ElementDef::new("value"));
assert!(schema.element_ns(NS_C, "value").is_none());
assert!(schema.element_ns_any("value").is_some());
}
#[test]
fn ns_type_lookup_chameleon_fallback() {
const NS_A: &str = "http://example.com/a";
let mut schema = CompiledSchema::new();
schema.types_ns.insert(
NsName::new("", "ChameleonType"),
TypeDef::Complex(ComplexType::new("ChameleonType")),
);
assert!(schema.type_ns(NS_A, "ChameleonType").is_some());
assert!(schema.type_ns("", "ChameleonType").is_some());
assert!(schema.type_ns(NS_A, "Missing").is_none());
}
}