use crate::tag::TagKind;
use crate::Tag;
use proc_macro2::Span;
use quote::{quote, quote_spanned};
use std::collections::HashMap;
use syn::spanned::Spanned;
use syn::{Ident, Stmt};
mod braced;
mod close_tag;
mod open_tag;
mod statement;
mod text;
pub enum NodesToPush<'a> {
Stmt(&'a Stmt),
TokenStream(&'a Stmt, proc_macro2::TokenStream),
}
pub struct HtmlParser {
tokens: Vec<proc_macro2::TokenStream>,
current_node_idx: usize,
node_order: Vec<usize>,
parent_stack: Vec<(usize, Ident)>,
parent_to_children: HashMap<usize, Vec<usize>>,
recent_span_locations: RecentSpanLocations,
last_tag_kind: Option<TagKind>,
}
impl HtmlParser {
pub fn new() -> HtmlParser {
let mut parent_to_children: HashMap<usize, Vec<usize>> = HashMap::new();
parent_to_children.insert(0, vec![]);
HtmlParser {
tokens: vec![],
current_node_idx: 0,
node_order: vec![],
parent_stack: vec![],
parent_to_children,
recent_span_locations: RecentSpanLocations::default(),
last_tag_kind: None,
}
}
pub fn push_tag(&mut self, tag: &Tag, next_tag: Option<&Tag>) {
match tag {
Tag::Open {
name,
attrs,
closing_bracket_span,
is_self_closing,
..
} => {
self.parse_open_tag(name, closing_bracket_span, attrs, *is_self_closing);
self.last_tag_kind = Some(TagKind::Open);
}
Tag::Close { name, .. } => {
self.parse_close_tag(name);
self.last_tag_kind = Some(TagKind::Close);
}
Tag::Text {
text,
start_span,
end_span,
} => {
self.parse_text(text, start_span.unwrap(), end_span.unwrap(), next_tag);
self.last_tag_kind = Some(TagKind::Text);
}
Tag::Braced { block, brace_span } => {
self.parse_braced(block, brace_span, next_tag);
self.last_tag_kind = Some(TagKind::Braced);
}
};
}
pub fn finish(&mut self) -> proc_macro2::TokenStream {
let node_order = &mut self.node_order;
let parent_to_children = &mut self.parent_to_children;
let tokens = &mut self.tokens;
if node_order.len() > 1 {
for _ in 0..(node_order.len()) {
let parent_idx = node_order.pop().unwrap();
let parent_name =
Ident::new(format!("node_{}", parent_idx).as_str(), Span::call_site());
let parent_to_children_indices = match parent_to_children.get(&parent_idx) {
Some(children) => children,
None => continue,
};
if parent_to_children_indices.len() > 0 {
for child_idx in parent_to_children_indices.iter() {
let children =
Ident::new(format!("node_{}", child_idx).as_str(), Span::call_site());
let unreachable = quote_spanned!(Span::call_site() => {
unreachable!("Non-elements cannot have children");
});
let push_children = quote! {
if let Some(ref mut element_node) = #parent_name.as_velement_mut() {
element_node.children.extend(#children.into_iter());
} else {
#unreachable;
}
};
tokens.push(push_children);
}
}
}
}
let node = quote! {
{
#(#tokens)*
node_0
}
};
node
}
fn push_tokens(&mut self, tokens: proc_macro2::TokenStream) {
self.tokens.push(tokens);
}
fn set_most_recent_open_tag_end(&mut self, span: Span) {
self.recent_span_locations.most_recent_open_tag_end = Some(span);
}
fn set_most_recent_block_start(&mut self, span: Span) {
self.recent_span_locations.most_recent_block_start = Some(span);
}
fn separated_by_whitespace(&self, first_span: &Span, second_span: &Span) -> bool {
if first_span.end().line != second_span.end().line {
return true;
}
second_span.start().column - first_span.end().column > 0
}
fn new_virtual_node_ident(&mut self, span: Span) -> Ident {
let node_name = format!("node_{}", self.current_node_idx);
let node_ident = Ident::new(node_name.as_str(), span);
self.current_node_idx += 1;
node_ident
}
fn current_virtual_node_ident(&self, span: Span) -> Ident {
let node_name = format!("node_{}", self.current_node_idx - 1);
Ident::new(node_name.as_str(), span)
}
fn push_iterable_nodes(&mut self, nodes: NodesToPush) {
let node_idx = self.current_node_idx;
match nodes {
NodesToPush::Stmt(stmt) => {
let node_ident = self.new_virtual_node_ident(stmt.span());
self.push_tokens(quote! {
let mut #node_ident: IterableNodes = (#stmt).into();
});
}
NodesToPush::TokenStream(stmt, tokens) => {
let node_ident = self.new_virtual_node_ident(stmt.span());
self.push_tokens(quote! {
let mut #node_ident: IterableNodes = #tokens.into();
});
}
}
let parent_idx = *&self.parent_stack[self.parent_stack.len() - 1].0;
self.parent_to_children
.get_mut(&parent_idx)
.expect("Parent of these iterable nodes")
.push(node_idx);
self.node_order.push(node_idx);
}
}
#[derive(Default)]
struct RecentSpanLocations {
most_recent_open_tag_end: Option<Span>,
most_recent_block_start: Option<Span>,
}
fn is_self_closing(tag: &str) -> bool {
html_validation::is_self_closing(tag)
}
fn is_valid_tag(tag: &str) -> bool {
html_validation::is_valid_tag(tag)
}