Files
SIA/lib/xml_schema_validator/src/token/attribute_name.rs
2025-04-12 18:02:55 +02:00

165 lines
5.6 KiB
Rust

use crate::*;
use std::sync::Arc;
/// Represents an XML attribute name that is in the process of being parsed
#[derive(Clone, Debug)]
pub struct AttributeName {
buffer: String,
element: Arc<schema::XsElement>,
used_attributes: Vec<schema::XsAttribute>,
}
impl AttributeName {
pub fn new(first_char: char, element: Arc<schema::XsElement>) -> Self {
Self {
buffer: first_char.to_string(),
element,
used_attributes: Vec::new(),
}
}
pub fn new_with_used(first_char: char, element: Arc<schema::XsElement>, used_attributes: Vec<schema::XsAttribute>) -> Self {
Self {
buffer: first_char.to_string(),
element,
used_attributes,
}
}
pub fn append(mut self, c: char) -> Vec<Token> {
if c == '=' {
// Check if we've matched a valid attribute name
if let Some(attributes) = &self.element.xs_complex_type.xs_attribute {
if let Some(attribute) = attributes
.iter()
.find(|attribute| attribute.name == self.buffer)
{
// Check if this attribute has already been used
if self.used_attributes.contains(&attribute) {
return vec![];
}
// Add this attribute to the used set
self.used_attributes.push(attribute.clone());
return vec![Token::AttributeEquals(AttributeEquals::new(
Arc::clone(&self.element),
self.used_attributes,
))];
}
}
return vec![];
} else if c.is_whitespace() {
// Check if we've matched a valid attribute name and allow whitespace after it
if let Some(attributes) = &self.element.xs_complex_type.xs_attribute {
if attributes
.iter()
.any(|attribute| attribute.name == self.buffer)
{
// Check if this attribute has already been used
if self.used_attributes.iter().filter(|attribute| attribute.name == self.buffer).next().is_some() {
return vec![];
}
return vec![Token::AttributeName(self)];
}
}
return vec![];
} else {
// Add character to buffer and check if we're still building a valid attribute name
let new_buffer = self.buffer.clone() + &c.to_string();
if let Some(attributes) = &self.element.xs_complex_type.xs_attribute {
// Check if any attribute name starts with our buffer
if attributes
.iter()
.any(|attribute| attribute.name.starts_with(&new_buffer))
{
return vec![Token::AttributeName(AttributeName {
buffer: new_buffer,
element: Arc::clone(&self.element),
used_attributes: self.used_attributes,
})];
}
}
// Character doesn't match what we expect for this attribute name
return vec![];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_attribute_name() {
let values = [
"<delete id=",
"<single timeout=",
"<single limit=",
];
for value in values {
let mut validator = crate::tests::example_validator();
validator.append(value).expect(value);
assert!(!validator.current_tokens.is_empty(), "{value}");
assert!(
validator
.current_tokens
.iter()
.filter(|token| matches!(token, Token::AttributeEquals(_)))
.next()
.is_some(),
"{value}"
);
}
}
#[test]
fn test_attribute_name_with_whitespace() {
let values = [
"<delete id =",
"<single timeout =",
"<single limit\t=",
];
for value in values {
let mut validator = crate::tests::example_validator();
validator.append(value).expect(value);
assert!(!validator.current_tokens.is_empty(), "{value}");
assert!(
validator
.current_tokens
.iter()
.filter(|token| matches!(token, Token::AttributeEquals(_)))
.next()
.is_some(),
"{value}"
);
}
}
#[test]
fn test_invalid_attribute_name() {
let values = [
"<delete invalid=",
"<single wrongattr=",
];
for value in values {
let mut validator = crate::tests::example_validator();
validator.append(value).expect_err(value);
assert_eq!(validator.current_tokens.len(), 0, "Should reject '{value}'");
}
}
#[test]
fn test_duplicate_attribute() {
let mut validator = crate::tests::example_validator();
// First attribute is valid
validator.append("<delete id=\"123\" ").expect("First attribute should be accepted");
// Try to add the same attribute again
let result = validator.append("id=");
assert!(result.is_err(), "Duplicate attribute should be rejected");
}
}