use nom::Input;
use nom_locate::LocatedSpan;
pub type Span<'a> = LocatedSpan<&'a str>;
use self::{
attribute::node::AttributeNode, class::node::ClassNode, comment::node::CommentNode,
doctype::node::DoctypeNode, id::node::IdNode, tag::node::TagNode, text::node::TextNode,
};
pub mod attribute;
pub mod class;
pub mod comment;
pub mod doctype;
pub mod error;
pub mod id;
pub mod parse;
pub mod tag;
pub mod text;
pub type HsmlResult<'a, T> = nom::IResult<Span<'a>, T, error::HsmlError<'a>>;
use serde::Serialize;
#[derive(Debug, PartialEq, Serialize)]
pub struct RootNode {
pub nodes: Vec<HsmlNode>,
}
#[derive(Debug, PartialEq, Serialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum HsmlNode {
Root(RootNode),
Tag(TagNode),
Comment(CommentNode),
Doctype(DoctypeNode),
Id(IdNode),
Class(ClassNode),
Attribute(AttributeNode),
Text(TextNode),
}
#[derive(Debug, Default)]
pub struct HsmlProcessContext {
pub nested_tag_level: usize,
pub indent_string: String,
}
pub fn advance<'a>(span: Span<'a>, n: usize) -> Span<'a> {
span.take_split(n).0
}
pub fn take_prefix<'a>(span: Span<'a>, n: usize) -> Span<'a> {
span.take_split(n).1
}
pub fn delimited_section_len(s: &str, open: char, close: char) -> Option<usize> {
if !s.starts_with(open) {
return None;
}
let mut chars = s.char_indices();
chars.next();
let mut is_escaped = false;
while let Some((index, c)) = chars.next() {
if c == '\\' {
is_escaped = !is_escaped;
continue;
}
if !is_escaped && (c == '"' || c == '\'') && c != close {
let quote = c;
let mut quote_escaped = false;
for (_, qc) in chars.by_ref() {
if qc == '\\' {
quote_escaped = !quote_escaped;
continue;
}
if qc == quote && !quote_escaped {
break;
}
quote_escaped = false;
}
is_escaped = false;
continue;
}
if c == close && !is_escaped {
return Some(index + c.len_utf8());
}
is_escaped = false;
}
None
}
pub fn quoted_string_len(s: &str) -> Option<usize> {
let quote = s.chars().next()?;
if quote != '"' && quote != '\'' {
return None;
}
delimited_section_len(s, quote, quote)
}