Fixed attribute parsing

This commit is contained in:
2025-04-12 18:02:55 +02:00
parent bd3adf78e6
commit 9564879edc
23 changed files with 2905 additions and 2786 deletions

View File

@@ -1,196 +1,165 @@
use std::sync::Arc;
use crate::*;
/// Represents an attribute name of an XML element
#[derive(Clone, Debug)]
pub struct AttributeName {
/// The element that owns this attribute
element: Arc<schema::XsElement>,
/// Buffer containing the attribute name characters processed so far
buffer: String,
/// Track which attributes have already been set for this element
attributes_set: Vec<String>,
}
impl AttributeName {
/// Create a new AttributeName with an empty buffer
pub fn new(element: Arc<schema::XsElement>) -> Self {
Self {
element,
buffer: String::new(),
attributes_set: Vec::new(),
}
}
/// Create a new AttributeName with the given buffer
pub fn with_buffer(element: Arc<schema::XsElement>, buffer: String, attributes_set: Vec<String>) -> Self {
Self {
element,
buffer,
attributes_set,
}
}
/// Create a new AttributeName with attributes already set
pub fn with_attributes(element: Arc<schema::XsElement>, attributes_set: Vec<String>) -> Self {
Self {
element,
buffer: String::new(),
attributes_set,
}
}
/// Append a character to the attribute name and return possible continuations
pub fn append(self, c: char) -> Vec<Token> {
if c.is_whitespace() && self.buffer.is_empty() {
return vec![Token::AttributeName(self)];
} else if c == '=' {
// Found the equals sign, transition to attribute value
// We need to verify that this attribute name is valid for this element
if let Some(attributes) = &self.element.xs_complex_type.xs_attribute {
for attr in attributes {
if attr.name == self.buffer {
// Valid attribute, transition to attribute value
return vec![Token::AttributeValue(AttributeValue::new(
Arc::clone(&self.element),
self.buffer,
self.attributes_set,
))];
}
}
}
// Invalid attribute name
vec![]
} else {
// Continue building the attribute name
let new_buffer = self.buffer + &c.to_string();
// Check if this could be a valid attribute for this element
if let Some(attributes) = &self.element.xs_complex_type.xs_attribute {
for attr in attributes {
if attr.name.starts_with(&new_buffer) {
// This could be a valid attribute, continue parsing
return vec![Token::AttributeName(AttributeName::with_buffer(
Arc::clone(&self.element),
new_buffer,
self.attributes_set.clone(),
))];
}
}
}
// No matching attribute found
vec![]
}
}
}
#[cfg(test)]
mod attribute_name_tests {
use super::*;
use std::sync::Arc;
use crate::schema;
fn create_element_with_attributes() -> Arc<schema::XsElement> {
let element = schema::XsElement {
name: "delete".to_string(),
text: None,
xs_complex_type: schema::XsComplexType {
mixed: None,
text: None,
xs_attribute: Some(vec![
schema::XsAttribute {
name: "id".to_string(),
xs_attribute_type: "xs:string".to_string(),
xs_attribute_use: "required".to_string(),
}
]),
xs_sequence: None,
},
};
Arc::new(element)
}
#[test]
fn test_attribute_name_append() {
let element = create_element_with_attributes();
let token = AttributeName::new(Arc::clone(&element));
// Build attribute name character by character
let mut current_token = token;
for c in "id".chars() {
let next_tokens = current_token.append(c);
assert!(!next_tokens.is_empty(), "Should accept attribute name character '{}'", c);
match &next_tokens[0] {
Token::AttributeName(next) => current_token = next.clone(),
_ => panic!("Expected AttributeName, got {:?}", next_tokens[0]),
}
}
// Test equals sign
let next_tokens = current_token.append('=');
assert!(!next_tokens.is_empty(), "Should accept '=' after attribute name");
match &next_tokens[0] {
Token::AttributeValue(_) => (),
_ => panic!("Expected AttributeValue, got {:?}", next_tokens[0]),
}
}
#[test]
fn test_attribute_name_whitespace() {
let element = create_element_with_attributes();
let token = AttributeName::new(Arc::clone(&element));
// Test with whitespace
let next_tokens = token.append(' ');
assert!(!next_tokens.is_empty(), "Should accept whitespace in attribute name");
match &next_tokens[0] {
Token::AttributeName(_) => (),
_ => panic!("Expected AttributeName, got {:?}", next_tokens[0]),
}
}
#[test]
fn test_invalid_attribute_name() {
let element = create_element_with_attributes();
let token = AttributeName::new(Arc::clone(&element));
// Test with invalid attribute name
let next_tokens = token.append('x'); // 'x' doesn't start any valid attribute
assert!(next_tokens.is_empty(), "Should reject invalid attribute name");
}
#[test]
fn test_attribute_tracking() {
let element = create_element_with_attributes();
// Create AttributeName with pre-existing attributes
let attributes_set = vec!["other".to_string()];
let token = AttributeName::with_attributes(Arc::clone(&element), attributes_set.clone());
// Build attribute name and transition to AttributeValue
let mut current_token = token;
for c in "id".chars() {
let next_tokens = current_token.append(c);
match &next_tokens[0] {
Token::AttributeName(next) => current_token = next.clone(),
_ => panic!("Expected AttributeName, got {:?}", next_tokens[0]),
}
}
// Test equals sign
let next_tokens = current_token.append('=');
match &next_tokens[0] {
Token::AttributeValue(value) => {
// Verify that pre-existing attributes are preserved
assert!(value.attributes_set.contains(&"other".to_string()),
"Pre-existing attribute should be preserved");
},
_ => panic!("Expected AttributeValue, got {:?}", next_tokens[0]),
}
}
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");
}
}