use crate::{
error::{EnrichError, XacroError, IMPLEMENTED_FEATURES, UNIMPLEMENTED_FEATURES},
eval::PropertyScope,
parse::{macro_def::MacroDefinition, macro_def::MacroProcessor},
};
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::OnceLock;
use xmltree::{Element, XMLNode};
use super::{expand_children_list, XacroContext};
pub(super) trait DirectiveHandler: Send + Sync {
fn handle(
&self,
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError>;
}
static DIRECTIVE_REGISTRY: OnceLock<HashMap<&'static str, Box<dyn DirectiveHandler>>> =
OnceLock::new();
pub(super) fn get_directive_registry() -> &'static HashMap<&'static str, Box<dyn DirectiveHandler>>
{
DIRECTIVE_REGISTRY.get_or_init(|| {
let mut registry: HashMap<&'static str, Box<dyn DirectiveHandler>> = HashMap::new();
registry.insert("property", Box::new(PropertyDirective));
registry.insert("arg", Box::new(ArgDirective));
registry.insert("macro", Box::new(MacroDirective));
registry.insert("if", Box::new(IfDirective));
registry.insert("unless", Box::new(UnlessDirective));
registry.insert("insert_block", Box::new(InsertBlockDirective));
registry
})
}
struct PropertyDirective;
impl DirectiveHandler for PropertyDirective {
fn handle(
&self,
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
handle_property_directive(elem, ctx)
}
}
struct ArgDirective;
impl DirectiveHandler for ArgDirective {
fn handle(
&self,
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
handle_arg_directive(elem, ctx)
}
}
struct MacroDirective;
impl DirectiveHandler for MacroDirective {
fn handle(
&self,
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
handle_macro_directive(elem, ctx)
}
}
struct IfDirective;
impl DirectiveHandler for IfDirective {
fn handle(
&self,
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
handle_conditional_directive(elem, ctx, true)
}
}
struct UnlessDirective;
impl DirectiveHandler for UnlessDirective {
fn handle(
&self,
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
handle_conditional_directive(elem, ctx, false)
}
}
struct InsertBlockDirective;
impl DirectiveHandler for InsertBlockDirective {
fn handle(
&self,
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
handle_insert_block_directive(elem, ctx)
}
}
pub(super) fn handle_property_directive(
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
let loc = ctx.get_location_context();
let name = ctx
.properties
.substitute_text(
elem.get_attribute("name")
.ok_or_else(|| XacroError::MissingAttribute {
element: "xacro:property".to_string(),
attribute: "name".to_string(),
})
.with_loc(&loc)?,
Some(&loc),
)
.with_loc(&loc)?;
let scope = match elem.get_attribute("scope").map(|s| s.as_str()) {
None => PropertyScope::Local, Some("parent") => PropertyScope::Parent,
Some("global") => PropertyScope::Global,
Some(other) => {
return Err(XacroError::InvalidScopeAttribute {
property: name.clone(),
scope: other.to_string(),
})
}
};
let value_attr = elem.get_attribute("value");
let default_attr = elem.get_attribute("default");
let define_property = |raw_value: String| -> Result<(), XacroError> {
if scope == PropertyScope::Local {
ctx.properties
.define_property(name.clone(), raw_value, scope);
} else {
let evaluated = ctx
.properties
.substitute_all(&raw_value, Some(&loc))
.with_loc(&loc)?;
ctx.properties
.define_property(name.clone(), evaluated, scope);
}
Ok(())
};
match (value_attr, default_attr) {
(Some(value), _) => {
define_property(value.clone())?;
}
(None, Some(default_value)) => {
if !ctx.properties.has_property_in_scope(&name, scope) {
define_property(default_value.clone())?;
}
}
(None, None) => {
let has_content = crate::parse::xml::has_structural_content(&elem.children);
let is_text_only = !elem.children.is_empty() && !has_content;
if is_text_only {
return Ok(vec![]);
}
let lazy_name = format!("**{}", name);
if scope == PropertyScope::Local {
let content = crate::parse::xml::serialize_nodes(&elem.children)?;
ctx.properties.define_property(lazy_name, content, scope);
} else {
let expanded_nodes = expand_children_list(elem.children, ctx)?;
let content = crate::parse::xml::serialize_nodes(&expanded_nodes)?;
ctx.properties.define_property(lazy_name, content, scope);
}
}
}
Ok(vec![])
}
pub(super) fn handle_arg_directive(
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
let loc = ctx.get_location_context();
let raw_name = elem
.get_attribute("name")
.ok_or_else(|| XacroError::MissingAttribute {
element: "xacro:arg".to_string(),
attribute: "name".to_string(),
})
.with_loc(&loc)?;
let name = ctx
.properties
.substitute_text(raw_name, Some(&loc))
.with_loc(&loc)?;
if !ctx.args.borrow().contains_key(&name) {
if let Some(default_value) = elem.get_attribute("default") {
let default = ctx
.properties
.substitute_all(default_value, Some(&loc))
.with_loc(&loc)?;
ctx.args.borrow_mut().insert(name.clone(), default);
}
}
Ok(vec![])
}
pub(super) fn handle_macro_directive(
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
let loc = ctx.get_location_context();
let name = elem
.get_attribute("name")
.ok_or_else(|| XacroError::MissingAttribute {
element: "xacro:macro".to_string(),
attribute: "name".to_string(),
})
.with_loc(&loc)?
.to_string();
let params_str = elem.get_attribute("params").map_or("", |s| s.as_str());
let (params_map, param_order, block_params_set, lazy_block_params_set) =
if ctx.compat_mode.duplicate_params {
MacroProcessor::parse_params_compat(params_str)?
} else {
MacroProcessor::parse_params(params_str)?
};
let macro_def = MacroDefinition {
name: name.clone(),
params: params_map,
param_order,
block_params: block_params_set,
lazy_block_params: lazy_block_params_set,
content: elem,
};
ctx.macros
.borrow_mut()
.insert(name.clone(), Rc::new(macro_def));
Ok(vec![])
}
pub(super) fn handle_conditional_directive(
elem: Element,
ctx: &XacroContext,
is_if: bool,
) -> Result<Vec<XMLNode>, XacroError> {
let tag_name = if is_if { "xacro:if" } else { "xacro:unless" };
let loc = ctx.get_location_context();
let value = elem
.get_attribute("value")
.ok_or_else(|| XacroError::MissingAttribute {
element: tag_name.to_string(),
attribute: "value".to_string(),
})
.with_loc(&loc)?;
let condition = ctx
.properties
.eval_boolean(value, Some(&loc))
.with_loc(&loc)?;
let should_expand = if is_if { condition } else { !condition };
if should_expand {
expand_children_list(elem.children, ctx)
} else {
Ok(vec![]) }
}
pub(super) fn handle_insert_block_directive(
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
let loc = ctx.get_location_context();
let name = ctx
.properties
.substitute_text(
elem.get_attribute("name")
.ok_or_else(|| XacroError::MissingAttribute {
element: "xacro:insert_block".to_string(),
attribute: "name".to_string(),
})
.with_loc(&loc)?,
Some(&loc),
)
.with_loc(&loc)?;
let lazy_name = format!("**{}", name);
if let Some(raw_value) = ctx.properties.lookup_raw_value(&lazy_name) {
match crate::parse::xml::parse_xml_fragment(&raw_value) {
Ok(nodes) => {
let expanded = expand_children_list(nodes, ctx)?;
return Ok(expanded);
}
Err(e) => {
log::error!(
"Failed to parse stored lazy property '{}' as XML fragment: {}. Raw value: '{}'",
name, e, raw_value
);
return Err(XacroError::InvalidXml(format!(
"Corrupted lazy property '{}': failed to parse stored XML fragment: {}",
name, e
)));
}
}
}
ctx.lookup_block(&name)
}
pub(super) fn check_unimplemented_directive(
elem: &Element,
xacro_ns: &str,
) -> Result<(), XacroError> {
for feature in UNIMPLEMENTED_FEATURES {
let directive = feature.strip_prefix("xacro:").unwrap_or(feature);
if crate::parse::xml::is_xacro_element(elem, directive, xacro_ns) {
return Err(XacroError::UnimplementedFeature(format!(
"<xacro:{}>\n\
This element was not processed. Either:\n\
1. The feature is not implemented yet (known unimplemented: {})\n\
2. There's a bug in the processor\n\
\n\
Currently implemented: {}",
elem.name,
UNIMPLEMENTED_FEATURES.join(", "),
IMPLEMENTED_FEATURES.join(", ")
)));
}
}
Ok(())
}