222 lines
6.7 KiB
Rust
222 lines
6.7 KiB
Rust
use crate::*;
|
|
use std::sync::Arc;
|
|
|
|
/// Represents the text content within an XML element
|
|
#[derive(Clone, Debug)]
|
|
pub struct TextContent {
|
|
element: Arc<schema::XsElement>,
|
|
}
|
|
|
|
impl TextContent {
|
|
pub fn new(element: Arc<schema::XsElement>) -> Self {
|
|
Self { element }
|
|
}
|
|
|
|
pub fn append(self, c: char) -> Vec<Token> {
|
|
if c == '<' {
|
|
let is_mixed = self
|
|
.element
|
|
.xs_complex_type
|
|
.mixed
|
|
.as_ref()
|
|
.map(|val| val == "true")
|
|
.unwrap_or(false);
|
|
|
|
let mut tokens = vec![Token::ElementCloseStart(ElementCloseStart::new(
|
|
Arc::clone(&self.element),
|
|
))];
|
|
|
|
// If mixed content is allowed, also add Comment and CData as possible transitions
|
|
if is_mixed {
|
|
tokens.push(Token::Comment(Comment::new(Arc::clone(&self.element))));
|
|
tokens.push(Token::CData(CData::new(Arc::clone(&self.element))));
|
|
}
|
|
|
|
tokens
|
|
} else {
|
|
vec![Token::TextContent(TextContent::new(Arc::clone(
|
|
&self.element,
|
|
)))]
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_valid_text_content() {
|
|
let values = [
|
|
"<reasoning>text",
|
|
"<reasoning>Hello world",
|
|
"<reasoning>Special chars: !@#$%^&*()",
|
|
"<reasoning>Numbers: 12345",
|
|
"<reasoning>Mixed content with spaces",
|
|
"<write_stdout>Output message",
|
|
"<single>ls -la /tmp",
|
|
];
|
|
|
|
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()
|
|
.any(|token| matches!(token, Token::TextContent(_))),
|
|
"{value}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_whitespace_text_content() {
|
|
let values = [
|
|
"<reasoning> ",
|
|
"<reasoning> ",
|
|
"<reasoning>\t",
|
|
"<reasoning>\n",
|
|
"<reasoning> \t\n ",
|
|
"<write_stdout> ",
|
|
"<single> ",
|
|
];
|
|
|
|
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()
|
|
.any(|token| matches!(token, Token::TextContent(_))),
|
|
"{value}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_valid_element_close_start_after_text() {
|
|
let values = [
|
|
"<reasoning>text<",
|
|
"<reasoning>Hello world<",
|
|
"<write_stdout>message<",
|
|
"<single>command<",
|
|
];
|
|
|
|
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()
|
|
.any(|token| matches!(token, Token::ElementCloseStart(_))),
|
|
"{value}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_complete_element_with_text() {
|
|
let values = [
|
|
"<reasoning>text</reasoning>",
|
|
"<reasoning>Hello world</reasoning>",
|
|
"<write_stdout>message</write_stdout>",
|
|
"<single>command</single>",
|
|
];
|
|
|
|
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()
|
|
.any(|token| matches!(token, Token::EndOfFile(_))),
|
|
"{value}"
|
|
);
|
|
}
|
|
}
|
|
|
|
// New tests for transitions to comment and CDATA
|
|
|
|
#[test]
|
|
fn test_text_to_comment_transition() {
|
|
let values = [
|
|
"<reasoning>text<",
|
|
"<reasoning>text<!",
|
|
"<reasoning>text<!--",
|
|
"<reasoning>text<!-- comment",
|
|
];
|
|
|
|
for value in values {
|
|
let mut validator = crate::tests::example_validator();
|
|
validator.append(value).expect(value);
|
|
assert!(!validator.current_tokens.is_empty(), "{value}");
|
|
|
|
let is_valid = validator
|
|
.current_tokens
|
|
.iter()
|
|
.any(|token| matches!(token, Token::Comment(_)));
|
|
|
|
assert!(is_valid, "{value}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_text_to_cdata_transition() {
|
|
let values = [
|
|
"<reasoning>text<",
|
|
"<reasoning>text<!",
|
|
"<reasoning>text<![",
|
|
"<reasoning>text<![C",
|
|
"<reasoning>text<![CD",
|
|
"<reasoning>text<![CDA",
|
|
"<reasoning>text<![CDAT",
|
|
"<reasoning>text<![CDATA",
|
|
"<reasoning>text<![CDATA[",
|
|
];
|
|
|
|
for value in values {
|
|
let mut validator = crate::tests::example_validator();
|
|
validator.append(value).expect(value);
|
|
assert!(!validator.current_tokens.is_empty(), "{value}");
|
|
|
|
// Should transition to CDATA state or remain in TextContent for partial prefix
|
|
let is_valid = validator
|
|
.current_tokens
|
|
.iter()
|
|
.any(|token| matches!(token, Token::CData(_)));
|
|
|
|
assert!(is_valid, "{value}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_text_with_mixed_content() {
|
|
let values = [
|
|
"<reasoning>text<!-- comment -->more text</reasoning>",
|
|
"<reasoning>text<![CDATA[cdata content]]>more text</reasoning>",
|
|
"<reasoning>text<!-- comment --><![CDATA[cdata]]>more</reasoning>",
|
|
"<reasoning><!-- c1 -->text<![CDATA[cd]]><!-- c2 --></reasoning>",
|
|
];
|
|
|
|
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()
|
|
.any(|token| matches!(token, Token::EndOfFile(_))),
|
|
"{value}"
|
|
);
|
|
}
|
|
}
|
|
} |