mod tag;
use core::str::Chars;
use crate::Html;
use crate::types::html_builder::HtmlBuilder;
use crate::types::tag::TagBuilder;
const AUTO_CLOSING_TAGS: [&str; 2] = ["meta", "br"];
impl Html {
pub fn parse(html: &str) -> Result<Self, String> {
let mut tree = HtmlBuilder::default();
tree.parse(&mut html.chars()).map(|()| tree.into_html())
}
}
impl HtmlBuilder {
fn parse(&mut self, chars: &mut Chars<'_>) -> Result<(), String> {
let mut dash_count: u32 = 0;
let mut style = false;
let mut script = false;
let mut comment = false;
while let Some(ch) = chars.next() {
if !comment && (style || script) {
if ch == '<'
&& let Ok(TagBuilder::Close(name)) = TagBuilder::parse(chars)
{
if style && name == "style" {
style = false;
self.close_tag(&name)?;
continue;
}
if script && name == "script" {
script = false;
self.close_tag(&name)?;
continue;
}
}
self.push_char(ch);
} else if ch == '-' {
#[expect(clippy::arithmetic_side_effects, reason = "checked")]
if dash_count == 2 {
self.push_char('-');
} else {
dash_count += 1;
}
} else if ch == '>' && dash_count == 2 {
if !self.close_comment() {
return Err("Tried to close unopened comment.".to_owned());
}
comment = false;
dash_count = 0;
} else {
for _ in 0..dash_count {
self.push_char('-');
}
dash_count = 0;
if comment {
self.push_char(ch);
} else if ch == '<' {
match TagBuilder::parse(chars)? {
TagBuilder::Doctype { name, attr } =>
self.push_node(Self::Doctype { name, attr }),
TagBuilder::Open(tag) => {
if tag.as_name() == "style" {
style = true;
} else if tag.as_name() == "script" {
script = true;
}
self.push_tag(tag, false);
}
TagBuilder::OpenClose(tag) => self.push_tag(tag, true),
TagBuilder::Close(name) => self.close_tag(&name)?,
TagBuilder::OpenComment => {
self.push_comment();
comment = true;
}
}
} else {
self.push_char(ch);
}
}
}
Ok(())
}
}