Fixed attribute parsing
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user