165 lines
5.6 KiB
Rust
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");
|
|
}
|
|
} |