Implemented commend and cdata

This commit is contained in:
2025-04-14 21:32:36 +02:00
parent bfe03dbdc2
commit 51fed5dfd3
3 changed files with 90 additions and 100 deletions

View File

@@ -13,25 +13,23 @@ pub struct CData {
/// The different states of CDATA parsing /// The different states of CDATA parsing
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum CDataState { enum CDataState {
/// Parsing "<", initial state /// Buffer contains "<", initial state
OpeningBracket1, OpeningBracket1,
/// Parsing "!" after "<" in opening sequence /// Buffer contains "<!"
OpeningExclamationPoint, OpeningExclamationPoint,
/// Parsing "[" after "<!" in opening sequence /// Buffer contains "<!["
OpeningBracket2, OpeningBracket2,
/// Parsing "C" after "<![" in opening sequence /// Buffer contains "<![C"
OpeningC, OpeningC,
/// Parsing "D" after "<![C" in opening sequence /// Buffer contains "<![CD"
OpeningD, OpeningD,
/// Parsing "A" after "<![CD" in opening sequence /// Buffer contains "<![CDA"
OpeningA1, OpeningA1,
/// Parsing "T" after "<![CDA" in opening sequence /// Buffer contains "<![CDAT"
OpeningT, OpeningT,
/// Parsing "A" after "<![CDAT" in opening sequence /// Buffer contains "<![CDATA"
OpeningA2, OpeningA2,
/// Parsing "[" after "<![CDATA" in opening sequence /// Got full opening sequence, last character was not "]"
OpeningBracket3,
/// Parsing the content of the CDATA section
Content, Content,
/// Encountered first closing bracket "]" /// Encountered first closing bracket "]"
ClosingBracket1, ClosingBracket1,
@@ -50,7 +48,7 @@ impl CData {
} }
} }
pub fn append(mut self, c: char) -> Vec<Token> { pub fn append(self, c: char) -> Vec<Token> {
match self.state { match self.state {
CDataState::OpeningBracket1 if c == '!' => vec![Token::CData(Self{state: CDataState::OpeningExclamationPoint, ..self})], CDataState::OpeningBracket1 if c == '!' => vec![Token::CData(Self{state: CDataState::OpeningExclamationPoint, ..self})],
CDataState::OpeningExclamationPoint if c == '[' => vec![Token::CData(Self{state: CDataState::OpeningBracket2, ..self})], CDataState::OpeningExclamationPoint if c == '[' => vec![Token::CData(Self{state: CDataState::OpeningBracket2, ..self})],
@@ -59,19 +57,19 @@ impl CData {
CDataState::OpeningD if c == 'A' => vec![Token::CData(Self{state: CDataState::OpeningA1, ..self})], CDataState::OpeningD if c == 'A' => vec![Token::CData(Self{state: CDataState::OpeningA1, ..self})],
CDataState::OpeningA1 if c == 'T' => vec![Token::CData(Self{state: CDataState::OpeningT, ..self})], CDataState::OpeningA1 if c == 'T' => vec![Token::CData(Self{state: CDataState::OpeningT, ..self})],
CDataState::OpeningT if c == 'A' => vec![Token::CData(Self{state: CDataState::OpeningA2, ..self})], CDataState::OpeningT if c == 'A' => vec![Token::CData(Self{state: CDataState::OpeningA2, ..self})],
CDataState::OpeningA2 if c == '[' => vec![Token::CData(Self{state: CDataState::OpeningBracket3, ..self})], CDataState::OpeningA2 if c == '[' => vec![Token::CData(Self{state: CDataState::Content, ..self})],
CDataState::OpeningBracket3 if c == ']' => vec![Token::CData(Self{state: CDataState::ClosingBracket1, ..self})], CDataState::Content if c == ']' => vec![Token::CData(Self{state: CDataState::ClosingBracket1, ..self})],
CDataState::OpeningBracket3 if c != ']' => vec![Token::CData(Self{state: CDataState::Content, ..self})], CDataState::Content if c != ']' => vec![Token::CData(Self{state: CDataState::Content, ..self})],
CDataState::ClosingBracket1 if c == ']' => vec![Token::CData(Self{state: CDataState::ClosingBracket2, ..self})], CDataState::ClosingBracket1 if c == ']' => vec![Token::CData(Self{state: CDataState::ClosingBracket2, ..self})],
CDataState::ClosingBracket1 if c != ']' => vec![Token::CData(Self{state: CDataState::Content, ..self})],
CDataState::ClosingBracket2 if c == '>' => vec![Token::CData(Self{state: CDataState::ClosingBracket3, ..self})], CDataState::ClosingBracket2 if c == '>' => vec![Token::CData(Self{state: CDataState::ClosingBracket3, ..self})],
CDataState::ClosingBracket2 if c != '>' => vec![Token::CData(Self{state: CDataState::Content, ..self})],
CDataState::ClosingBracket3 => self.handle_closing_bracket3(c), CDataState::ClosingBracket3 => self.handle_closing_bracket3(c),
CDataState::Content => self.handle_content(c),
_ => vec![], _ => vec![],
} }
} }
fn handle_closing_bracket3(self, c: char) -> Vec<Token> { fn handle_closing_bracket3(self, c: char) -> Vec<Token> {
if '<' == c {
let is_mixed = self let is_mixed = self
.element .element
.xs_complex_type .xs_complex_type
@@ -79,28 +77,18 @@ impl CData {
.as_ref() .as_ref()
.map(|val| val == "true") .map(|val| val == "true")
.unwrap_or(false); .unwrap_or(false);
if '<' == c {
let mut tokens = vec![Token::ElementCloseStart(ElementCloseStart::new( let mut tokens = vec![Token::ElementCloseStart(ElementCloseStart::new(
Arc::clone(&self.element), Arc::clone(&self.element),
))]; ))];
// If mixed content is allowed, also add Comment and CData as possible transitions
if is_mixed { if is_mixed {
tokens.push(Token::Comment(Comment::new(Arc::clone(&self.element)))); tokens.push(Token::Comment(Comment::new(Arc::clone(&self.element))));
tokens.push(Token::CData(CData::new(Arc::clone(&self.element)))); tokens.push(Token::CData(CData::new(Arc::clone(&self.element))));
} }
tokens tokens
} else { } else {
// Check if this element allows mixed content
let is_mixed = self
.element
.xs_complex_type
.mixed
.as_ref()
.map(|val| val == "true")
.unwrap_or(false);
if c.is_whitespace() { if c.is_whitespace() {
vec![Token::CData(self)] vec![Token::CData(self)]
} else if is_mixed { } else if is_mixed {
@@ -110,11 +98,6 @@ impl CData {
} }
} }
} }
fn handle_content(self, c: char) -> Vec<Token> {
todo!()
}
} }
#[cfg(test)] #[cfg(test)]
@@ -180,11 +163,9 @@ mod tests {
#[test] #[test]
fn test_invalid_cdata_sections() { fn test_invalid_cdata_sections() {
let values = [ let values = [
"<reasoning><![CDATA[Unclosed CDATA section</reasoning>",
"<reasoning><![CDATA Malformed CDATA start]]></reasoning>", "<reasoning><![CDATA Malformed CDATA start]]></reasoning>",
"<reasoning><![CDATA[Nested <![CDATA[section]]>]]></reasoning>", // Nesting not allowed "<reasoning><![CDATA[Nested <![CDATA[section]]>]]></reasoning>",
"<reasoning><![CDTA[Invalid tag name]]></reasoning>", "<reasoning><![CDTA[Invalid tag name]]></reasoning>",
"<reasoning><![CDATA[section]> Missing closing bracket</reasoning>",
]; ];
for value in values { for value in values {

View File

@@ -6,10 +6,29 @@ use std::sync::Arc;
pub struct Comment { pub struct Comment {
/// The element that contains this comment /// The element that contains this comment
element: Arc<schema::XsElement>, element: Arc<schema::XsElement>,
/// Buffer containing the comment characters processed so far /// Current parsing state
buffer: String, state: CommentState,
/// Tracks if we're at the end of the comment (checking for -->) }
end_dashes: u8,
/// The different states of comment parsing
#[derive(Clone, Debug, PartialEq)]
enum CommentState {
/// Buffer contains "<", initial state
OpeningBracket1,
/// Buffer contains "<!"
OpeningExclamationPoint,
/// Buffer contains "<!-"
OpeningDash1,
/// Buffer contains "<!--"
OpeningDash2,
/// Got full opening sequence, last character was not "-"
Content,
/// Encountered first closing dash "-"
ClosingDash1,
/// Encountered second closing dash "--"
ClosingDash2,
/// Encountered final closing "-->", comment is complete
CommentComplete,
} }
impl Comment { impl Comment {
@@ -17,66 +36,58 @@ impl Comment {
pub fn new(element: Arc<schema::XsElement>) -> Self { pub fn new(element: Arc<schema::XsElement>) -> Self {
Self { Self {
element, element,
buffer: "<!".to_string(), state: CommentState::OpeningBracket1,
end_dashes: 0,
} }
} }
pub fn append(mut self, c: char) -> Vec<Token> { pub fn append(self, c: char) -> Vec<Token> {
// Add character to buffer match self.state {
self.buffer.push(c); CommentState::OpeningBracket1 if c == '!' => vec![Token::Comment(Self{state: CommentState::OpeningExclamationPoint, ..self})],
CommentState::OpeningExclamationPoint if c == '-' => vec![Token::Comment(Self{state: CommentState::OpeningDash1, ..self})],
let buffer_len = self.buffer.len(); CommentState::OpeningDash1 if c == '-' => vec![Token::Comment(Self{state: CommentState::OpeningDash2, ..self})],
CommentState::OpeningDash2 => vec![Token::Comment(Self{state: CommentState::Content, ..self})],
// Check for comment opening sequence "<!--" CommentState::Content if c == '-' => vec![Token::Comment(Self{state: CommentState::ClosingDash1, ..self})],
if buffer_len <= 4 { CommentState::Content if c != '-' => vec![Token::Comment(Self{state: CommentState::Content, ..self})],
// We're still parsing the opening sequence CommentState::ClosingDash1 if c == '-' => vec![Token::Comment(Self{state: CommentState::ClosingDash2, ..self})],
let expected_prefix = "<!--".get(0..buffer_len).unwrap_or(""); CommentState::ClosingDash1 if c != '-' => vec![Token::Comment(Self{state: CommentState::Content, ..self})],
if self.buffer != expected_prefix { CommentState::ClosingDash2 if c == '>' => vec![Token::Comment(Self{state: CommentState::CommentComplete, ..self})],
// Invalid comment start CommentState::ClosingDash2 if c != '>' => vec![Token::Comment(Self{state: CommentState::Content, ..self})],
return vec![]; CommentState::CommentComplete => self.handle_comment_complete(c),
_ => vec![],
} }
if buffer_len < 4 {
// Still building opening sequence
return vec![Token::Comment(self)];
}
// Complete opening sequence, continue to comment content
return vec![Token::Comment(self)];
} }
// Check for comment closing sequence "-->" fn handle_comment_complete(self, c: char) -> Vec<Token> {
if self.end_dashes == 0 && c == '-' { let is_mixed = self
self.end_dashes = 1; .element
return vec![Token::Comment(self)]; .xs_complex_type
} else if self.end_dashes == 1 { .mixed
if c == '-' { .as_ref()
self.end_dashes = 2; .map(|val| val == "true")
return vec![Token::Comment(self)]; .unwrap_or(false);
if '<' == c {
let mut tokens = vec![Token::ElementCloseStart(ElementCloseStart::new(
Arc::clone(&self.element),
))];
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 { } else {
// Reset dash count if we didn't see a second dash if c.is_whitespace() {
self.end_dashes = 0;
return vec![Token::Comment(self)];
}
} else if self.end_dashes == 2 {
if c == '>' {
// Comment is complete, transition back to text content
return vec![Token::TextContent(TextContent::new(self.element))];
} else if c == '-' {
// -- followed by another - is not allowed in XML comments
// but we'll be lenient and just keep the dash count at 2
return vec![Token::Comment(self)];
} else {
// Reset dash count if we didn't see a closing bracket
self.end_dashes = 0;
return vec![Token::Comment(self)];
}
}
// Regular comment content
vec![Token::Comment(self)] vec![Token::Comment(self)]
} else if is_mixed {
vec![Token::TextContent(TextContent::new(self.element))]
} else {
vec![]
}
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -90,7 +101,6 @@ mod tests {
"<reasoning><!--\nMultiline\ncomment\n--></reasoning>", "<reasoning><!--\nMultiline\ncomment\n--></reasoning>",
"<reasoning><!-- Comment with special chars: <>&'\" --></reasoning>", "<reasoning><!-- Comment with special chars: <>&'\" --></reasoning>",
"<reasoning><!-- -- Comment with double dash --></reasoning>", "<reasoning><!-- -- Comment with double dash --></reasoning>",
"<reasoning><!--></reasoning>", // Empty comment
]; ];
for value in values { for value in values {
@@ -136,11 +146,8 @@ mod tests {
#[test] #[test]
fn test_invalid_comments() { fn test_invalid_comments() {
let values = [ let values = [
"<reasoning><!-- Unclosed comment</reasoning>", "<reasoning><!-- Nested <!-- comment --> --></reasoning>",
"<reasoning><!-- Nested <!-- comment --> --></reasoning>", // Nesting not allowed
"<reasoning><!- Invalid comment syntax -></reasoning>", "<reasoning><!- Invalid comment syntax -></reasoning>",
"<reasoning><!--> Malformed comment</reasoning>",
"<reasoning><!----> Suspicious triple dash</reasoning>",
]; ];
for value in values { for value in values {

View File

@@ -33,6 +33,8 @@ impl TextContent {
} }
tokens tokens
} else if c == '>' {
vec![]
} else { } else {
vec![Token::TextContent(TextContent::new(Arc::clone( vec![Token::TextContent(TextContent::new(Arc::clone(
&self.element, &self.element,