use crate::{error::XacroError, parse::xml::extract_xacro_namespace};
use std::path::PathBuf;
use xmltree::{Element, XMLNode};
use super::{children::expand_children_list, guards::IncludeGuard, XacroContext};
fn is_glob_pattern(filename: &str) -> bool {
filename.contains('*') || filename.contains('[') || filename.contains('?')
}
fn process_single_include(
file_path: PathBuf,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
if ctx.include_stack.borrow().contains(&file_path) {
return Err(XacroError::Include(format!(
"Circular include detected: {}",
file_path.display()
)));
}
let content = std::fs::read_to_string(&file_path).map_err(|e| {
XacroError::Include(format!(
"Failed to read file '{}': {}",
file_path.display(),
e
))
})?;
let included_root = Element::parse(content.as_bytes()).map_err(|e| {
XacroError::Include(format!(
"Failed to parse XML in file '{}': {}",
file_path.display(),
e
))
})?;
let included_ns = extract_xacro_namespace(&included_root, ctx.compat_mode.namespace)?;
let old_base_path = ctx.base_path.borrow().clone();
let mut new_base_path = file_path.clone();
new_base_path.pop();
let _include_guard = IncludeGuard::new(
&ctx.base_path,
&ctx.include_stack,
&ctx.namespace_stack,
old_base_path,
);
*ctx.base_path.borrow_mut() = new_base_path;
ctx.include_stack.borrow_mut().push(file_path.clone());
ctx.all_includes.borrow_mut().push(file_path.clone());
ctx.namespace_stack
.borrow_mut()
.push((file_path.clone(), included_ns));
expand_children_list(included_root.children, ctx)
}
pub(super) fn handle_include_directive(
elem: Element,
ctx: &XacroContext,
) -> Result<Vec<XMLNode>, XacroError> {
use crate::error::EnrichError;
let loc = ctx.get_location_context();
let filename = ctx
.properties
.substitute_text(
elem.get_attribute("filename")
.ok_or_else(|| XacroError::MissingAttribute {
element: "xacro:include".to_string(),
attribute: "filename".to_string(),
})
.with_loc(&loc)?,
Some(&loc),
)
.with_loc(&loc)?;
let optional = elem
.get_attribute("optional")
.map(|v| v == "true" || v == "1")
.unwrap_or(false);
if is_glob_pattern(&filename) {
let glob_pattern = {
let base = ctx.base_path.borrow();
if std::path::Path::new(&filename).is_absolute() {
filename.clone()
} else {
base.join(&filename)
.to_str()
.ok_or_else(|| {
XacroError::Include(format!("Invalid UTF-8 in glob pattern: {}", filename))
})?
.to_string()
}
};
let mut matches: Vec<PathBuf> = glob::glob(&glob_pattern)
.map_err(|e| {
XacroError::Include(format!("Invalid glob pattern '{}': {}", filename, e))
})?
.filter_map(|result| match result {
Ok(path) => Some(path),
Err(e) => {
log::warn!("Error reading glob match: {}", e);
None
}
})
.collect();
if matches.is_empty() {
if !optional {
log::warn!(
"Include tag's filename spec \"{}\" matched no files.",
filename
);
}
return Ok(vec![]);
}
matches.sort();
let mut result = Vec::new();
for file_path in matches {
let expanded = process_single_include(file_path, ctx)?;
result.extend(expanded);
}
return Ok(result);
}
let file_path = ctx.base_path.borrow().join(&filename);
if optional && !file_path.exists() {
return Ok(vec![]);
}
process_single_include(file_path, ctx)
}