use crate::{
node::{ElementNode, Node},
token::Token,
};
use super::{error::ParserError, parser_trait::ParserTrait};
pub struct Parser {
tokens: Vec<Token>,
position: usize,
}
impl Parser {
fn next_token(&mut self) -> Option<Token> {
if self.position < self.tokens.len() {
let token = self.tokens[self.position].clone();
self.position += 1;
Some(token)
} else {
None
}
}
fn read_attributes(&mut self) -> Vec<(String, String)> {
let mut attributes = Vec::new();
while let Some(token) = self.next_token() {
match token {
Token::AttributeName(attr_name) => {
if let Some(Token::AttributeValue(attr_value)) = self.next_token() {
attributes.push((attr_name, attr_value));
} else {
attributes.push((attr_name, "true".to_string()));
self.position -= 1;
}
}
_ => {
self.position -= 1;
break;
}
}
}
attributes
}
fn parse_element(&mut self, tag_name: String) -> Result<ElementNode, ParserError> {
let attributes = self.read_attributes();
let mut children = Vec::new();
while let Some(token) = self.next_token() {
match token {
Token::TagClose(ref close_tag) if *close_tag == tag_name => {
return Ok(ElementNode {
tag_name,
attributes,
children,
});
}
Token::TagClose(close_tag) => {
return Err(ParserError::UnexpectedClosingTag(tag_name, close_tag));
}
Token::TagOpen(tag) => {
children.push(Node::Element(self.parse_element(tag)?));
}
Token::SelfClosingTag(tag) => {
children.push(Node::Element(ElementNode {
tag_name: tag,
attributes: self.read_attributes(),
children: vec![],
}));
}
Token::Text(text) => {
children.push(Node::Text(text));
}
_ => {}
}
}
Err(ParserError::UnexpectedClosingTag(tag_name, "".to_string()))
}
}
impl ParserTrait for Parser {
fn new(tokens: Vec<Token>) -> Self {
Self {
tokens,
position: 0,
}
}
fn parse(&mut self) -> Result<Vec<Node>, ParserError> {
let mut nodes = Vec::new();
while let Some(token) = self.next_token() {
match token {
Token::TagOpen(tag) => {
nodes.push(Node::Element(self.parse_element(tag)?));
}
Token::SelfClosingTag(tag) => {
nodes.push(Node::Element(ElementNode {
tag_name: tag,
attributes: self.read_attributes(),
children: vec![],
}));
}
Token::Text(text) => {
nodes.push(Node::Text(text));
}
Token::TagClose(close_tag) => {
return Err(ParserError::UnexpectedClosingTag(close_tag, "".to_string()));
}
_ => {}
}
}
Ok(nodes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::token::Token;
#[test]
fn test_parse_single_element() {
let tokens = vec![
Token::TagOpen("div".to_string()),
Token::TagClose("div".to_string()),
];
let mut parser = Parser::new(tokens);
let result = parser.parse();
let expected = vec![Node::Element(ElementNode {
tag_name: "div".to_string(),
attributes: vec![],
children: vec![],
})];
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_parse_element_with_text() {
let tokens = vec![
Token::TagOpen("p".to_string()),
Token::Text("Hello, world!".to_string()),
Token::TagClose("p".to_string()),
];
let mut parser = Parser::new(tokens);
let result = parser.parse();
let expected = vec![Node::Element(ElementNode {
tag_name: "p".to_string(),
attributes: vec![],
children: vec![Node::Text("Hello, world!".to_string())],
})];
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_parse_nested_elements() {
let tokens = vec![
Token::TagOpen("div".to_string()),
Token::TagOpen("p".to_string()),
Token::Text("Nested".to_string()),
Token::TagClose("p".to_string()),
Token::TagClose("div".to_string()),
];
let mut parser = Parser::new(tokens);
let result = parser.parse();
let expected = vec![Node::Element(ElementNode {
tag_name: "div".to_string(),
attributes: vec![],
children: vec![Node::Element(ElementNode {
tag_name: "p".to_string(),
attributes: vec![],
children: vec![Node::Text("Nested".to_string())],
})],
})];
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_parse_self_closing_tag() {
let tokens = vec![Token::SelfClosingTag("img".to_string())];
let mut parser = Parser::new(tokens);
let result = parser.parse();
let expected = vec![Node::Element(ElementNode {
tag_name: "img".to_string(),
attributes: vec![],
children: vec![],
})];
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_parse_element_with_attributes() {
let tokens = vec![
Token::SelfClosingTag("input".to_string()),
Token::AttributeName("type".to_string()),
Token::AttributeValue("text".to_string()),
Token::SelfClosingTag("input".to_string()),
];
let mut parser = Parser::new(tokens);
let result = parser.parse();
let expected = vec![
Node::Element(ElementNode {
tag_name: "input".to_string(),
attributes: vec![("type".to_string(), "text".to_string())],
children: vec![],
}),
Node::Element(ElementNode {
tag_name: "input".to_string(),
attributes: vec![],
children: vec![],
}),
];
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_parse_deeply_nested_elements() {
let tokens = vec![
Token::TagOpen("div".to_string()),
Token::TagOpen("section".to_string()),
Token::TagOpen("article".to_string()),
Token::Text("Deep content".to_string()),
Token::TagClose("article".to_string()),
Token::TagClose("section".to_string()),
Token::TagClose("div".to_string()),
];
let mut parser = Parser::new(tokens);
let result = parser.parse();
let expected = vec![Node::Element(ElementNode {
tag_name: "div".to_string(),
attributes: vec![],
children: vec![Node::Element(ElementNode {
tag_name: "section".to_string(),
attributes: vec![],
children: vec![Node::Element(ElementNode {
tag_name: "article".to_string(),
attributes: vec![],
children: vec![Node::Text("Deep content".to_string())],
})],
})],
})];
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_unclosed_div() {
let tokens = vec![
Token::TagOpen("div".to_string()),
Token::TagOpen("section".to_string()),
Token::TagOpen("article".to_string()),
Token::Text("Deep content".to_string()),
Token::TagClose("article".to_string()),
Token::TagClose("section".to_string()),
];
let mut parser = Parser::new(tokens);
let result = parser.parse();
assert_eq!(result.is_err(), true);
}
#[test]
fn test_parse_complex_attributes() {
let tokens = vec![
Token::TagOpen("button".to_string()),
Token::AttributeName("class".to_string()),
Token::AttributeValue("btn primary".to_string()),
Token::AttributeName("disabled".to_string()),
Token::TagClose("button".to_string()),
];
let mut parser = Parser::new(tokens);
let result = parser.parse();
let expected = vec![Node::Element(ElementNode {
tag_name: "button".to_string(),
attributes: vec![
("class".to_string(), "btn primary".to_string()),
("disabled".to_string(), "true".to_string()),
],
children: vec![],
})];
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_parse_complex_html_document() {
let tokens = vec![
Token::TagOpen("html".to_string()),
Token::AttributeName("lang".to_string()),
Token::AttributeValue("en".to_string()),
Token::TagOpen("head".to_string()),
Token::SelfClosingTag("meta".to_string()),
Token::AttributeName("charset".to_string()),
Token::AttributeValue("UTF-8".to_string()),
Token::SelfClosingTag("meta".to_string()),
Token::AttributeName("name".to_string()),
Token::AttributeValue("viewport".to_string()),
Token::AttributeName("content".to_string()),
Token::AttributeValue("width=device-width, initial-scale=1.0".to_string()),
Token::TagOpen("title".to_string()),
Token::Text("Document".to_string()),
Token::TagClose("title".to_string()),
Token::TagClose("head".to_string()),
Token::TagOpen("body".to_string()),
Token::Text("ok ceci est un texte".to_string()),
Token::TagOpen("a".to_string()),
Token::AttributeName("href".to_string()),
Token::AttributeValue("2".to_string()),
Token::Text("link1".to_string()),
Token::TagClose("a".to_string()),
Token::TagOpen("a".to_string()),
Token::AttributeName("href".to_string()),
Token::AttributeValue("1".to_string()),
Token::Text("link2".to_string()),
Token::TagClose("a".to_string()),
Token::TagOpen("form".to_string()),
Token::AttributeName("action".to_string()),
Token::AttributeValue("d".to_string()),
Token::AttributeName("method".to_string()),
Token::AttributeValue("get".to_string()),
Token::SelfClosingTag("input".to_string()),
Token::AttributeName("type".to_string()),
Token::AttributeValue("text".to_string()),
Token::AttributeName("name".to_string()),
Token::AttributeValue("name".to_string()),
Token::TagClose("form".to_string()),
Token::TagClose("body".to_string()),
Token::TagClose("html".to_string()),
];
let mut parser = Parser::new(tokens);
let result = parser.parse();
let expected = vec![Node::Element(ElementNode {
tag_name: "html".to_string(),
attributes: vec![("lang".to_string(), "en".to_string())],
children: vec![
Node::Element(ElementNode {
tag_name: "head".to_string(),
attributes: vec![],
children: vec![
Node::Element(ElementNode {
tag_name: "meta".to_string(),
attributes: vec![("charset".to_string(), "UTF-8".to_string())],
children: vec![],
}),
Node::Element(ElementNode {
tag_name: "meta".to_string(),
attributes: vec![
("name".to_string(), "viewport".to_string()),
(
"content".to_string(),
"width=device-width, initial-scale=1.0".to_string(),
),
],
children: vec![],
}),
Node::Element(ElementNode {
tag_name: "title".to_string(),
attributes: vec![],
children: vec![Node::Text("Document".to_string())],
}),
],
}),
Node::Element(ElementNode {
tag_name: "body".to_string(),
attributes: vec![],
children: vec![
Node::Text("ok ceci est un texte".to_string()),
Node::Element(ElementNode {
tag_name: "a".to_string(),
attributes: vec![("href".to_string(), "2".to_string())],
children: vec![Node::Text("link1".to_string())],
}),
Node::Element(ElementNode {
tag_name: "a".to_string(),
attributes: vec![("href".to_string(), "1".to_string())],
children: vec![Node::Text("link2".to_string())],
}),
Node::Element(ElementNode {
tag_name: "form".to_string(),
attributes: vec![
("action".to_string(), "d".to_string()),
("method".to_string(), "get".to_string()),
],
children: vec![Node::Element(ElementNode {
tag_name: "input".to_string(),
attributes: vec![
("type".to_string(), "text".to_string()),
("name".to_string(), "name".to_string()),
],
children: vec![],
})],
}),
],
}),
],
})];
println!("{:#?}", result);
println!("");
println!("{:#?}", expected);
assert_eq!(result.is_ok(), true);
assert_eq!(result.unwrap(), expected);
}
}