use convert_case::{Case, Casing};
use std::{
borrow::{Borrow, Cow},
collections::{HashMap, HashSet},
fmt::Write,
path::Path,
};
use crate::component_file::ComponentFile;
use crate::error::{
AutoError, AutoErrorWithName, AutoErrorWithNameOffset, Error, Result, SpannedWithComponent,
};
use crate::lang::{Context, Value};
use crate::parser::{Node, ParseEvent, Tree, TreeRefId, EMPTY_HTML_ELEMENTS};
use crate::template_engine::{Escaping, TemplateBlock};
use crate::transformers::Transformers;
use crate::{
common::{Spanned, StreamParser},
TransformerElementContext,
};
pub fn is_custom_element(name: &str) -> bool {
name.starts_with(char::is_lowercase)
&& name.contains('-')
&& ![
"annotation-xml",
"color-profile",
"font-face",
"font-face-src",
"font-face-uri",
"font-face-format",
"font-face-name",
"missing-glyph",
]
.contains(&name)
}
#[derive(Debug)]
struct CompilationContext<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
name: &'a str,
context: &'b mut Context,
tree: &'c Tree<'d>,
node_ids: Vec<TreeRefId>,
slots: &'e HashMap<Cow<'f, str>, Vec<TreeRefId>>,
parent: Option<&'g CompilationContext<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h>>,
extra_attr_on_all_elements: Option<&'h str>,
raw: bool,
spaceless: bool,
verbatim: bool,
}
#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
fn eval_stmt<'a, 'b, 'c, 'd>(
stmt_expr: &str,
ctx: &mut CompilationContext<'_, '_, '_, 'a, '_, '_, '_, '_>,
component_resolver: &mut impl FnMut(&str) -> Result<ComponentFile<'c, 'd>>,
node_index: &mut usize,
result: &mut String,
components: Cow<HashMap<String, Tree<'a>>>,
file_offset: usize,
transformers: &mut Transformers,
unique_elements: &mut HashSet<(String, usize)>,
) -> Result<()> {
let mut parser = StreamParser::new(ctx.tree.file_path(), stmt_expr);
if parser.take_exact("if ").is_ok() {
parser.trim();
let is_let = parser.take_exact("let ").is_ok();
parser.trim();
let expr_index = parser.index();
let expr = parser.take_until_eoi().trim();
let mut if_branches = vec![(expr.to_string(), is_let, vec![])];
let mut else_branch = vec![];
let mut in_else = false;
let mut count_if = 0;
while *node_index < ctx.node_ids.len() {
let node_id = ctx.node_ids[*node_index];
let node = ctx.tree.get(node_id);
*node_index += 1;
match &node.data() {
Node::TemplateBlock(TemplateBlock::Stmt(s)) if s.starts_with("if ") => {
if in_else {
else_branch.push(node_id);
} else {
if_branches.last_mut().unwrap().2.push(node_id);
}
count_if += 1;
}
Node::TemplateBlock(TemplateBlock::Stmt(s)) if s.starts_with("elif ") => {
if count_if > 1 {
if in_else {
else_branch.push(node_id);
} else {
if_branches.last_mut().unwrap().2.push(node_id);
}
} else if in_else {
return Err(SpannedWithComponent::new(Error::ElifAfterElse)
.with_span(node.spanned_data().span().clone())
.with_file_path(ctx.tree.file_path())
.with_component_name(ctx.name));
} else {
let val = s.strip_prefix("elif ").unwrap().trim();
let (is_let, val) = if let Some(val) = val.strip_prefix("let ") {
(true, val.trim_start())
} else {
(false, val)
};
if_branches.push((val.to_string(), is_let, vec![]));
}
}
Node::TemplateBlock(TemplateBlock::Stmt(s)) if s == "endif" => {
if count_if == 0 {
break;
}
if in_else {
else_branch.push(node_id);
} else {
if_branches.last_mut().unwrap().2.push(node_id);
}
count_if -= 1;
}
Node::TemplateBlock(TemplateBlock::Stmt(s)) if s == "else" => {
if count_if == 0 {
in_else = true;
} else if in_else {
else_branch.push(node_id);
} else {
if_branches.last_mut().unwrap().2.push(node_id);
}
}
_ if in_else => else_branch.push(node_id),
_ => if_branches.last_mut().unwrap().2.push(node_id),
}
}
let mut if_matched = None;
for (i, branch) in if_branches.iter().enumerate() {
let cond_result = pochoir_lang::eval(
ctx.tree.file_path(),
&branch.0,
ctx.context,
file_offset + expr_index,
)
.auto_error()?;
if (branch.1 && cond_result != Value::Null) || cond_result == Value::Bool(true) {
if_matched = Some(i);
break;
}
}
if let Some(if_matched) = if_matched {
let old_node_ids = ctx.node_ids.clone();
ctx.node_ids = if_branches.remove(if_matched).2;
compile_recursive(
ctx,
component_resolver,
result,
components,
transformers,
unique_elements,
)?;
ctx.node_ids = old_node_ids;
} else {
let old_node_ids = ctx.node_ids.clone();
ctx.node_ids = else_branch;
compile_recursive(
ctx,
component_resolver,
result,
components,
transformers,
unique_elements,
)?;
ctx.node_ids = old_node_ids;
}
} else if parser.take_exact("for ").is_ok() {
parser.trim();
let mut aliases = vec![];
loop {
parser.trim();
let mut first = true;
let alias = parser
.take_while(|(_, ch)| {
if first {
first = false;
ch.is_alphabetic() || ch == '_'
} else {
ch.is_alphanumeric() || ch == '_'
}
})
.trim();
aliases.push(alias);
if parser.take_exact(",").is_err() {
break;
}
}
parser.trim();
parser
.take_exact("in ")
.auto_error_with_name_offset(ctx.name, file_offset)?;
parser.trim();
let expr_index = parser.index();
let expr = parser.take_until_eoi().trim();
let expr_end_index = parser.index();
let mut for_branch = vec![];
let mut count_for = 0;
while *node_index < ctx.node_ids.len() {
let node_id = ctx.node_ids[*node_index];
let node = ctx.tree.get(node_id);
*node_index += 1;
match &node.data() {
Node::TemplateBlock(TemplateBlock::Stmt(s)) if s.starts_with("for ") => {
for_branch.push(node_id);
count_for += 1;
}
Node::TemplateBlock(TemplateBlock::Stmt(s)) if s == "endfor" => {
if count_for == 0 {
break;
}
for_branch.push(node_id);
count_for -= 1;
}
_ => for_branch.push(node_id),
}
}
let old_values = aliases
.iter()
.map(|a| ctx.context.get(*a).cloned())
.collect::<Vec<Option<Value>>>();
let old_node_ids = ctx.node_ids.clone();
let interpreted_val = crate::lang::eval(
ctx.tree.file_path(),
expr,
ctx.context,
file_offset + expr_index,
)
.auto_error_with_name(ctx.name)?;
if let Value::Array(array) = interpreted_val {
for item in array {
if aliases.len() == 1 {
ctx.context.insert(aliases[0], item);
} else {
match item {
Value::Array(array) => {
for (i, alias) in aliases.iter().enumerate() {
ctx.context
.insert(*alias, array.get(i).cloned().unwrap_or(Value::Null));
}
}
Value::Object(object) => {
for alias in &aliases {
ctx.context.insert(
*alias,
object.get(*alias).cloned().unwrap_or(Value::Null),
);
}
}
_ => {
for alias in &aliases {
ctx.context.insert(*alias, Value::Null);
}
}
}
}
ctx.node_ids.clone_from(&for_branch);
compile_recursive(
ctx,
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
}
} else if let Value::Range(start, end) = interpreted_val {
let range = match (start, end) {
(Some(start), Some(end)) => start..end,
_ => {
return Err(SpannedWithComponent::new(Error::UnboundedRangeInForLoop)
.with_span(file_offset + expr_index..file_offset + expr_end_index)
.with_file_path(ctx.tree.file_path())
.with_component_name(ctx.name));
}
};
for item in range {
if aliases.len() == 1 {
ctx.context.insert(aliases[0], item);
} else {
for alias in &aliases {
ctx.context.insert(*alias, Value::Null);
}
}
ctx.node_ids.clone_from(&for_branch);
compile_recursive(
ctx,
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
}
}
ctx.node_ids = old_node_ids;
for (i, old_val) in old_values.into_iter().enumerate() {
if let Some(old_val) = old_val {
ctx.context.insert(aliases[i], old_val);
} else {
ctx.context.remove(aliases[i]);
}
}
} else if parser.take_exact("let ").is_ok() {
parser.trim();
let mut first = true;
let name = parser
.take_while(|(_, ch)| {
if first {
first = false;
ch.is_alphabetic() || ch == '_'
} else {
ch.is_alphanumeric() || ch == '_'
}
})
.trim();
parser.trim();
parser
.take_exact("=")
.auto_error_with_name_offset(ctx.name, file_offset)?;
parser.trim();
let expr_index = parser.index();
let expr = parser.take_until_eoi().trim();
let val_evaluated = crate::lang::eval(
ctx.tree.file_path(),
expr,
ctx.context,
file_offset + expr_index,
)
.auto_error_with_name(ctx.name)?;
ctx.context.insert((*name).to_string(), val_evaluated);
} else if parser.take_exact("spaceless").is_ok() && parser.is_eoi() {
let last_ch_not_whitespace_index = result
.rfind(|ch: char| !ch.is_whitespace())
.map_or(0, |i| i + 1);
result.truncate(last_ch_not_whitespace_index);
let mut spaceless_branch = vec![];
while *node_index < ctx.node_ids.len() {
let node_id = ctx.node_ids[*node_index];
let node = ctx.tree.get(node_id);
*node_index += 1;
match &node.data() {
Node::TemplateBlock(TemplateBlock::Stmt(s)) if s == "endspaceless" => break,
_ => spaceless_branch.push(node_id),
}
}
let old_spaceless = ctx.spaceless;
ctx.spaceless = true;
let old_node_ids = ctx.node_ids.clone();
ctx.node_ids = spaceless_branch;
compile_recursive(
ctx,
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
ctx.spaceless = old_spaceless;
let old_result_len = result.len();
while *node_index < old_node_ids.len() {
let node_id = old_node_ids[*node_index];
*node_index += 1;
ctx.node_ids = vec![node_id];
compile_recursive(
ctx,
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
if result.len() != old_result_len {
break;
}
}
let ch_not_whitespace_index = result[old_result_len..]
.find(|ch: char| !ch.is_whitespace())
.unwrap_or(result.len() - old_result_len);
result.drain(old_result_len..old_result_len + ch_not_whitespace_index);
ctx.node_ids = old_node_ids;
} else if parser.take_exact("verbatim").is_ok() && parser.is_eoi() {
let mut verbatim_branch = vec![];
while *node_index < ctx.node_ids.len() {
let node_id = ctx.node_ids[*node_index];
let node = ctx.tree.get(node_id);
*node_index += 1;
match &node.data() {
Node::TemplateBlock(TemplateBlock::Stmt(s)) if s == "endverbatim" => break,
_ => verbatim_branch.push(node_id),
}
}
let old_verbatim = ctx.verbatim;
ctx.verbatim = true;
let old_node_ids = ctx.node_ids.clone();
ctx.node_ids = verbatim_branch;
compile_recursive(
ctx,
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
ctx.verbatim = old_verbatim;
ctx.node_ids = old_node_ids;
} else {
return Err(SpannedWithComponent::new(Error::UnknownStatement {
stmt: stmt_expr[..stmt_expr
.find(char::is_whitespace)
.unwrap_or(stmt_expr.len())]
.to_string(),
})
.with_span(
ctx.tree
.get(ctx.node_ids[*node_index - 1])
.spanned_data()
.span()
.clone(),
)
.with_file_path(ctx.tree.file_path())
.with_component_name(ctx.name));
}
Ok(())
}
fn render_template_block<'a, 'b, 'c, 'd, 'e, T: Borrow<TemplateBlock<'e>>>(
ctx: &mut CompilationContext<'_, '_, '_, 'a, '_, '_, '_, '_>,
component_resolver: &mut impl FnMut(&str) -> Result<ComponentFile<'c, 'd>>,
node_index: &mut usize,
blocks: &[Spanned<T>],
result: &mut String,
components: &HashMap<String, Tree<'a>>,
transformers: &mut Transformers,
unique_elements: &mut HashSet<(String, usize)>,
) -> Result<()> {
for block in blocks {
match (**block).borrow() {
TemplateBlock::RawText(text) => {
let needs_trimming = if ctx.spaceless {
if *node_index == 1 {
true
} else {
matches!(
ctx.tree.get(ctx.node_ids[*node_index - 2]).data(),
Node::Element(_, _)
)
}
} else {
false
};
if needs_trimming {
result.push_str(text.trim_start());
} else {
result.push_str(text);
}
}
TemplateBlock::Expr(expr, escape) => {
if ctx.verbatim {
result.push_str("{{");
result.push_str(expr);
result.push_str("}}");
} else {
let expr_evaluated = crate::lang::eval(
ctx.tree.file_path(),
expr,
ctx.context,
block.span().start,
)
.auto_error_with_name(ctx.name)?
.to_string();
if *escape {
result.push_str(&Escaping::Html.escape(&expr_evaluated));
} else {
result.push_str(&expr_evaluated);
}
}
}
TemplateBlock::Stmt(stmt_expr) => {
if ctx.verbatim {
result.push_str("{%");
result.push_str(stmt_expr);
result.push_str("%}");
} else {
eval_stmt(
stmt_expr,
ctx,
component_resolver,
node_index,
result,
Cow::Borrowed(components),
block.span().start,
transformers,
unique_elements,
)?;
}
}
}
}
Ok(())
}
fn compile_template_block<'a, 'b, 'c, 'd>(
ctx: &mut CompilationContext<'_, '_, '_, 'a, '_, '_, '_, '_>,
component_resolver: &mut impl FnMut(&str) -> Result<ComponentFile<'c, 'd>>,
node_index: &mut usize,
blocks: &[Spanned<TemplateBlock>],
in_attr: bool,
components: &HashMap<String, Tree<'a>>,
transformers: &mut Transformers,
unique_elements: &mut HashSet<(String, usize)>,
) -> Result<Value> {
let mut evaluated_value = Value::String(String::new());
for (i, block) in blocks.iter().enumerate() {
let val = match &**block {
TemplateBlock::RawText(text) => Value::String(text.to_string()),
TemplateBlock::Expr(expr, _) => {
crate::lang::eval(ctx.tree.file_path(), expr, ctx.context, block.span().start)
.auto_error_with_name(ctx.name)?
}
TemplateBlock::Stmt(stmt_expr) => {
if in_attr {
let val: Cow<Value> = Cow::Owned(Value::String(
crate::template_engine::render_template(&blocks[i..], ctx.context)
.auto_error_with_name(ctx.name)?,
));
return Ok(Value::String(
evaluated_value.to_string() + &val.to_string(),
));
}
let mut value_string = String::new();
eval_stmt(
stmt_expr,
ctx,
component_resolver,
node_index,
&mut value_string,
Cow::Borrowed(components),
block.span().start,
transformers,
unique_elements,
)?;
Value::String(value_string)
}
};
if val == Value::Null {
} else if matches!(&evaluated_value, Value::String(ref s) if s.is_empty()) {
evaluated_value = val;
} else {
evaluated_value = Value::String(evaluated_value.to_string() + &val.to_string());
}
}
Ok(evaluated_value)
}
fn parse_and_tranform<'a>(
file_path: &Path,
component_name: &str,
data: &'a str,
context: &mut Context,
transformers: &mut Transformers,
) -> Result<Tree<'a>> {
let mut builder = pochoir_parser::Builder::new();
if !transformers.inner.is_empty() {
builder = builder.on_event(|event, tree, id| match event {
ParseEvent::BeforeElement => transformers.inner.iter_mut().try_for_each(|t| {
t.on_before_element(&mut TransformerElementContext {
tree,
context,
element_id: id,
})
}),
ParseEvent::AfterElement => transformers.inner.iter_mut().try_for_each(|t| {
t.on_after_element(&mut TransformerElementContext {
tree,
context,
element_id: id,
})
}),
});
}
let mut tree = builder
.parse(file_path, data)
.auto_error_with_name(component_name)?;
if !transformers.inner.is_empty() {
transformers
.inner
.iter_mut()
.try_for_each(|t| {
t.on_tree_parsed(&mut crate::TransformerTreeContext {
tree: &mut tree,
context,
component_name,
file_path,
})
})
.map_err(|e| {
SpannedWithComponent::new(Error::ParserError(
crate::parser::Error::EventHandlerError(e.to_string()),
))
.with_file_path(file_path)
.with_component_name(component_name)
})?;
}
Ok(tree)
}
pub fn compile<'a, 'b, 'c>(
default_component_name: &str,
default_context: &mut Context,
component_resolver: impl FnMut(&str) -> Result<ComponentFile<'b, 'c>>,
) -> Result<String> {
transform_and_compile(
default_component_name,
default_context,
component_resolver,
&mut Transformers::new(),
)
}
pub fn transform_and_compile<'a, 'b, 'c>(
default_component_name: &str,
default_context: &mut Context,
mut component_resolver: impl FnMut(&str) -> Result<ComponentFile<'b, 'c>>,
transformers: &mut Transformers,
) -> Result<String> {
component_resolver(default_component_name).and_then(|file| {
let mut result = String::new();
let tree = parse_and_tranform(
&file.path,
default_component_name,
&file.data,
default_context,
transformers,
)?;
compile_recursive(
&mut CompilationContext {
name: default_component_name,
context: default_context,
tree: &tree,
node_ids: tree.root_nodes(),
slots: &HashMap::new(),
parent: None,
extra_attr_on_all_elements: None,
raw: false,
spaceless: false,
verbatim: false,
},
&mut component_resolver,
&mut result,
Cow::Borrowed(&HashMap::new()),
transformers,
&mut HashSet::new(),
)?;
Ok(result)
})
}
#[allow(clippy::too_many_lines)]
fn compile_recursive<'a, 'b, 'c, 'd>(
ctx: &mut CompilationContext<'_, '_, '_, 'a, '_, '_, '_, '_>,
component_resolver: &mut impl FnMut(&str) -> Result<ComponentFile<'c, 'd>>,
result: &mut String,
mut components: Cow<HashMap<String, Tree<'a>>>,
transformers: &mut Transformers,
unique_elements: &mut HashSet<(String, usize)>,
) -> Result<()> {
let mut node_index = 0;
while node_index < ctx.node_ids.len() {
let node = ctx.tree.get(ctx.node_ids[node_index]);
node_index += 1;
match node.data() {
Node::Element(el_name, attrs) => {
if el_name == "slot" && !ctx.raw {
let slot_name_blocks = node
.attr_spanned("name")
.expect("attr_spanned is called on an element")
.unwrap_or(vec![Spanned::new(TemplateBlock::text("default"))]);
let mut slot_name = String::new();
render_template_block(
ctx,
component_resolver,
&mut node_index,
&slot_name_blocks,
&mut slot_name,
&components,
transformers,
unique_elements,
)?;
if let Some(node_ids) = ctx.slots.get(&*slot_name) {
let mut context = ctx
.parent
.expect("slot element should be used in a component")
.context
.clone();
for (key, val) in ctx
.context
.iter()
.filter(|(k, _)| ctx.context.is_inherited(&**k).unwrap())
{
context.insert(key, val.clone());
context.make_inherited(key);
}
for (key, val_blocks) in attrs {
if (**key == "slot" || **key == "'__p_attr'" || **key == "pochoir-once")
&& !ctx.raw
{
continue;
}
let evaluated_value = compile_template_block(
ctx,
component_resolver,
&mut node_index,
val_blocks,
true,
&components,
transformers,
unique_elements,
)?;
context.insert(&***key, evaluated_value);
}
let mut rendered = String::new();
compile_recursive(
&mut CompilationContext {
name: ctx
.parent
.expect("slot element should be used in a component")
.name,
context: &mut context,
tree: ctx
.parent
.expect("slot element should be used in a component")
.tree,
node_ids: node_ids.clone(),
parent: ctx
.parent
.expect("slot element should be used in a component")
.parent,
slots: ctx
.parent
.expect("slot element should be used in a component")
.slots,
extra_attr_on_all_elements: attrs
.get("'__p_attr'")
.and_then(|v| v.first())
.and_then(|v| {
if let TemplateBlock::RawText(attr_val) = &**v {
Some(&**attr_val)
} else {
None
}
}),
raw: ctx.raw,
spaceless: ctx.spaceless,
verbatim: ctx.verbatim,
},
component_resolver,
&mut rendered,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
if rendered.is_empty() {
compile_recursive(
&mut CompilationContext {
name: ctx.name,
context: ctx.context,
tree: ctx.tree,
node_ids: node.children_id(),
parent: ctx.parent,
slots: &HashMap::new(),
extra_attr_on_all_elements: ctx.extra_attr_on_all_elements,
raw: ctx.raw,
spaceless: ctx.spaceless,
verbatim: ctx.verbatim,
},
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
} else {
result.push_str(&rendered);
}
} else {
compile_recursive(
&mut CompilationContext {
name: ctx.name,
context: ctx.context,
tree: ctx.tree,
node_ids: node.children_id(),
parent: ctx.parent,
slots: &HashMap::new(),
extra_attr_on_all_elements: ctx.extra_attr_on_all_elements,
raw: ctx.raw,
spaceless: ctx.spaceless,
verbatim: ctx.verbatim,
},
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
}
} else if el_name == "template"
&& node
.attr("shadowrootmode")
.expect("attr is called on an element")
.is_some()
&& !ctx.raw
{
compile_recursive(
&mut CompilationContext {
name: el_name,
context: ctx.context,
tree: ctx.tree,
node_ids: vec![node.id()],
slots: &HashMap::new(),
parent: ctx.parent,
extra_attr_on_all_elements: ctx.extra_attr_on_all_elements,
raw: true,
spaceless: ctx.spaceless,
verbatim: ctx.verbatim,
},
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
&mut HashSet::new(),
)?;
} else if el_name == "template"
&& node
.attr("pochoir-hybrid")
.expect("attr is called on an element")
.is_some()
{
let name_blocks = node
.attr_spanned("name")
.expect("attr_spanned is called on an element")
.unwrap_or(vec![Spanned::new(TemplateBlock::text("default"))]);
let mut name = String::new();
render_template_block(
ctx,
component_resolver,
&mut node_index,
&name_blocks,
&mut name,
&components,
transformers,
unique_elements,
)?;
components.to_mut().insert(name, node.sub_tree());
let mut template_tree = Tree::new(ctx.tree.file_path());
template_tree.insert(TreeRefId::Root, node.spanned_data().clone());
template_tree
.get_mut(TreeRefId::Node(0))
.remove_attr("pochoir-hybrid");
template_tree
.get_mut(TreeRefId::Node(0))
.append_children(&node.sub_tree());
let rendered = pochoir_parser::render(&template_tree);
write!(result, "{rendered}").expect("writing to a String can't fail");
} else if el_name == "template" && !ctx.raw {
let name_blocks = node
.attr_spanned("name")
.expect("attr_spanned is called on an element")
.unwrap_or(vec![Spanned::new(TemplateBlock::text("default"))]);
let mut name = String::new();
render_template_block(
ctx,
component_resolver,
&mut node_index,
&name_blocks,
&mut name,
&components,
transformers,
unique_elements,
)?;
components.to_mut().insert(name, node.sub_tree());
} else if is_custom_element(el_name) {
if el_name == ctx.name {
let spanned_data = node.spanned_data();
return Err(SpannedWithComponent::new(Error::CyclicComponent {
name: ctx.name.to_string(),
})
.with_span(spanned_data.span().clone())
.with_file_path(spanned_data.file_path())
.with_component_name(ctx.name));
}
let mut context = Context::new();
for (key, val_blocks) in attrs {
if (**key == "slot" || **key == "'__p_attr'" || **key == "pochoir-once")
&& !ctx.raw
{
continue;
}
let evaluated_value = compile_template_block(
ctx,
component_resolver,
&mut node_index,
val_blocks,
true,
&components,
transformers,
unique_elements,
)?;
context.insert(key.to_case(Case::Snake), evaluated_value);
}
for (key, val) in ctx
.context
.iter()
.filter(|(k, _)| ctx.context.is_inherited(&**k).unwrap())
{
context.insert(key, val.clone());
context.make_inherited(key);
}
let mut resolved_component_file = None;
let component_result = components
.get(&**el_name)
.ok_or(())
.map(Cow::Borrowed)
.or_else(|()| {
let file = component_resolver(el_name)?;
resolved_component_file = Some(file);
parse_and_tranform(
&resolved_component_file.as_ref().unwrap().path,
el_name,
&resolved_component_file.as_ref().unwrap().data,
&mut context,
transformers,
)
.map(Cow::Owned)
});
match component_result {
Ok(tree) => {
let mut slots: HashMap<Cow<str>, Vec<TreeRefId>> = HashMap::new();
for child_id in node.children_id() {
let child = ctx.tree.get(child_id);
let slot_name_blocks = child
.attr_spanned("slot")
.unwrap_or_default()
.unwrap_or(vec![Spanned::new(TemplateBlock::text("default"))]);
let mut slot_name = String::new();
render_template_block(
ctx,
component_resolver,
&mut node_index,
&slot_name_blocks,
&mut slot_name,
&components,
transformers,
unique_elements,
)?;
if let Some(children) = slots.get_mut(&*slot_name) {
children.push(child_id);
} else {
slots.insert(Cow::Owned(slot_name), vec![child_id]);
}
}
let contains_shadowrootmode =
tree.select("template[shadowrootmode]").unwrap().is_some();
if contains_shadowrootmode {
write!(result, "<{el_name}")
.expect("writing to a String can't fail");
for (key, val_blocks) in attrs {
if (**key == "slot"
|| **key == "'__p_attr'"
|| **key == "pochoir-once")
&& !ctx.raw
{
continue;
}
let attr_val = crate::template_engine::render_template(
val_blocks,
ctx.context,
)
.auto_error_with_name(ctx.name)?;
if attr_val.is_empty() {
write!(result, " {}", **key)
.expect("writing to a String can't fail");
} else {
write!(result, " {}=\"{}\"", **key, attr_val)
.expect("writing to a String can't fail");
}
}
write!(result, ">").expect("writing to a String can't fail");
}
compile_recursive(
&mut CompilationContext {
name: el_name,
context: &mut context,
tree: &tree,
node_ids: tree.root_nodes(),
slots: &slots,
parent: Some(ctx),
extra_attr_on_all_elements: ctx.extra_attr_on_all_elements,
raw: ctx.raw,
spaceless: ctx.spaceless,
verbatim: ctx.verbatim,
},
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
if contains_shadowrootmode {
compile_recursive(
&mut CompilationContext {
name: el_name,
context: ctx.context,
tree: ctx.tree,
node_ids: node.children_id(),
slots: &slots,
parent: ctx.parent,
extra_attr_on_all_elements: ctx.extra_attr_on_all_elements,
raw: true,
spaceless: ctx.spaceless,
verbatim: ctx.verbatim,
},
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
write!(result, "</{el_name}>")
.expect("writing to a String can't fail");
}
}
Err(e) if matches!(**e, Error::ComponentNotFound { .. }) => {
if let Error::ComponentNotFound { name } = (**e).clone() {
let spanned_data = node.spanned_data();
return Err(SpannedWithComponent::new(Error::ComponentNotFound {
name,
})
.with_span(spanned_data.span().clone())
.with_file_path(spanned_data.file_path())
.with_component_name(ctx.name));
}
unreachable!();
}
Err(e) => {
return Err(e);
}
}
} else {
if unique_elements.contains(&(ctx.name.to_string(), node_index)) {
continue;
}
write!(result, "<{el_name}").expect("writing to a String can't fail");
for (key, val_blocks) in attrs.iter().chain(
ctx.extra_attr_on_all_elements
.map(|key| (Spanned::new(Cow::Borrowed(key)), Spanned::new(vec![])))
.as_ref(),
) {
if (**key == "slot" || **key == "'__p_attr'" || **key == "pochoir-once")
&& !ctx.raw
{
continue;
}
let evaluated_value = compile_template_block(
ctx,
component_resolver,
&mut node_index,
val_blocks,
true,
&components,
transformers,
unique_elements,
)?;
if evaluated_value == Value::Null
|| evaluated_value == Value::String(String::new())
|| evaluated_value == Value::Bool(true)
{
write!(result, " {}", **key).expect("writing to a String can't fail");
} else if evaluated_value == Value::Bool(false) {
} else {
write!(result, " {}=\"{}\"", **key, evaluated_value)
.expect("writing to a String can't fail");
}
}
write!(result, ">").expect("writing to a String can't fail");
if attrs.get("pochoir-once").is_some() {
unique_elements.insert((ctx.name.to_string(), node_index));
}
compile_recursive(
&mut CompilationContext {
name: ctx.name,
context: ctx.context,
tree: ctx.tree,
node_ids: node.children_id(),
slots: ctx.slots,
parent: ctx.parent,
extra_attr_on_all_elements: ctx.extra_attr_on_all_elements,
raw: ctx.raw,
spaceless: ctx.spaceless,
verbatim: ctx.verbatim,
},
component_resolver,
result,
Cow::Borrowed(&*components),
transformers,
unique_elements,
)?;
if !EMPTY_HTML_ELEMENTS.contains(&&**el_name) {
write!(result, "</{el_name}>").expect("writing to a String can't fail");
}
}
}
Node::Comment(_) => (),
Node::Doctype(doctype) => {
write!(result, "<!DOCTYPE {doctype}>").expect("writing to a String can't fail");
}
Node::TemplateBlock(block) => {
render_template_block(
ctx,
component_resolver,
&mut node_index,
&[Spanned::new(block)
.with_span(node.spanned_data().span().clone())
.with_file_path(ctx.tree.file_path())],
result,
&components,
transformers,
unique_elements,
)?;
}
Node::Root => unreachable!(),
}
}
Ok(())
}