use core::convert::TryFrom;
use alloc::{
collections::btree_map::BTreeMap,
string::{String, ToString},
vec::Vec,
};
use woxml::{Write, XmlWriter};
use crate::{
ConstString, ID, NAME, SUBTREE,
tree::{BehaviorTree, BehaviorTreeElement, TreeElementKind},
};
#[derive(Default)]
pub struct XmlCreator;
impl XmlCreator {
pub fn write_tree(
tree: &BehaviorTree,
metadata: bool,
builtin_models: bool,
pretty: bool,
) -> Result<ConstString, woxml::Error> {
let mut writer = if pretty {
XmlWriter::pretty_mode(Vec::new())
} else {
XmlWriter::compact_mode(Vec::new())
};
writer.begin_elem("root")?;
writer.attr("BTCPP_format", "4")?;
let (behaviors, subtrees) = Self::scan_tree(tree, builtin_models);
{
Self::create_behavior_trees(&mut writer, &subtrees, metadata)?;
Self::create_tree_nodes_model(&mut writer, &behaviors, builtin_models, pretty, false)?;
}
writer.end_elem()?; writer.flush()?;
Ok(String::try_from(writer)?.into())
}
fn create_behavior_trees<'a>(
writer: &mut XmlWriter<'a, impl Write>,
subtrees: &'a Vec<&BehaviorTreeElement>,
metadata: bool,
) -> Result<(), woxml::Error> {
for subtree in subtrees {
writer.begin_elem("BehaviorTree")?;
writer.attr(ID, subtree.name())?;
writer.attr("_fullpath", subtree.groot2_path())?;
for element in subtree.children().iter() {
Self::write_tree_element(element, writer, metadata)?;
}
writer.end_elem()?; }
Ok(())
}
fn write_tree_element<'a>(
element: &'a BehaviorTreeElement,
writer: &mut XmlWriter<'a, impl Write>,
metadata: bool,
) -> Result<(), woxml::Error> {
let name = element.name().as_ref();
let is_subtree = match element.kind() {
TreeElementKind::Leaf | TreeElementKind::Node => {
writer.begin_elem(element.id())?;
writer.attr(NAME, name)?;
false
}
TreeElementKind::SubTree => {
writer.begin_elem(SUBTREE)?;
writer.attr(ID, name)?;
if metadata {
writer.attr("_fullpath", element.groot2_path())?;
}
true
}
};
if metadata {
writer.attr("_uid", &element.uid().to_string())?;
}
let txt = element.data().description().configuration();
if !txt.is_empty() {
writer.write(" ")?;
writer.write(txt)?;
}
if !is_subtree {
for element in element.children().iter() {
Self::write_tree_element(element, writer, metadata)?;
}
}
writer.end_elem()?;
Ok(())
}
fn create_tree_nodes_model<'a>(
writer: &mut XmlWriter<'a, impl Write>,
behaviors: &'a BTreeMap<ConstString, &BehaviorTreeElement>,
builtin_models: bool,
pretty: bool,
groot: bool,
) -> Result<(), woxml::Error> {
writer.begin_elem("TreeNodesModel")?;
for (_, item) in behaviors {
if builtin_models || !item.data().description().groot2() {
writer.begin_elem(item.behavior().kind().as_str())?;
writer.attr(ID, item.id())?;
for (port_name, port_variant) in item.behavior().portlist().iter() {
writer.begin_elem(port_variant.direction())?;
writer.attr(NAME, port_name)?;
if groot {
writer.attr("type", Self::groot_map_types(port_variant.data_type()))?;
} else {
writer.attr("type", port_variant.data_type())?;
}
writer.end_elem()?;
if pretty {
writer.set_pretty_mode();
}
}
writer.end_elem()?;
}
}
writer.end_elem()?;
Ok(())
}
fn scan_tree(
tree: &BehaviorTree,
builtin_models: bool,
) -> (BTreeMap<ConstString, &BehaviorTreeElement>, Vec<&BehaviorTreeElement>) {
let mut behaviors: BTreeMap<ConstString, &BehaviorTreeElement> = BTreeMap::new();
let mut subtrees: Vec<&BehaviorTreeElement> = Vec::new();
for item in tree.iter() {
match item.kind() {
TreeElementKind::Leaf | TreeElementKind::Node => {
let desc = item.data().description();
if builtin_models || !desc.groot2() {
behaviors.insert(desc.name().clone(), item);
}
}
TreeElementKind::SubTree => {
subtrees.push(item);
}
}
}
(behaviors, subtrees)
}
pub fn groot_write_tree(tree: &BehaviorTree) -> Result<bytes::Bytes, woxml::Error> {
let mut writer = XmlWriter::compact_mode(bytes::BytesMut::new());
writer.begin_elem("root")?;
writer.attr("BTCPP_format", "4")?;
let (behaviors, subtrees) = Self::scan_tree(tree, false);
{
Self::create_behavior_trees(&mut writer, &subtrees, true)?;
Self::create_tree_nodes_model(&mut writer, &behaviors, false, false, true)?;
}
writer.end_elem()?; writer.flush()?;
Ok(String::try_from(writer)?.into())
}
fn groot_map_types(input: &str) -> &str {
match input {
"char" => "char",
"i16" => "short",
"u16" => "unsigned short",
"i32" => "int",
"u32" => "unsigned int",
"i64" => "long",
"u64" => "unsigned long",
"f32" => "float",
"f64" => "double",
"String" => "std::string",
"BehaviorState" => "BT::NodeStatus",
_ => "BT::Any",
}
}
}
#[cfg(test)]
mod tests {
use super::XmlCreator;
#[test]
fn groot_map_types_all_branches() {
assert_eq!(XmlCreator::groot_map_types("char"), "char");
assert_eq!(XmlCreator::groot_map_types("i16"), "short");
assert_eq!(XmlCreator::groot_map_types("u16"), "unsigned short");
assert_eq!(XmlCreator::groot_map_types("i32"), "int");
assert_eq!(XmlCreator::groot_map_types("u32"), "unsigned int");
assert_eq!(XmlCreator::groot_map_types("i64"), "long");
assert_eq!(XmlCreator::groot_map_types("u64"), "unsigned long");
assert_eq!(XmlCreator::groot_map_types("f32"), "float");
assert_eq!(XmlCreator::groot_map_types("f64"), "double");
assert_eq!(XmlCreator::groot_map_types("String"), "std::string");
assert_eq!(XmlCreator::groot_map_types("BehaviorState"), "BT::NodeStatus");
assert_eq!(XmlCreator::groot_map_types("SomeCustomType"), "BT::Any");
}
}