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
#[derive(Clone, Debug, PartialEq)]
enum CDataState {
/// Parsing "<", initial state
/// Buffer contains "<", initial state
OpeningBracket1,
/// Parsing "!" after "<" in opening sequence
/// Buffer contains "<!"
OpeningExclamationPoint,
/// Parsing "[" after "<!" in opening sequence
/// Buffer contains "<!["
OpeningBracket2,
/// Parsing "C" after "<![" in opening sequence
/// Buffer contains "<![C"
OpeningC,
/// Parsing "D" after "<![C" in opening sequence
/// Buffer contains "<![CD"
OpeningD,
/// Parsing "A" after "<![CD" in opening sequence
/// Buffer contains "<![CDA"
OpeningA1,
/// Parsing "T" after "<![CDA" in opening sequence
/// Buffer contains "<![CDAT"
OpeningT,
/// Parsing "A" after "<![CDAT" in opening sequence
/// Buffer contains "<![CDATA"
OpeningA2,
/// Parsing "[" after "<![CDATA" in opening sequence
OpeningBracket3,
/// Parsing the content of the CDATA section
/// Got full opening sequence, last character was not "]"
Content,
/// Encountered first closing bracket "]"
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 {
CDataState::OpeningBracket1 if c == '!' => vec![Token::CData(Self{state: CDataState::OpeningExclamationPoint, ..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::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::OpeningA2 if c == '[' => vec![Token::CData(Self{state: CDataState::OpeningBracket3, ..self})],
CDataState::OpeningBracket3 if c == ']' => vec![Token::CData(Self{state: CDataState::ClosingBracket1, ..self})],
CDataState::OpeningBracket3 if c != ']' => vec![Token::CData(Self{state: CDataState::Content, ..self})],
CDataState::OpeningA2 if c == '[' => vec![Token::CData(Self{state: CDataState::Content, ..self})],
CDataState::Content if c == ']' => vec![Token::CData(Self{state: CDataState::ClosingBracket1, ..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::Content, ..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::Content => self.handle_content(c),
_ => vec![],
}
}
fn handle_closing_bracket3(self, c: char) -> Vec<Token> {
if '<' == c {
let is_mixed = self
.element
.xs_complex_type
@@ -79,28 +77,18 @@ impl CData {
.as_ref()
.map(|val| val == "true")
.unwrap_or(false);
if '<' == c {
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 {
// 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() {
vec![Token::CData(self)]
} else if is_mixed {
@@ -110,11 +98,6 @@ impl CData {
}
}
}
fn handle_content(self, c: char) -> Vec<Token> {
todo!()
}
}
#[cfg(test)]
@@ -180,11 +163,9 @@ mod tests {
#[test]
fn test_invalid_cdata_sections() {
let values = [
"<reasoning><![CDATA[Unclosed CDATA section</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><![CDATA[section]> Missing closing bracket</reasoning>",
];
for value in values {

View File

@@ -6,10 +6,29 @@ use std::sync::Arc;
pub struct Comment {
/// The element that contains this comment
element: Arc<schema::XsElement>,
/// Buffer containing the comment characters processed so far
buffer: String,
/// Tracks if we're at the end of the comment (checking for -->)
end_dashes: u8,
/// Current parsing state
state: CommentState,
}
/// 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 {
@@ -17,66 +36,58 @@ impl Comment {
pub fn new(element: Arc<schema::XsElement>) -> Self {
Self {
element,
buffer: "<!".to_string(),
end_dashes: 0,
state: CommentState::OpeningBracket1,
}
}
pub fn append(mut self, c: char) -> Vec<Token> {
// Add character to buffer
self.buffer.push(c);
let buffer_len = self.buffer.len();
// Check for comment opening sequence "<!--"
if buffer_len <= 4 {
// We're still parsing the opening sequence
let expected_prefix = "<!--".get(0..buffer_len).unwrap_or("");
if self.buffer != expected_prefix {
// Invalid comment start
return vec![];
pub fn append(self, c: char) -> Vec<Token> {
match self.state {
CommentState::OpeningBracket1 if c == '!' => vec![Token::Comment(Self{state: CommentState::OpeningExclamationPoint, ..self})],
CommentState::OpeningExclamationPoint if c == '-' => vec![Token::Comment(Self{state: CommentState::OpeningDash1, ..self})],
CommentState::OpeningDash1 if c == '-' => vec![Token::Comment(Self{state: CommentState::OpeningDash2, ..self})],
CommentState::OpeningDash2 => vec![Token::Comment(Self{state: CommentState::Content, ..self})],
CommentState::Content if c == '-' => vec![Token::Comment(Self{state: CommentState::ClosingDash1, ..self})],
CommentState::Content if c != '-' => vec![Token::Comment(Self{state: CommentState::Content, ..self})],
CommentState::ClosingDash1 if c == '-' => vec![Token::Comment(Self{state: CommentState::ClosingDash2, ..self})],
CommentState::ClosingDash1 if c != '-' => vec![Token::Comment(Self{state: CommentState::Content, ..self})],
CommentState::ClosingDash2 if c == '>' => vec![Token::Comment(Self{state: CommentState::CommentComplete, ..self})],
CommentState::ClosingDash2 if c != '>' => vec![Token::Comment(Self{state: CommentState::Content, ..self})],
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 "-->"
if self.end_dashes == 0 && c == '-' {
self.end_dashes = 1;
return vec![Token::Comment(self)];
} else if self.end_dashes == 1 {
if c == '-' {
self.end_dashes = 2;
return vec![Token::Comment(self)];
fn handle_comment_complete(self, c: char) -> Vec<Token> {
let is_mixed = self
.element
.xs_complex_type
.mixed
.as_ref()
.map(|val| val == "true")
.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 {
// Reset dash count if we didn't see a second dash
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
if c.is_whitespace() {
vec![Token::Comment(self)]
} else if is_mixed {
vec![Token::TextContent(TextContent::new(self.element))]
} else {
vec![]
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -90,7 +101,6 @@ mod tests {
"<reasoning><!--\nMultiline\ncomment\n--></reasoning>",
"<reasoning><!-- Comment with special chars: <>&'\" --></reasoning>",
"<reasoning><!-- -- Comment with double dash --></reasoning>",
"<reasoning><!--></reasoning>", // Empty comment
];
for value in values {
@@ -136,11 +146,8 @@ mod tests {
#[test]
fn test_invalid_comments() {
let values = [
"<reasoning><!-- Unclosed comment</reasoning>",
"<reasoning><!-- Nested <!-- comment --> --></reasoning>", // Nesting not allowed
"<reasoning><!-- Nested <!-- comment --> --></reasoning>",
"<reasoning><!- Invalid comment syntax -></reasoning>",
"<reasoning><!--> Malformed comment</reasoning>",
"<reasoning><!----> Suspicious triple dash</reasoning>",
];
for value in values {

View File

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