use std::sync::Arc;
use smallvec::SmallVec;
use crate::namespace::Namespace;
use crate::schema::types::{ContentModelType, FlattenedChildren};
#[derive(Debug, Default)]
pub(crate) struct ValidationState {
pub element_stack: Vec<ElementContext>,
pub depth: usize,
pub namespace_stack: Vec<SmallVec<[(String, String); 4]>>,
}
#[derive(Debug, Clone)]
pub(crate) struct ElementContext {
pub name: Arc<str>,
#[allow(dead_code)]
pub namespace: Option<Arc<str>>,
pub child_counts: SmallVec<[(Arc<str>, u32); 8]>,
pub text_content: String,
pub schema_validated: bool,
pub type_ref: Option<String>,
pub flattened_children: Option<Arc<FlattenedChildren>>,
pub sequence_index: usize,
}
impl ElementContext {
pub fn new(name: Arc<str>, namespace: Option<Arc<str>>) -> Self {
Self {
name,
namespace,
child_counts: SmallVec::new(),
text_content: String::new(),
schema_validated: false,
type_ref: None,
flattened_children: None,
sequence_index: 0,
}
}
#[allow(dead_code)]
pub fn from_str(name: &str, namespace: Option<&str>) -> Self {
Self::new(Arc::from(name), namespace.map(Arc::from))
}
pub fn increment_child_arc(&mut self, child_name: &Arc<str>) -> u32 {
for (name, count) in &mut self.child_counts {
if Arc::ptr_eq(name, child_name) || name.as_ref() == child_name.as_ref() {
*count += 1;
return *count;
}
}
self.child_counts.push((Arc::clone(child_name), 1));
1
}
#[allow(dead_code)]
pub fn increment_child(&mut self, child_name: &str) -> u32 {
for (name, count) in &mut self.child_counts {
if name.as_ref() == child_name {
*count += 1;
return *count;
}
}
self.child_counts.push((Arc::from(child_name), 1));
1
}
pub fn get_child_count(&self, child_name: &str) -> u32 {
let mut total = 0;
let has_prefix = child_name.contains(':');
for (name, count) in &self.child_counts {
if name.as_ref() == child_name {
total += count;
} else if !has_prefix {
if let Some((_prefix, local)) = name.split_once(':') {
if local == child_name {
total += count;
}
}
}
}
total
}
#[allow(dead_code)]
pub fn content_model_type(&self) -> ContentModelType {
self.flattened_children
.as_ref()
.map(|f| f.content_model_type)
.unwrap_or(ContentModelType::Empty)
}
pub fn expects_child(&self, child_name: &str) -> bool {
self.flattened_children
.as_ref()
.map(|f| f.constraints.contains_key(child_name))
.unwrap_or(false)
}
pub fn get_child_constraint(&self, child_name: &str) -> Option<(u32, Option<u32>)> {
self.flattened_children
.as_ref()
.and_then(|f| f.constraints.get(child_name).copied())
}
#[allow(dead_code)]
pub fn expected_children(&self) -> impl Iterator<Item = (&String, &(u32, Option<u32>))> {
self.flattened_children
.iter()
.flat_map(|f| f.constraints.iter())
}
}
impl ValidationState {
pub fn new() -> Self {
Self {
element_stack: Vec::with_capacity(64),
depth: 0,
namespace_stack: vec![SmallVec::new()],
}
}
pub fn push_element(&mut self, name: Arc<str>, namespace: Option<Arc<str>>) {
if let Some(parent) = self.element_stack.last_mut() {
parent.increment_child_arc(&name);
}
self.element_stack
.push(ElementContext::new(name, namespace));
self.depth += 1;
}
#[allow(dead_code)]
pub fn push_element_str(&mut self, name: &str, namespace: Option<&str>) {
self.push_element(Arc::from(name), namespace.map(Arc::from));
}
pub fn pop_element(&mut self) -> Option<ElementContext> {
self.depth = self.depth.saturating_sub(1);
self.element_stack.pop()
}
#[allow(dead_code)]
pub fn current_element(&self) -> Option<&ElementContext> {
self.element_stack.last()
}
pub fn current_element_mut(&mut self) -> Option<&mut ElementContext> {
self.element_stack.last_mut()
}
pub fn push_namespaces(&mut self, decls: &[Namespace]) {
let bindings: SmallVec<[(String, String); 4]> = decls
.iter()
.map(|ns| (ns.prefix().to_string(), ns.uri().to_string()))
.collect();
self.namespace_stack.push(bindings);
}
pub fn pop_namespaces(&mut self) {
if self.namespace_stack.len() > 1 {
self.namespace_stack.pop();
}
}
#[allow(dead_code)]
pub fn resolve_prefix(&self, prefix: &str) -> Option<&str> {
for scope in self.namespace_stack.iter().rev() {
for (p, uri) in scope {
if p == prefix {
return Some(uri.as_str());
}
}
}
None
}
pub fn element_path(&self) -> String {
if self.element_stack.is_empty() {
return "/".to_string();
}
let mut path = String::new();
for ctx in &self.element_stack {
path.push('/');
path.push_str(&ctx.name);
}
path
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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);
assert!(ctx.type_ref.is_none());
assert!(ctx.flattened_children.is_none());
}
#[test]
fn test_element_context_new_without_namespace() {
let ctx = ElementContext::from_str("test", None);
assert_eq!(ctx.name.as_ref(), "test");
assert!(ctx.namespace.is_none());
}
#[test]
fn test_element_context_increment_child() {
let mut ctx = ElementContext::from_str("parent", None);
assert_eq!(ctx.increment_child("child"), 1);
assert_eq!(ctx.increment_child("child"), 2);
assert_eq!(ctx.increment_child("other"), 1);
assert_eq!(ctx.increment_child("child"), 3);
}
#[test]
fn test_element_context_get_child_count() {
let mut ctx = ElementContext::from_str("parent", None);
assert_eq!(ctx.get_child_count("child"), 0);
ctx.increment_child("child");
assert_eq!(ctx.get_child_count("child"), 1);
ctx.increment_child("child");
assert_eq!(ctx.get_child_count("child"), 2);
assert_eq!(ctx.get_child_count("other"), 0);
}
#[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);
state.push_element_str("child", Some("http://ns"));
assert_eq!(state.depth, 2);
assert_eq!(state.element_stack.len(), 2);
let popped = state.pop_element();
assert!(popped.is_some());
assert_eq!(popped.unwrap().name.as_ref(), "child");
assert_eq!(state.depth, 1);
let popped = state.pop_element();
assert!(popped.is_some());
assert_eq!(popped.unwrap().name.as_ref(), "root");
assert_eq!(state.depth, 0);
}
#[test]
fn test_validation_state_child_count_increment() {
let mut state = ValidationState::new();
state.push_element_str("parent", None);
state.push_element_str("child1", None);
state.pop_element();
state.push_element_str("child1", None);
state.pop_element();
state.push_element_str("child2", None);
state.pop_element();
let parent = state.pop_element().unwrap();
assert_eq!(parent.get_child_count("child1"), 2);
assert_eq!(parent.get_child_count("child2"), 1);
}
#[test]
fn test_validation_state_current_element() {
let mut state = ValidationState::new();
assert!(state.current_element().is_none());
state.push_element_str("root", None);
assert!(state.current_element().is_some());
assert_eq!(state.current_element().unwrap().name.as_ref(), "root");
}
#[test]
fn test_validation_state_current_element_mut() {
let mut state = ValidationState::new();
state.push_element_str("root", None);
if let Some(ctx) = state.current_element_mut() {
ctx.text_content.push_str("hello");
}
assert_eq!(state.current_element().unwrap().text_content, "hello");
}
#[test]
fn test_validation_state_push_pop_namespaces() {
let mut state = ValidationState::new();
assert_eq!(state.namespace_stack.len(), 1);
let decls = vec![Namespace::new(
"ns".to_string(),
"http://example.com".to_string(),
)];
state.push_namespaces(&decls);
assert_eq!(state.namespace_stack.len(), 2);
assert_eq!(state.resolve_prefix("ns"), Some("http://example.com"));
state.pop_namespaces();
assert_eq!(state.namespace_stack.len(), 1);
}
#[test]
fn test_validation_state_resolve_prefix() {
let mut state = ValidationState::new();
assert!(state.resolve_prefix("ns").is_none());
let decls = vec![Namespace::new(
"ns".to_string(),
"http://example.com".to_string(),
)];
state.push_namespaces(&decls);
assert_eq!(state.resolve_prefix("ns"), Some("http://example.com"));
assert!(state.resolve_prefix("other").is_none());
}
#[test]
fn test_validation_state_element_path_empty() {
let state = ValidationState::new();
assert_eq!(state.element_path(), "/");
}
#[test]
fn test_validation_state_element_path() {
let mut state = ValidationState::new();
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");
state.push_element_str("grandchild", None);
assert_eq!(state.element_path(), "/root/child/grandchild");
}
#[test]
fn test_validation_state_pop_empty() {
let mut state = ValidationState::new();
assert!(state.pop_element().is_none());
assert_eq!(state.depth, 0);
}
#[test]
fn test_namespace_stack_pop_minimum() {
let mut state = ValidationState::new();
assert_eq!(state.namespace_stack.len(), 1);
state.pop_namespaces();
assert_eq!(state.namespace_stack.len(), 1);
state.pop_namespaces();
assert_eq!(state.namespace_stack.len(), 1);
}
#[test]
fn test_get_child_count_matches_local_name_with_prefix() {
let mut ctx = ElementContext::from_str("parent", None);
ctx.increment_child("gml:LinearRing");
assert_eq!(ctx.get_child_count("gml:LinearRing"), 1);
assert_eq!(
ctx.get_child_count("LinearRing"),
1,
"get_child_count should match local name 'LinearRing' for prefixed child 'gml:LinearRing'"
);
}
#[test]
fn test_get_child_count_matches_local_name_multiple_prefixes() {
let mut ctx = ElementContext::from_str("parent", None);
ctx.increment_child("gml:Ring");
ctx.increment_child("ns:Ring");
assert_eq!(ctx.get_child_count("gml:Ring"), 1);
assert_eq!(ctx.get_child_count("ns:Ring"), 1);
assert_eq!(
ctx.get_child_count("Ring"),
2,
"get_child_count with local name should sum all prefixed variants"
);
}
}