use std::collections::HashSet;
use super::boolean::insert_boolean_directives;
use super::combo::AxisGroup;
use super::container::{hoist_list_container, is_list_container, unwrap_container_tree};
use super::directives::{comment_each, comment_else, comment_endeach, comment_endif, comment_if};
use super::dom::{DomNode, parse_html, serialize};
use super::tree_diff::{DiffOp, diff_children};
use super::variant::{find_pair_for_axis, find_scoped_variant_indices};
use super::{
Axis, content_indices, extract_template_inner, navigate_to_children, nth_content_index,
rename_slot_markers,
};
pub(super) fn process_array(
result: Vec<DomNode>,
axes: &[Axis],
variants: &[String],
axis_idx: usize,
) -> Vec<DomNode> {
let axis = &axes[axis_idx];
let pair = find_pair_for_axis(axes, variants.len(), axis_idx);
let Some((vi_pop, vi_empty)) = pair else {
return result;
};
let tree_pop = parse_html(&variants[vi_pop]);
let tree_empty = parse_html(&variants[vi_empty]);
insert_array_directives(result, &tree_pop, &tree_empty, &axis.path)
}
fn insert_array_directives(
tree: Vec<DomNode>,
pop_nodes: &[DomNode],
empty_nodes: &[DomNode],
path: &str,
) -> Vec<DomNode> {
let ops = diff_children(pop_nodes, empty_nodes);
let mut body_indices: Vec<usize> = Vec::new();
let mut has_only_right = false;
let mut has_modified = false;
for op in &ops {
match op {
DiffOp::OnlyLeft(ai) => body_indices.push(*ai),
DiffOp::OnlyRight(_) => has_only_right = true,
DiffOp::Modified(_, _) => has_modified = true,
DiffOp::Identical(_, _) => {}
}
}
if body_indices.is_empty() && has_modified {
return insert_array_modified(tree, pop_nodes, empty_nodes, path);
}
if body_indices.is_empty() && has_only_right {
return insert_boolean_directives(&tree, pop_nodes, empty_nodes, path);
}
if body_indices.is_empty() {
return tree;
}
let mut body: Vec<DomNode> = body_indices.iter().map(|&i| pop_nodes[i].clone()).collect();
rename_slot_markers(&mut body, path);
let each_nodes = wrap_array_body(&body, path);
let final_nodes = if has_only_right {
let fallback: Vec<DomNode> = ops
.iter()
.filter_map(|op| match op {
DiffOp::OnlyRight(bi) => Some(empty_nodes[*bi].clone()),
_ => None,
})
.collect();
let mut nodes = vec![comment_if(path)];
nodes.extend(each_nodes);
nodes.push(comment_else());
nodes.extend(fallback);
nodes.push(comment_endif(path));
nodes
} else {
each_nodes
};
let content_map = content_indices(&tree);
let mut result = Vec::new();
let mut tree_content_idx = 0usize;
let mut tree_pos = 0usize;
for op in &ops {
let target =
if tree_content_idx < content_map.len() { content_map[tree_content_idx] } else { tree.len() };
while tree_pos < target {
result.push(tree[tree_pos].clone());
tree_pos += 1;
}
match op {
DiffOp::Identical(_, _) => {
result.push(tree[tree_pos].clone());
tree_pos += 1;
tree_content_idx += 1;
}
DiffOp::OnlyLeft(ai) => {
if *ai == body_indices[0] {
result.extend(final_nodes.iter().cloned());
}
tree_pos += 1;
tree_content_idx += 1;
}
DiffOp::OnlyRight(_) => {
}
DiffOp::Modified(_, _) => {
result.push(tree[tree_pos].clone());
tree_pos += 1;
tree_content_idx += 1;
}
}
}
while tree_pos < tree.len() {
result.push(tree[tree_pos].clone());
tree_pos += 1;
}
result
}
fn insert_array_modified(
mut tree: Vec<DomNode>,
pop_nodes: &[DomNode],
empty_nodes: &[DomNode],
path: &str,
) -> Vec<DomNode> {
let ops = diff_children(pop_nodes, empty_nodes);
for op in ops {
if let DiffOp::Modified(ai, bi) = op
&& let (DomNode::Element { children: pc, .. }, DomNode::Element { children: ec, .. }) =
(&pop_nodes[ai], &empty_nodes[bi])
{
if let Some(ti) = nth_content_index(&tree, ai)
&& let DomNode::Element { children: tc, .. } = &mut tree[ti]
{
*tc = insert_array_directives(std::mem::take(tc), pc, ec, path);
}
}
}
tree
}
fn wrap_array_body(body: &[DomNode], path: &str) -> Vec<DomNode> {
if body.len() == 1
&& let Some(wrapped) = wrap_single_body_node(&body[0], path)
{
return vec![wrapped];
}
if let Some((tag, attrs, inner)) = unwrap_container_tree(body) {
let mut inner_with_directives = vec![comment_each(path)];
inner_with_directives.extend(inner.iter().cloned());
inner_with_directives.push(comment_endeach());
return vec![DomNode::Element {
tag: tag.to_string(),
attrs: attrs.to_string(),
children: inner_with_directives,
self_closing: false,
}];
}
if let Some((tag, attrs, inner)) = hoist_list_container(body) {
let mut inner_with_directives = vec![comment_each(path)];
inner_with_directives.extend(inner);
inner_with_directives.push(comment_endeach());
return vec![DomNode::Element {
tag: tag.clone(),
attrs: attrs.clone(),
children: inner_with_directives,
self_closing: false,
}];
}
let mut nodes = vec![comment_each(path)];
nodes.extend(body.iter().cloned());
nodes.push(comment_endeach());
nodes
}
fn wrap_single_body_node(node: &DomNode, path: &str) -> Option<DomNode> {
match node {
DomNode::Element { tag, attrs, children, self_closing: false } => {
if tag == "table" {
return wrap_table_body(tag, attrs, children, path);
}
if is_list_container(tag) {
let mut inner_with_directives = vec![comment_each(path)];
inner_with_directives.extend(children.iter().cloned());
inner_with_directives.push(comment_endeach());
return Some(DomNode::Element {
tag: tag.clone(),
attrs: attrs.clone(),
children: inner_with_directives,
self_closing: false,
});
}
let mut target_idx: Option<usize> = None;
let mut wrapped_child: Option<DomNode> = None;
for (idx, child) in children.iter().enumerate() {
if let Some(candidate) = wrap_single_body_node(child, path) {
if target_idx.is_some() {
return None;
}
target_idx = Some(idx);
wrapped_child = Some(candidate);
}
}
if let (Some(idx), Some(child)) = (target_idx, wrapped_child) {
let mut new_children = children.clone();
new_children[idx] = child;
return Some(DomNode::Element {
tag: tag.clone(),
attrs: attrs.clone(),
children: new_children,
self_closing: false,
});
}
None
}
_ => None,
}
}
fn wrap_table_body(tag: &str, attrs: &str, children: &[DomNode], path: &str) -> Option<DomNode> {
let tbody_indices: Vec<usize> = children
.iter()
.enumerate()
.filter_map(|(idx, child)| match child {
DomNode::Element { tag, self_closing: false, .. } if tag == "tbody" => Some(idx),
_ => None,
})
.collect();
if tbody_indices.len() != 1 {
return None;
}
let tbody_idx = tbody_indices[0];
let mut new_children = children.to_vec();
if let DomNode::Element {
tag: tbody_tag,
attrs: tbody_attrs,
children: tbody_children,
self_closing: false,
} = &children[tbody_idx]
{
let mut inner_with_directives = vec![comment_each(path)];
inner_with_directives.extend(tbody_children.iter().cloned());
inner_with_directives.push(comment_endeach());
new_children[tbody_idx] = DomNode::Element {
tag: tbody_tag.clone(),
attrs: tbody_attrs.clone(),
children: inner_with_directives,
self_closing: false,
};
return Some(DomNode::Element {
tag: tag.to_string(),
attrs: attrs.to_string(),
children: new_children,
self_closing: false,
});
}
None
}
struct BodyLocation {
path: Vec<usize>,
body_indices: Vec<usize>,
fallback_indices: Vec<usize>,
}
fn find_body_in_trees(pop: &[DomNode], empty: &[DomNode]) -> Option<BodyLocation> {
let ops = diff_children(pop, empty);
let body_idx: Vec<usize> = ops
.iter()
.filter_map(|op| if let DiffOp::OnlyLeft(ai) = op { Some(*ai) } else { None })
.collect();
if !body_idx.is_empty() {
let fallback_idx: Vec<usize> = ops
.iter()
.filter_map(|op| if let DiffOp::OnlyRight(bi) = op { Some(*bi) } else { None })
.collect();
return Some(BodyLocation {
path: vec![],
body_indices: body_idx,
fallback_indices: fallback_idx,
});
}
for op in &ops {
if let DiffOp::Modified(ai, bi) = op
&& let (DomNode::Element { children: pc, .. }, DomNode::Element { children: ec, .. }) =
(&pop[*ai], &empty[*bi])
&& let Some(mut loc) = find_body_in_trees(pc, ec)
{
loc.path.insert(0, *ai);
return Some(loc);
}
}
None
}
fn replace_body_at_path(
result: &mut Vec<DomNode>,
path: &[usize],
body_indices: &[usize],
replacement: Vec<DomNode>,
) {
if path.is_empty() {
let body_set: HashSet<usize> = body_indices.iter().copied().collect();
let mut new = Vec::new();
for (i, node) in result.iter().enumerate() {
if body_set.contains(&i) {
if i == body_indices[0] {
new.extend(replacement.iter().cloned());
}
} else {
new.push(node.clone());
}
}
*result = new;
} else {
if let Some(ci) = nth_content_index(result, path[0])
&& let DomNode::Element { children, .. } = &mut result[ci]
{
replace_body_at_path(children, &path[1..], body_indices, replacement);
}
}
}
pub(super) fn process_array_with_children(
mut result: Vec<DomNode>,
axes: &[Axis],
variants: &[String],
group: &AxisGroup,
) -> Vec<DomNode> {
let array_axis = &axes[group.parent_axis_idx];
if array_axis.kind != "array" {
return result;
}
let pair = find_pair_for_axis(axes, variants.len(), group.parent_axis_idx);
let Some((_, vi_empty)) = pair else {
return result;
};
let tree_empty = parse_html(&variants[vi_empty]);
let scoped_indices =
find_scoped_variant_indices(axes, variants.len(), group.parent_axis_idx, &group.children);
if scoped_indices.is_empty() {
return result;
}
let scoped_trees: Vec<Vec<DomNode>> =
scoped_indices.iter().map(|&i| parse_html(&variants[i])).collect();
let first_pop = &scoped_trees[0];
let Some(body_loc) = find_body_in_trees(first_pop, &tree_empty) else {
return result;
};
let body_variants: Vec<String> = scoped_trees
.iter()
.map(|tree| {
let parent = navigate_to_children(tree, &body_loc.path);
let body_nodes: Vec<DomNode> = body_loc
.body_indices
.iter()
.filter(|&&i| i < parent.len())
.map(|&i| parent[i].clone())
.collect();
serialize(&body_nodes)
})
.collect();
let parent_dot = format!("{}.", array_axis.path);
let child_axes: Vec<Axis> = group
.children
.iter()
.map(|&i| {
let orig = &axes[i];
Axis {
path: orig.path.strip_prefix(&parent_dot).unwrap_or(&orig.path).to_string(),
kind: orig.kind.clone(),
values: orig.values.clone(),
}
})
.collect();
let slot_prefix = format!("<!--seam:{}.", array_axis.path);
let body_variants: Vec<String> =
body_variants.into_iter().map(|b| b.replace(&slot_prefix, "<!--seam:")).collect();
let template_body = extract_template_inner(&child_axes, &body_variants);
let mut body_tree = parse_html(&template_body);
rename_slot_markers(&mut body_tree, &array_axis.path);
let each_nodes = wrap_array_body(&body_tree, &array_axis.path);
let final_nodes = if !body_loc.fallback_indices.is_empty() {
let empty_children = navigate_to_children(&tree_empty, &body_loc.path);
let fallback: Vec<DomNode> = body_loc
.fallback_indices
.iter()
.filter(|&&i| i < empty_children.len())
.map(|&i| empty_children[i].clone())
.collect();
let mut nodes = vec![comment_if(&array_axis.path)];
nodes.extend(each_nodes);
nodes.push(comment_else());
nodes.extend(fallback);
nodes.push(comment_endif(&array_axis.path));
nodes
} else {
each_nodes
};
replace_body_at_path(&mut result, &body_loc.path, &body_loc.body_indices, final_nodes);
result
}