use std::sync::Arc;
use super::*;
use crate::error::{ErrorLevel, StructuredError, ValidationErrorType};
use crate::event::{XmlEvent, XmlEventHandler};
use crate::namespace::Namespace;
use crate::schema::types::{
CompiledSchema, ComplexType, ContentModel, ContentModelType, FlattenedChildren,
};
use super::super::state::{ElementContext, ValidationState};
#[test]
fn test_validation_mode_default() {
let mode = ValidationMode::default();
assert_eq!(mode, ValidationMode::Strict);
}
#[test]
fn test_validation_state_new() {
let state = ValidationState::new();
assert!(state.element_stack.is_empty());
assert_eq!(state.depth, 0);
assert_eq!(state.namespace_stack.len(), 1);
}
#[test]
fn test_validation_state_push_pop_element() {
let mut state = ValidationState::new();
state.push_element_str("root", None);
assert_eq!(state.depth, 1);
assert_eq!(state.element_stack.len(), 1);
assert_eq!(state.element_stack[0].name.as_ref(), "root");
state.push_element_str("child", Some("http://example.com"));
assert_eq!(state.depth, 2);
assert_eq!(state.element_stack.len(), 2);
let popped = state.pop_element().unwrap();
assert_eq!(popped.name.as_ref(), "child");
assert_eq!(state.depth, 1);
}
#[test]
fn test_validation_state_element_path() {
let mut state = ValidationState::new();
assert_eq!(state.element_path(), "/");
state.push_element_str("root", None);
assert_eq!(state.element_path(), "/root");
state.push_element_str("child", None);
assert_eq!(state.element_path(), "/root/child");
}
#[test]
fn test_element_context_new() {
let ctx = ElementContext::from_str("test", Some("http://example.com"));
assert_eq!(ctx.name.as_ref(), "test");
assert_eq!(ctx.namespace.as_deref(), Some("http://example.com"));
assert!(ctx.child_counts.is_empty());
assert!(ctx.text_content.is_empty());
assert!(!ctx.schema_validated);
}
#[test]
fn test_element_context_child_counts() {
let mut ctx = ElementContext::from_str("parent", None);
assert_eq!(ctx.get_child_count("child1"), 0);
assert_eq!(ctx.increment_child("child1"), 1);
assert_eq!(ctx.get_child_count("child1"), 1);
assert_eq!(ctx.increment_child("child1"), 2);
assert_eq!(ctx.get_child_count("child1"), 2);
assert_eq!(ctx.increment_child("child2"), 1);
assert_eq!(ctx.get_child_count("child2"), 1);
}
#[test]
fn test_streaming_validator_new() {
let schema = CompiledSchema::new();
let validator = OnePassSchemaValidator::new(Arc::new(schema));
assert!(validator.is_valid());
assert!(validator.is_clean());
assert_eq!(validator.error_count(), 0);
}
#[test]
fn test_streaming_validator_with_mode() {
let schema = CompiledSchema::new();
let validator = OnePassSchemaValidator::new(Arc::new(schema)).set_mode(ValidationMode::Lenient);
assert!(validator.is_valid());
}
#[test]
fn test_streaming_validator_max_errors() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator.set_max_errors(2);
validator.add_error(StructuredError::new("error1", ValidationErrorType::Other));
validator.add_error(StructuredError::new("error2", ValidationErrorType::Other));
validator.add_error(StructuredError::new("error3", ValidationErrorType::Other));
assert_eq!(validator.errors().len(), 2);
}
#[test]
fn test_streaming_validator_make_error() {
let schema = CompiledSchema::new();
let validator = OnePassSchemaValidator::new(Arc::new(schema));
let error = validator.make_error(ValidationErrorType::UnknownElement, "test error");
assert_eq!(error.message, "test error");
assert_eq!(error.error_type, ValidationErrorType::UnknownElement);
}
#[test]
fn test_streaming_validator_errors_and_warnings() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator.add_error(
StructuredError::new("error1", ValidationErrorType::Other).with_level(ErrorLevel::Error),
);
validator.add_error(
StructuredError::new("warning1", ValidationErrorType::Other)
.with_level(ErrorLevel::Warning),
);
validator.add_error(
StructuredError::new("error2", ValidationErrorType::Other).with_level(ErrorLevel::Error),
);
assert_eq!(validator.error_count(), 2);
assert_eq!(validator.warning_count(), 1);
assert_eq!(validator.errors_only().len(), 2);
assert_eq!(validator.warnings().len(), 1);
assert!(!validator.is_valid());
assert!(!validator.is_clean());
}
#[test]
fn test_streaming_validator_with_schema_elements() {
use crate::schema::types::{ElementDef, SimpleType, TypeDef};
let mut schema = CompiledSchema::new();
schema.elements.insert(
"root".to_string(),
ElementDef::new("root").with_type("xs:string"),
);
schema.types.insert(
"xs:string".to_string(),
TypeDef::Simple(SimpleType::new("xs:string")),
);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
let _ = validator.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
});
assert!(validator.is_valid());
}
#[test]
fn test_streaming_validator_unknown_element_strict() {
use crate::schema::types::ElementDef;
let mut schema = CompiledSchema::new();
schema
.elements
.insert("known".to_string(), ElementDef::new("known"));
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
let _ = validator.handle(&XmlEvent::StartElement {
name: "unknown".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
});
assert!(!validator.is_valid());
assert!(
validator
.errors()
.iter()
.any(|e| e.message.contains("unknown"))
);
}
#[test]
fn test_streaming_validator_unknown_element_lenient() {
use crate::schema::types::ElementDef;
let mut schema = CompiledSchema::new();
schema
.elements
.insert("known".to_string(), ElementDef::new("known"));
let mut validator =
OnePassSchemaValidator::new(Arc::new(schema)).set_mode(ValidationMode::Lenient);
let _ = validator.handle(&XmlEvent::StartElement {
name: "unknown".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
});
assert!(validator.is_valid());
}
#[test]
fn test_streaming_validator_text_content() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
let _ = validator.handle(&XmlEvent::StartElement {
name: "test".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
});
let _ = validator.handle(&XmlEvent::Text("content".to_string()));
let ctx = validator.state.current_element().unwrap();
assert_eq!(ctx.text_content, "content");
}
#[test]
fn test_streaming_validator_cdata_content() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
let _ = validator.handle(&XmlEvent::StartElement {
name: "test".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
});
let _ = validator.handle(&XmlEvent::CData("cdata content".to_string()));
let ctx = validator.state.current_element().unwrap();
assert_eq!(ctx.text_content, "cdata content");
}
#[test]
fn test_streaming_validator_finish_unclosed_element() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
let _ = validator.handle(&XmlEvent::StartElement {
name: "unclosed".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
});
let _ = validator.finish();
assert!(!validator.is_valid());
assert!(
validator
.errors()
.iter()
.any(|e| e.message.contains("not closed"))
);
}
#[test]
fn test_streaming_validator_into_errors() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator.add_error(StructuredError::new(
"test error",
ValidationErrorType::Other,
));
let errors = validator.into_errors();
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].message, "test error");
}
#[test]
fn test_streaming_validator_min_occurs() {
use crate::schema::types::{ElementDef, TypeDef};
let mut schema = CompiledSchema::new();
let complex_type = ComplexType {
name: "ParentType".to_string(),
base_type: None,
content: ContentModel::Sequence(vec![
ElementDef::new("required_child").with_occurs(1, Some(1)),
]),
attributes: Vec::new(),
is_abstract: false,
mixed: false,
};
schema.elements.insert(
"parent".to_string(),
ElementDef::new("parent").with_type("ParentType"),
);
schema
.types
.insert("ParentType".to_string(), TypeDef::Complex(complex_type));
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
let _ = validator.handle(&XmlEvent::StartElement {
name: "parent".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
});
let _ = validator.handle(&XmlEvent::EndElement {
name: "parent".into(),
prefix: None,
});
assert!(!validator.is_valid());
assert!(
validator
.errors()
.iter()
.any(|e| e.message.contains("required_child"))
);
}
#[test]
fn test_streaming_validator() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: None,
})
.unwrap();
validator.handle(&XmlEvent::Eof).unwrap();
validator.finish().unwrap();
assert!(validator.is_valid());
}
#[test]
fn test_streaming_validator_set_max_errors() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator.set_max_errors(5);
assert!(validator.is_valid());
}
#[test]
fn test_streaming_validator_errors_methods() {
let schema = CompiledSchema::new();
let validator = OnePassSchemaValidator::new(Arc::new(schema));
assert!(validator.errors().is_empty());
assert!(validator.errors_only().is_empty());
assert!(validator.warnings().is_empty());
assert_eq!(validator.error_count(), 0);
assert_eq!(validator.warning_count(), 0);
}
#[test]
fn test_streaming_validator_is_clean() {
let schema = CompiledSchema::new();
let validator = OnePassSchemaValidator::new(Arc::new(schema));
assert!(validator.is_clean());
}
#[test]
fn test_streaming_validator_with_prefix() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: Some("ns".into()),
namespace: Some("http://example.com".to_string()),
attributes: vec![],
namespace_decls: vec![Namespace::new(
"ns".to_string(),
"http://example.com".to_string(),
)],
line: None,
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: Some("ns".into()),
})
.unwrap();
validator.finish().unwrap();
assert!(validator.is_valid());
}
#[test]
fn test_streaming_validator_with_attributes() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![
("id".into(), "1".into()),
("xmlns:ns".into(), "http://example.com".into()),
(
"xsi:schemaLocation".into(),
"http://example.com schema.xsd".into(),
),
],
namespace_decls: vec![],
line: None,
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
assert!(validator.is_valid());
}
#[test]
fn test_streaming_validator_nested_elements() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "child".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "grandchild".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::Text("content".to_string()))
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "grandchild".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "child".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
assert!(validator.is_valid());
}
#[test]
fn test_streaming_validator_other_events() {
let schema = CompiledSchema::new();
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::ProcessingInstruction {
target: "xml".to_string(),
content: Some("version=\"1.0\"".to_string()),
})
.unwrap();
validator
.handle(&XmlEvent::Comment("This is a comment".to_string()))
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: None,
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
assert!(validator.is_valid());
}
#[test]
fn test_inherited_elements_from_base_type() {
use crate::schema::types::{ComplexType, ContentModel, ElementDef, TypeDef};
let mut schema = CompiledSchema::new();
let mut base_type = ComplexType::new("BaseType");
base_type.content = ContentModel::Sequence(vec![
ElementDef::new("baseElement")
.with_type("xs:string")
.optional(),
]);
schema
.types
.insert("BaseType".to_string(), TypeDef::Complex(base_type));
let mut extended_type = ComplexType::new("ExtendedType");
extended_type.content = ContentModel::ComplexExtension {
base_type: "BaseType".to_string(),
elements: vec![
ElementDef::new("extElement")
.with_type("xs:integer")
.optional(),
],
};
schema
.types
.insert("ExtendedType".to_string(), TypeDef::Complex(extended_type));
let root_elem = ElementDef::new("root").with_type("ExtendedType");
schema.elements.insert("root".to_string(), root_elem);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "baseElement".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::Text("inherited content".to_string()))
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "baseElement".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "extElement".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(3),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("42".to_string())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "extElement".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
let errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("baseElement"))
.collect();
assert!(
errors.is_empty(),
"Inherited element 'baseElement' should be recognized, but got errors: {:?}",
errors
);
assert!(
validator.is_valid(),
"Validation should pass for inherited elements, but got errors: {:?}",
validator.errors()
);
}
#[test]
fn test_multi_level_inheritance() {
use crate::schema::types::{ComplexType, ContentModel, ElementDef, TypeDef};
let mut schema = CompiledSchema::new();
let mut grandparent_type = ComplexType::new("GrandparentType");
grandparent_type.content = ContentModel::Sequence(vec![
ElementDef::new("grandparentElem")
.with_type("xs:string")
.optional(),
]);
schema.types.insert(
"GrandparentType".to_string(),
TypeDef::Complex(grandparent_type),
);
let mut parent_type = ComplexType::new("ParentType");
parent_type.content = ContentModel::ComplexExtension {
base_type: "GrandparentType".to_string(),
elements: vec![
ElementDef::new("parentElem")
.with_type("xs:string")
.optional(),
],
};
schema
.types
.insert("ParentType".to_string(), TypeDef::Complex(parent_type));
let mut child_type = ComplexType::new("ChildType");
child_type.content = ContentModel::ComplexExtension {
base_type: "ParentType".to_string(),
elements: vec![
ElementDef::new("childElem")
.with_type("xs:string")
.optional(),
],
};
schema
.types
.insert("ChildType".to_string(), TypeDef::Complex(child_type));
let root_elem = ElementDef::new("root").with_type("ChildType");
schema.elements.insert("root".to_string(), root_elem);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "root".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "grandparentElem".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("gp".to_string())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "grandparentElem".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "parentElem".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(3),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("p".to_string())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "parentElem".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "childElem".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(4),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("c".to_string())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "childElem".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "root".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
assert!(
validator.is_valid(),
"Multi-level inheritance should work, but got errors: {:?}",
validator.errors()
);
}
#[test]
fn test_substitution_group_basic() {
use crate::schema::types::{ComplexType, ContentModel, ElementDef, TypeDef};
let mut schema = CompiledSchema::new();
let mut parent_type = ComplexType::new("ParentType");
parent_type.content = ContentModel::Sequence(vec![
ElementDef::new("_CityObject").with_type("AbstractCityObjectType"),
]);
schema
.types
.insert("ParentType".to_string(), TypeDef::Complex(parent_type));
let abstract_type = ComplexType::new("AbstractCityObjectType");
schema.types.insert(
"AbstractCityObjectType".to_string(),
TypeDef::Complex(abstract_type),
);
let concrete_type = ComplexType::new("ReliefFeatureType");
schema.types.insert(
"ReliefFeatureType".to_string(),
TypeDef::Complex(concrete_type),
);
let mut head_elem = ElementDef::new("_CityObject");
head_elem.is_abstract = true;
head_elem.type_ref = Some("AbstractCityObjectType".to_string());
schema.elements.insert("_CityObject".to_string(), head_elem);
let mut substitute_elem = ElementDef::new("ReliefFeature");
substitute_elem.type_ref = Some("ReliefFeatureType".to_string());
substitute_elem.substitution_group = Some("_CityObject".to_string());
schema
.elements
.insert("ReliefFeature".to_string(), substitute_elem);
let parent_elem = ElementDef::new("parent").with_type("ParentType");
schema.elements.insert("parent".to_string(), parent_elem);
schema
.substitution_groups
.insert("_CityObject".to_string(), vec!["ReliefFeature".to_string()]);
schema
.substitution_group_heads
.insert("ReliefFeature".to_string(), "_CityObject".to_string());
schema.transitive_substitution_groups.insert(
"_CityObject".to_string(),
Arc::new(vec!["ReliefFeature".to_string()]),
);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "parent".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "ReliefFeature".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "ReliefFeature".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "parent".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
let errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("ReliefFeature") && e.message.contains("not declared"))
.collect();
assert!(
errors.is_empty(),
"Substitution group member 'ReliefFeature' should be accepted in place of '_CityObject', but got errors: {:?}",
errors
);
assert!(
validator.is_valid(),
"Validation should pass for substitution group members, but got errors: {:?}",
validator.errors()
);
}
#[test]
fn test_substitution_group_max_occurs() {
use crate::schema::types::{ComplexType, ContentModel, ElementDef, TypeDef};
let mut schema = CompiledSchema::new();
let mut parent_type = ComplexType::new("ParentType");
parent_type.content = ContentModel::Sequence(vec![
ElementDef::new("_CityObject")
.with_type("AbstractCityObjectType")
.with_occurs(0, Some(2)),
]);
schema
.types
.insert("ParentType".to_string(), TypeDef::Complex(parent_type));
let abstract_type = ComplexType::new("AbstractCityObjectType");
schema.types.insert(
"AbstractCityObjectType".to_string(),
TypeDef::Complex(abstract_type),
);
let relief_type = ComplexType::new("ReliefFeatureType");
schema.types.insert(
"ReliefFeatureType".to_string(),
TypeDef::Complex(relief_type),
);
let building_type = ComplexType::new("BuildingType");
schema
.types
.insert("BuildingType".to_string(), TypeDef::Complex(building_type));
let mut head_elem = ElementDef::new("_CityObject");
head_elem.is_abstract = true;
head_elem.type_ref = Some("AbstractCityObjectType".to_string());
schema.elements.insert("_CityObject".to_string(), head_elem);
let mut relief_elem = ElementDef::new("ReliefFeature");
relief_elem.type_ref = Some("ReliefFeatureType".to_string());
relief_elem.substitution_group = Some("_CityObject".to_string());
schema
.elements
.insert("ReliefFeature".to_string(), relief_elem);
let mut building_elem = ElementDef::new("Building");
building_elem.type_ref = Some("BuildingType".to_string());
building_elem.substitution_group = Some("_CityObject".to_string());
schema
.elements
.insert("Building".to_string(), building_elem);
let parent_elem = ElementDef::new("parent").with_type("ParentType");
schema.elements.insert("parent".to_string(), parent_elem);
schema.substitution_groups.insert(
"_CityObject".to_string(),
vec!["ReliefFeature".to_string(), "Building".to_string()],
);
schema
.substitution_group_heads
.insert("ReliefFeature".to_string(), "_CityObject".to_string());
schema
.substitution_group_heads
.insert("Building".to_string(), "_CityObject".to_string());
schema.transitive_substitution_groups.insert(
"_CityObject".to_string(),
Arc::new(vec!["ReliefFeature".to_string(), "Building".to_string()]),
);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "parent".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
})
.unwrap();
for (i, name) in ["ReliefFeature", "Building", "ReliefFeature"]
.iter()
.enumerate()
{
validator
.handle(&XmlEvent::StartElement {
name: (*name).into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(i + 2),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: (*name).into(),
prefix: None,
})
.unwrap();
}
validator
.handle(&XmlEvent::EndElement {
name: "parent".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
let errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("occurs") && e.message.contains("maximum"))
.collect();
assert!(
!errors.is_empty(),
"Should have a max_occurs error when 3 substitutes are used but max is 2, errors: {:?}",
validator.errors()
);
}
#[test]
fn test_choice_content_model_basic() {
use crate::schema::types::{ComplexType, ContentModel, ElementDef, TypeDef};
let mut schema = CompiledSchema::new();
let mut choice_type = ComplexType::new("BoundingShapeType");
choice_type.content = ContentModel::Choice(vec![
ElementDef::new("Envelope").with_type("xs:string"),
ElementDef::new("Null").with_type("xs:string"),
]);
schema.types.insert(
"BoundingShapeType".to_string(),
TypeDef::Complex(choice_type),
);
let parent_elem = ElementDef::new("boundedBy").with_type("BoundingShapeType");
schema.elements.insert("boundedBy".to_string(), parent_elem);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "boundedBy".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "Envelope".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "Envelope".into(),
prefix: None,
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "boundedBy".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
let errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("Null") && e.message.contains("requires"))
.collect();
assert!(
errors.is_empty(),
"Choice content model should accept any ONE of the choices, not require ALL. Got errors: {:?}",
errors
);
assert!(
validator.is_valid(),
"Validation should pass when one choice element is present, but got errors: {:?}",
validator.errors()
);
}
#[test]
fn test_validate_simple_api() {
let schema = CompiledSchema::new();
let xml = r#"<root><child>text</child></root>"#;
let reader = std::io::BufReader::new(xml.as_bytes());
let errors = OnePassSchemaValidator::new(Arc::new(schema))
.validate(reader)
.unwrap();
assert!(errors.is_empty());
}
#[test]
fn test_validate_simple_api_with_max_errors() {
use crate::schema::types::ElementDef;
let mut schema = CompiledSchema::new();
schema
.elements
.insert("known".to_string(), ElementDef::new("known"));
let xml = r#"<unknown1><unknown2><unknown3/></unknown2></unknown1>"#;
let reader = std::io::BufReader::new(xml.as_bytes());
let errors = OnePassSchemaValidator::new(Arc::new(schema))
.with_max_errors(2)
.validate(reader)
.unwrap();
assert_eq!(errors.len(), 2);
}
#[test]
fn test_builder_pattern() {
let schema = Arc::new(CompiledSchema::new());
let xml = r#"<root/>"#;
let reader = std::io::BufReader::new(xml.as_bytes());
let errors = OnePassSchemaValidator::new(Arc::clone(&schema))
.set_mode(ValidationMode::Lenient)
.with_max_errors(10)
.validate(reader)
.unwrap();
assert!(errors.is_empty());
}
#[test]
fn test_substitution_group_with_prefixed_elements() {
use crate::schema::types::{ComplexType, ContentModel, ElementDef, TypeDef};
let mut schema = CompiledSchema::new();
let mut parent_type = ComplexType::new("AbstractRingPropertyType");
parent_type.content = ContentModel::Sequence(vec![
ElementDef::new("_Ring").with_type("AbstractRingType"),
]);
schema.types.insert(
"AbstractRingPropertyType".to_string(),
TypeDef::Complex(parent_type),
);
let abstract_type = ComplexType::new("AbstractRingType");
schema.types.insert(
"AbstractRingType".to_string(),
TypeDef::Complex(abstract_type),
);
let concrete_type = ComplexType::new("LinearRingType");
schema.types.insert(
"LinearRingType".to_string(),
TypeDef::Complex(concrete_type),
);
let mut head_elem = ElementDef::new("_Ring");
head_elem.is_abstract = true;
head_elem.type_ref = Some("AbstractRingType".to_string());
schema.elements.insert("_Ring".to_string(), head_elem);
let mut substitute_elem = ElementDef::new("LinearRing");
substitute_elem.type_ref = Some("LinearRingType".to_string());
substitute_elem.substitution_group = Some("_Ring".to_string());
schema
.elements
.insert("LinearRing".to_string(), substitute_elem);
let parent_elem = ElementDef::new("exterior").with_type("AbstractRingPropertyType");
schema.elements.insert("exterior".to_string(), parent_elem);
schema
.substitution_groups
.insert("_Ring".to_string(), vec!["LinearRing".to_string()]);
schema
.substitution_group_heads
.insert("LinearRing".to_string(), "_Ring".to_string());
schema.transitive_substitution_groups.insert(
"_Ring".to_string(),
Arc::new(vec!["LinearRing".to_string()]),
);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "exterior".into(),
prefix: None,
namespace: None,
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "LinearRing".into(), prefix: Some("gml".into()), namespace: Some("http://www.opengis.net/gml".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "LinearRing".into(),
prefix: Some("gml".into()),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "exterior".into(),
prefix: None,
})
.unwrap();
validator.finish().unwrap();
let ring_errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("_Ring"))
.collect();
assert!(
ring_errors.is_empty(),
"Prefixed substitution group member 'gml:LinearRing' should satisfy '_Ring' requirement, but got errors: {:?}",
ring_errors
);
}
#[test]
fn test_same_local_name_different_namespaces() {
use crate::schema::types::{ComplexType, ContentModel, ElementDef, TypeDef};
let mut schema = CompiledSchema::new();
let mut gml_bounding_type = ComplexType::new("BoundingShapeType");
gml_bounding_type.content = ContentModel::Choice(vec![
ElementDef::new("Envelope").with_type("xs:string"),
ElementDef::new("Null").with_type("xs:string"),
]);
schema.types.insert(
"gml:BoundingShapeType".to_string(),
TypeDef::Complex(gml_bounding_type),
);
let mut brid_bounded_type = ComplexType::new("BridgeBoundedByType");
brid_bounded_type.content = ContentModel::Choice(vec![
ElementDef::new("WallSurface").with_type("xs:string"),
ElementDef::new("RoofSurface").with_type("xs:string"),
]);
schema.types.insert(
"brid:BridgeBoundedByType".to_string(),
TypeDef::Complex(brid_bounded_type),
);
let gml_bounded_elem = ElementDef::new("boundedBy").with_type("gml:BoundingShapeType");
schema
.elements
.insert("gml:boundedBy".to_string(), gml_bounded_elem);
let brid_bounded_elem = ElementDef::new("boundedBy").with_type("brid:BridgeBoundedByType");
schema
.elements
.insert("brid:boundedBy".to_string(), brid_bounded_elem);
let gml_cache = FlattenedChildren::with_content_model(ContentModelType::Choice);
schema.type_children_cache.insert(
"gml:BoundingShapeType".to_string(),
Arc::new({
let mut f = gml_cache;
f.constraints.insert("Envelope".to_string(), (0, Some(1)));
f.constraints.insert("Null".to_string(), (0, Some(1)));
f
}),
);
let brid_cache = FlattenedChildren::with_content_model(ContentModelType::Choice);
schema.type_children_cache.insert(
"brid:BridgeBoundedByType".to_string(),
Arc::new({
let mut f = brid_cache;
f.constraints
.insert("WallSurface".to_string(), (0, Some(1)));
f.constraints
.insert("RoofSurface".to_string(), (0, Some(1)));
f
}),
);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "boundedBy".into(),
prefix: Some("brid".into()),
namespace: Some("http://www.opengis.net/citygml/bridge/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "WallSurface".into(),
prefix: Some("brid".into()),
namespace: Some("http://www.opengis.net/citygml/bridge/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "WallSurface".into(),
prefix: Some("brid".into()),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "boundedBy".into(),
prefix: Some("brid".into()),
})
.unwrap();
validator.finish().unwrap();
let envelope_errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("Envelope") || e.message.contains("Null"))
.collect();
assert!(
envelope_errors.is_empty(),
"brid:boundedBy should NOT require Envelope/Null (those are for gml:boundedBy). Got errors: {:?}",
envelope_errors
);
assert!(
validator.is_valid(),
"Validation should pass for brid:boundedBy with WallSurface, but got errors: {:?}",
validator.errors()
);
}