15#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
21#include "ament_index_cpp/get_package_prefix.hpp"
22#include "ament_index_cpp/get_package_share_path.hpp"
23#include "auto_apms_behavior_tree_core/builder.hpp"
24#include "auto_apms_behavior_tree_core/exceptions.hpp"
25#include "auto_apms_behavior_tree_core/node/node_model_type.hpp"
26#include "auto_apms_behavior_tree_core/tree/tree_resource.hpp"
27#include "auto_apms_util/container.hpp"
28#include "auto_apms_util/logging.hpp"
29#include "auto_apms_util/string.hpp"
30#include "behaviortree_cpp/xml_parsing.h"
31#include "yaml-cpp/yaml.h"
36std::vector<std::string> getAllTreeNamesImpl(
const tinyxml2::XMLDocument & doc)
38 std::vector<std::string> names;
39 if (
const tinyxml2::XMLElement * root = doc.RootElement()) {
40 if (strcmp(root->Name(), TreeDocument::ROOT_ELEMENT_NAME) == 0) {
42 for (
const tinyxml2::XMLElement * child = root->FirstChildElement(); child !=
nullptr;
43 child = child->NextSiblingElement()) {
44 if (strcmp(TreeDocument::TREE_ELEMENT_NAME, child->Name()) == 0) {
45 if (
const char * name = child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) {
46 names.push_back(name);
48 throw exceptions::TreeDocumentError(
49 "Cannot get tree name, because required attribute '" +
50 std::string(TreeDocument::TREE_NAME_ATTRIBUTE_NAME) +
"' is missing.");
54 }
else if (strcmp(root->Name(), TreeDocument::TREE_ELEMENT_NAME) == 0) {
56 if (
const char * name = root->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) names.push_back(name);
62bool hasInlineRegistrationOptions(
const tinyxml2::XMLElement * ele)
64 for (
const tinyxml2::XMLAttribute * attr = ele->FirstAttribute(); attr !=
nullptr; attr = attr->Next()) {
65 if (std::string_view(attr->Name()).rfind(TreeDocument::INLINE_NODE_REGISTRATION_OPTIONS_ATTRIBUTE_PREFIX, 0) == 0)
71std::optional<NodeRegistrationOptions> parseInlineRegistrationOptions(
const tinyxml2::XMLElement * ele)
73 YAML::Node reg_opts_node(YAML::NodeType::Map);
75 for (
const tinyxml2::XMLAttribute * attr = ele->FirstAttribute(); attr !=
nullptr; attr = attr->Next()) {
76 if (
const std::string attr_name = attr->Name();
77 attr_name.rfind(TreeDocument::INLINE_NODE_REGISTRATION_OPTIONS_ATTRIBUTE_PREFIX, 0) == 0) {
78 const std::string option_name =
79 attr_name.substr(strlen(TreeDocument::INLINE_NODE_REGISTRATION_OPTIONS_ATTRIBUTE_PREFIX));
81 reg_opts_node[option_name] = YAML::Load(attr->Value());
82 }
catch (
const std::exception &) {
90 return options.
valid() ? std::optional(options) : std::nullopt;
91 }
catch (
const std::exception &) {
100 throw exceptions::TreeDocumentError(
"Cannot create an instance of NodeElement with doc_ptr=nullptr.");
103 throw exceptions::TreeDocumentError(
"Cannot create an instance of NodeElement with ele_ptr=nullptr.");
113 refreshPortInfoCache();
116void TreeDocument::NodeElement::refreshPortInfoCache()
const
119 NodeModelMap::const_iterator it =
model.find(ele_ptr_->Name());
121 std::vector<std::string> refreshed_port_names;
122 PortValues refreshed_port_default_values;
123 if (it !=
model.end()) {
124 const std::vector<NodePortInfo> & port_infos_str_vec = it->second.port_infos;
125 refreshed_port_names.reserve(port_infos_str_vec.size());
126 for (
const NodePortInfo & port_info : port_infos_str_vec) {
127 refreshed_port_names.push_back(port_info.port_name);
128 if (!port_info.port_default.empty()) {
129 refreshed_port_default_values[port_info.port_name] = port_info.port_default;
134 if (refreshed_port_names == port_names_ && refreshed_port_default_values == port_default_values_) {
138 port_names_ = std::move(refreshed_port_names);
139 port_default_values_ = std::move(refreshed_port_default_values);
143 if (!port_default_values_.empty()) {
144 PortValues existing_port_values;
145 for (
const tinyxml2::XMLAttribute * attr = ele_ptr_->FirstAttribute(); attr !=
nullptr; attr = attr->Next()) {
147 existing_port_values[attr_name] = attr->Value();
150 for (
const auto & [name, val] : port_default_values_) {
151 if (existing_port_values.find(name) == existing_port_values.end()) {
152 ele_ptr_->SetAttribute(name.c_str(), val.c_str());
160 XMLElement * other_ele = other.
ele_ptr_;
163 other_ele = other_ele->DeepClone(
doc_ptr_)->ToElement();
165 XMLElement * prev =
ele_ptr_->PreviousSiblingElement();
167 ele_ptr_->Parent()->InsertAfterChild(
ele_ptr_->PreviousSibling(), other_ele);
169 ele_ptr_->Parent()->InsertFirstChild(other_ele);
176 port_names_ = other.port_names_;
177 port_default_values_ = other.port_default_values_;
183bool TreeDocument::NodeElement::operator!=(
const NodeElement & other)
const {
return !this->operator==(other); }
186 const std::string & name,
const NodeElement * before_this)
190 XMLElement * ele =
doc_ptr_->NewElement(name.c_str());
191 return insertBeforeImpl(before_this, ele);
199 if (
const std::set<std::string> names =
doc_ptr_->getRegisteredNodeNames(
false); names.find(name) == names.end()) {
206 const std::string & tree_name,
const NodeElement * before_this)
208 if (!
doc_ptr_->hasTreeName(tree_name)) {
209 throw exceptions::TreeDocumentError(
210 "Cannot insert subtree node for tree '" + tree_name +
"' because no tree with that name exists.");
213 ele.
ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
219 const std::string tree_name = tree.
getName();
220 if (
doc_ptr_->hasTreeName(tree_name)) {
227 if (existing_tree != tree) {
228 throw exceptions::TreeDocumentError(
229 "Cannot insert subtree node using the provided tree element, because another tree with name '" + tree_name +
230 "' already exists.");
234 const std::string existing_xml = existing_tree.
writeToString();
236 if (existing_xml != incoming_xml) {
237 throw exceptions::TreeDocumentError(
238 "Cannot insert subtree node using the provided tree element, because another tree with name '" + tree_name +
239 "' already exists with different content.");
253 const XMLElement * root_child = tree.
ele_ptr_->FirstChildElement();
255 throw exceptions::TreeDocumentError(
258 if (root_child->NextSibling()) {
259 throw exceptions::TreeDocumentError(
260 "Cannot insert tree element '" + tree.
getFullyQualifiedName() +
"' because it has more than one child node.");
266 const TreeDocument & doc,
const std::string & tree_name,
const NodeElement * before_this,
bool auto_register_nodes)
268 const std::vector<std::string> other_tree_names = doc.
getAllTreeNames();
269 if (other_tree_names.empty()) {
270 throw exceptions::TreeDocumentError(
271 "Cannot insert tree '" + tree_name +
"' because document has no <" + TREE_ELEMENT_NAME +
"> elements.");
274 throw exceptions::TreeDocumentError(
275 "Cannot insert tree '" + tree_name +
"' because document doesn't specify a tree with that name.");
278 if (auto_register_nodes) {
286 std::set<std::string> required_tree_names = {tree_name};
292 doc.DeepCopy(&temp_doc);
293 const TreeElement other_tree(&temp_doc, temp_doc.getXMLElementForTreeWithName(tree_name));
295 collect_dependency_tree_names = [&required_tree_names, &collect_dependency_tree_names](
const NodeElement & ele) {
296 if (ele.getRegistrationName() == SUBTREE_ELEMENT_NAME) {
297 if (
const char * name = ele.ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
299 required_tree_names.insert(name);
302 ele.doc_ptr_->getTree(name).deepApplyConst(collect_dependency_tree_names);
304 throw exceptions::TreeDocumentError(
"Subtree element has no name attribute.");
314 return available_names.find(ele.getRegistrationName()) == available_names.end();
316 for (
const std::string & name : required_tree_names) {
317 const NodeElement ele(&temp_doc, temp_doc.getXMLElementForTreeWithName(name)->FirstChildElement());
318 if (
const std::vector<NodeElement> found = ele.
deepApplyConst(apply); !found.empty()) {
319 std::vector<std::string> names;
321 throw exceptions::TreeDocumentError(
322 "Cannot insert tree '" + tree_name +
"' because the following nodes found in tree '" + name +
323 "' are unknown to the builder:\n\t- " + auto_apms_util::join(names,
"\n\t- "));
329 required_tree_names.erase(tree_name);
330 for (
const std::string & name : other_tree_names) {
331 if (required_tree_names.find(name) == required_tree_names.end()) {
336 doc_ptr_->mergeTreeDocument(
static_cast<const XMLDocument &
>(temp_doc),
false);
339 return insertBeforeImpl(
340 before_this, doc.getXMLElementForTreeWithName(tree_name)->FirstChildElement()->DeepClone(
doc_ptr_)->ToElement());
350 const std::string & tree_str,
const std::string & tree_name,
const NodeElement * before_this)
358 const std::string & tree_str,
const NodeElement * before_this)
366 const std::string & path,
const std::string & tree_name,
const NodeElement * before_this)
374 const std::string & path,
const NodeElement * before_this)
383 bool auto_register_nodes)
386 insert_doc.
mergeFile(resource.build_request_file_path_);
399 const std::string & registration_name,
const std::string & instance_name,
bool deep_search)
const
404 if (registration_name.empty())
return ele.getName() == instance_name;
405 if (instance_name.empty())
return ele.getRegistrationName() == registration_name;
406 return ele.getRegistrationName() == registration_name && ele.getName() == instance_name;
410 if (
const std::vector<NodeElement> found =
deepApplyConst(apply); !found.empty())
return found[0];
412 for (
auto child : *
this) {
413 if (apply(child))
return child;
418 throw exceptions::TreeDocumentError(
419 "Cannot find a child node that matches the search arguments (registration_name: '" + registration_name +
424 const std::string & registration_name,
const std::string & instance_name,
bool deep_search)
426 XMLElement * found =
getFirstNode(registration_name, instance_name, deep_search).ele_ptr_;
427 found->Parent()->DeleteChild(found);
439 refreshPortInfoCache();
445 refreshPortInfoCache();
447 for (
const tinyxml2::XMLAttribute * attr =
ele_ptr_->FirstAttribute(); attr !=
nullptr; attr = attr->Next()) {
449 values[attr_name] = attr->Value();
457 return parseInlineRegistrationOptions(
ele_ptr_);
462 refreshPortInfoCache();
465 std::vector<std::string> unknown_keys;
466 for (
const auto & [key, _] : port_values) {
469 if (!unknown_keys.empty()) {
470 throw exceptions::TreeDocumentError(
471 "Cannot set ports. According to the node model, the following ports are not implemented by '" +
472 std::string(
ele_ptr_->Name()) +
"': [ " + auto_apms_util::join(unknown_keys,
", ") +
" ].");
476 for (
const auto & [key, val] : port_values) {
477 ele_ptr_->SetAttribute(key.c_str(), val.c_str());
485 if (!registration_options.
valid()) {
486 throw exceptions::TreeDocumentError(
488 "': Registration options are invalid.");
490 const YAML::Node node = YAML::convert<NodeRegistrationOptions>::encode(registration_options);
491 for (YAML::const_iterator it = node.begin(); it != node.end(); ++it) {
492 const std::string attr_name =
493 std::string(INLINE_NODE_REGISTRATION_OPTIONS_ATTRIBUTE_PREFIX) + it->first.as<std::string>();
496 YAML::Emitter emitter;
497 emitter << YAML::Flow << it->second;
498 ele_ptr_->SetAttribute(attr_name.c_str(), emitter.c_str());
506 if (!
doc_ptr_->registered_nodes_manifest_.contains(name)) {
507 throw exceptions::TreeDocumentError(
509 "' has not been registered with the parent document.");
516 refreshPortInfoCache();
517 for (
const std::string & name : port_names_) {
518 PortValues::const_iterator it = port_default_values_.find(name);
519 if (it != port_default_values_.end()) {
520 ele_ptr_->SetAttribute(it->first.c_str(), it->second.c_str());
522 ele_ptr_->DeleteAttribute(name.c_str());
530 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.
str().c_str());
536 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.
str().c_str());
542 ele_ptr_->SetAttribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME, instance_name.c_str());
550 if (
const char * name =
ele_ptr_->Attribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME))
return name;
557 std::string instance_name =
getName();
558 if (registration_name == instance_name)
return registration_name;
559 return instance_name +
" (" + registration_name +
")";
569 std::vector<NodeElement> found;
570 deepApplyImpl(*
this, apply_callback, found);
576 std::vector<NodeElement> found;
577 deepApplyImpl(*
this, apply_callback, found);
582 const NodeElement * before_this, XMLElement * add_this)
585 XMLElement * prev =
nullptr;
586 XMLElement * curr = ele_ptr_->FirstChildElement();
592 if (curr == before_this->
ele_ptr_) {
597 curr = curr->NextSiblingElement();
601 ele_ptr_->InsertAfterChild(prev, add_this);
603 throw exceptions::TreeDocumentError(
605 getFullyQualifiedName() +
".");
608 ele_ptr_->InsertFirstChild(add_this);
611 ele_ptr_->InsertEndChild(add_this);
616void TreeDocument::NodeElement::deepApplyImpl(
617 const NodeElement & parent, ConstDeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
619 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child !=
nullptr;
620 child = child->NextSiblingElement()) {
621 const NodeElement child_ele(parent.doc_ptr_, child);
624 if (apply_callback(child_ele)) vec.push_back(child_ele);
627 deepApplyImpl(child_ele, apply_callback, vec);
631void TreeDocument::NodeElement::deepApplyImpl(
632 NodeElement & parent, DeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
634 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child !=
nullptr;
635 child = child->NextSiblingElement()) {
639 if (apply_callback(child_ele)) vec.push_back(child_ele);
642 deepApplyImpl(child_ele, apply_callback, vec);
649:
doc_ptr_(doc_ptr), current_(current)
660 if (current_) current_ = current_->NextSiblingElement();
671bool TreeDocument::NodeElement::ChildIterator::operator==(
const ChildIterator & other)
const
673 return current_ == other.current_;
676bool TreeDocument::NodeElement::ChildIterator::operator!=(
const ChildIterator & other)
const
678 return current_ != other.current_;
690 if (!ele_ptr->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
691 throw exceptions::TreeDocumentError(
692 "Cannot create tree element without a '" + std::string(TREE_NAME_ATTRIBUTE_NAME) +
"' attribute.");
698 const std::string other_tree_name = other.
getName();
702 throw exceptions::TreeDocumentError(
703 "Cannot copy tree '" + other.
getName() +
"' because another tree with this name already exists.");
711 ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
717 if (
const char * name =
ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME))
return name;
732 const bool is_native_node =
doc_ptr_->native_node_names_.find(name) !=
doc_ptr_->native_node_names_.end();
733 if (!is_native_node && !m.
contains(name)) {
734 if (!
doc_ptr_->registered_nodes_manifest_.contains(name)) {
735 throw exceptions::NodeManifestError(
736 "Cannot assemble the required node manifest for tree '" +
getName() +
737 "' since there are no registration options for node '" + name +
"'.");
739 m.
add(name,
doc_ptr_->registered_nodes_manifest_[name]);
750 const bool is_native_node =
doc_ptr_->native_node_names_.find(name) !=
doc_ptr_->native_node_names_.end();
751 if (!is_native_node) {
761 TreeDocument doc(
doc_ptr_->format_version_,
doc_ptr_->tree_node_loader_ptr_);
767 XMLDocument tree_doc;
768 tree_doc.InsertEndChild(
ele_ptr_->DeepClone(&tree_doc));
769 tinyxml2::XMLPrinter printer(
nullptr, remove_whitespace);
770 tree_doc.Print(&printer);
771 return printer.CStr();
775 const std::string & registration_name,
const std::string & instance_name,
bool deep_search)
790: XMLDocument(true, tinyxml2::PRESERVE_WHITESPACE),
792 native_node_names_(BT::BehaviorTreeFactory().builtinNodes()),
793 format_version_(format_version),
794 tree_node_loader_ptr_(tree_node_loader),
795 registered_nodes_manifest_(),
797 logger_(rclcpp::get_logger(LOGGER_NAME))
803 const XMLDocument & other,
bool adopt_root_tree, std::set<std::string> & include_stack)
805 const XMLElement * other_root = other.RootElement();
807 throw exceptions::TreeDocumentError(
"Cannot merge tree documents: other_root is nullptr.");
810 auto verify_tree_structure = [](
const XMLElement * tree_ele) {
811 const char * tree_id = tree_ele->Attribute(TREE_NAME_ATTRIBUTE_NAME);
813 throw exceptions::TreeDocumentError(
814 "Cannot merge tree document: Found a <" + std::string(TREE_ELEMENT_NAME) +
815 "> element that doesn't specify the required attribute '" + TREE_NAME_ATTRIBUTE_NAME +
"'.");
817 const XMLElement * tree_root_child = tree_ele->FirstChildElement();
818 if (!tree_root_child) {
819 throw exceptions::TreeDocumentError(
820 "Cannot merge tree document: Tree '" + std::string(tree_id) +
"' has no child nodes.");
822 if (tree_root_child->NextSibling()) {
823 throw exceptions::TreeDocumentError(
824 "Cannot merge tree document: Tree '" + std::string(tree_id) +
"' has more than one child node.");
829 auto check_duplicates = [
this](
const std::vector<std::string> & new_tree_names,
const std::string & source) {
831 if (!common.empty()) {
832 throw exceptions::TreeDocumentError(
833 "Cannot merge tree document: The following trees from " + source +
" are already defined: [ " +
834 auto_apms_util::join(common,
", ") +
" ].");
838 auto register_inline_nodes_for_tree = [
this](
const XMLElement * tree_ele) {
839 const char * tree_name_attr = tree_ele->Attribute(TREE_NAME_ATTRIBUTE_NAME);
840 const std::string tree_name = tree_name_attr ? tree_name_attr :
"<unknown>";
841 NodeManifest inline_manifest;
843 auto collect_inline_registration_options = [&](
auto && self,
const XMLElement * node_ele) ->
void {
848 if (strcmp(node_ele->Name(), SUBTREE_ELEMENT_NAME) != 0 && hasInlineRegistrationOptions(node_ele)) {
849 const std::string registration_name = node_ele->Name();
850 const std::optional<NodeRegistrationOptions> parsed_options = parseInlineRegistrationOptions(node_ele);
851 if (!parsed_options.has_value()) {
852 throw exceptions::TreeDocumentError(
853 "Cannot merge tree document: Invalid inline registration options for node '" + registration_name +
854 "' in tree '" + tree_name +
"'.");
857 const NodeRegistrationOptions & inline_options = parsed_options.value();
859 if (registered_nodes_manifest_.contains(registration_name)) {
860 if (registered_nodes_manifest_[registration_name] != inline_options) {
861 throw exceptions::TreeDocumentError(
862 "Cannot merge tree document: Inline registration options for node '" + registration_name +
"' in tree '" +
863 tree_name +
"' conflict with an existing registration.");
865 }
else if (inline_manifest.contains(registration_name)) {
868 if (inline_manifest[registration_name] != inline_options) {
869 throw exceptions::TreeDocumentError(
870 "Cannot merge tree document: Found conflicting inline registration options for node '" +
871 registration_name +
"' in tree '" + tree_name +
"'.");
874 inline_manifest.add(registration_name, inline_options);
878 for (
const XMLElement * child = node_ele->FirstChildElement(); child !=
nullptr;
879 child = child->NextSiblingElement()) {
884 collect_inline_registration_options(collect_inline_registration_options, tree_ele->FirstChildElement());
886 if (!inline_manifest.empty()) {
892 const std::vector<std::string> other_tree_names = getAllTreeNamesImpl(other);
894 if (strcmp(other_root->Name(), ROOT_ELEMENT_NAME) == 0) {
896 if (
const char * ver = other_root->Attribute(BTCPP_FORMAT_ATTRIBUTE_NAME)) {
897 if (std::string(ver) != format_version_) {
898 throw exceptions::TreeDocumentError(
899 "Cannot merge tree document: Format of other document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
900 ver +
") is not compatible with this document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
901 format_version_ +
").");
904 throw exceptions::TreeDocumentError(
905 "Cannot merge tree document: Root element of other document doesn't have required attribute '" +
906 std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) +
"'.");
910 TreeDocument include_doc(format_version_, tree_node_loader_ptr_);
911 for (
const XMLElement * include_ele = other_root->FirstChildElement(INCLUDE_ELEMENT_NAME); include_ele !=
nullptr;
912 include_ele = include_ele->NextSiblingElement(INCLUDE_ELEMENT_NAME)) {
913 const char * path = include_ele->Attribute(INCLUDE_PATH_ATTRIBUTE_NAME);
914 const char * resource_identity = include_ele->Attribute(INCLUDE_RESOURCE_ATTRIBUTE_NAME);
916 if (path && resource_identity) {
917 throw exceptions::TreeDocumentError(
918 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
919 "> element that specifies both the '" + INCLUDE_PATH_ATTRIBUTE_NAME +
"' and the '" +
920 INCLUDE_RESOURCE_ATTRIBUTE_NAME +
"' attribute. Please specify only one of them.");
923 std::string absolute_path;
926 std::string cherry_pick_tree_name;
927 if (resource_identity) {
931 if (std::string(resource_identity).empty()) {
932 throw exceptions::TreeDocumentError(
933 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
934 "> element that specifies an empty resource identity in attribute '" + INCLUDE_RESOURCE_ATTRIBUTE_NAME +
937 if (include_ele->Attribute(INCLUDE_ROS_PKG_ATTRIBUTE_NAME)) {
938 throw exceptions::TreeDocumentError(
939 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
940 "> element that specifies the '" + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
"' attribute together with '" +
941 INCLUDE_RESOURCE_ATTRIBUTE_NAME +
"'. The '" + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
942 "' attribute is only allowed in combination with '" + INCLUDE_PATH_ATTRIBUTE_NAME +
"'.");
945 const TreeResource resource{std::string(resource_identity)};
946 absolute_path = resource.build_request_file_path_;
949 cherry_pick_tree_name = resource.getRootTreeName();
950 }
catch (
const std::exception & e) {
951 throw exceptions::TreeDocumentError(
952 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
953 "> element that specifies the tree resource identity '" + std::string(resource_identity) +
954 "' in attribute '" + INCLUDE_RESOURCE_ATTRIBUTE_NAME +
"' which cannot be resolved: " + e.what());
957 if (std::string(path).empty()) {
958 throw exceptions::TreeDocumentError(
959 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
960 "> element that specifies an empty path in attribute '" + INCLUDE_PATH_ATTRIBUTE_NAME +
"'.");
962 absolute_path = path;
963 if (
const char * ros_pkg = include_ele->Attribute(INCLUDE_ROS_PKG_ATTRIBUTE_NAME)) {
964 if (std::string(ros_pkg).empty()) {
965 throw exceptions::TreeDocumentError(
966 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
967 "> element that specifies an empty ROS package name in attribute '" + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
970 if (std::filesystem::path(path).is_absolute()) {
971 throw exceptions::TreeDocumentError(
972 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
973 "> element that specifies an absolute path '" + std::string(path) +
"' in attribute '" +
974 INCLUDE_PATH_ATTRIBUTE_NAME +
"' together with a ROS package name in attribute '" +
975 INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
"'. Please remove the attribute " + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
976 " or use a relative path if you want to refer to a location "
977 "relative to the package's share directory.");
980 absolute_path = (ament_index_cpp::get_package_share_path(ros_pkg) / path).
string();
981 }
catch (
const ament_index_cpp::PackageNotFoundError & e) {
982 throw exceptions::TreeDocumentError(
983 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
984 "> element that specifies a non-existing ROS package '" + std::string(ros_pkg) +
"' in attribute '" +
985 INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
"'.");
989 throw exceptions::TreeDocumentError(
990 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
991 "> element that doesn't specify any of the required attributes '" + INCLUDE_PATH_ATTRIBUTE_NAME +
"' or '" +
992 INCLUDE_RESOURCE_ATTRIBUTE_NAME +
"'.");
998 TreeDocument work_doc(format_version_, tree_node_loader_ptr_);
1000 work_doc.mergeFileImpl(absolute_path,
false, include_stack);
1001 }
catch (
const std::exception & e) {
1002 throw exceptions::TreeDocumentError(
1003 "Cannot merge tree document: Failed to include file '" + absolute_path +
"': " + e.what());
1008 if (!cherry_pick_tree_name.empty()) {
1009 if (!work_doc.hasTreeName(cherry_pick_tree_name)) {
1010 throw exceptions::TreeDocumentError(
1011 "Cannot merge tree document: The tree resource '" + std::string(resource_identity) +
"' refers to tree '" +
1012 cherry_pick_tree_name +
"' which is not defined in file '" + absolute_path +
"'.");
1017 std::set<std::string> required_tree_names;
1018 std::vector<std::string> pending{cherry_pick_tree_name};
1019 while (!pending.empty()) {
1020 const std::string current = pending.back();
1022 if (!work_doc.hasTreeName(current))
continue;
1023 if (!required_tree_names.insert(current).second)
continue;
1024 std::function<void(
const XMLElement *)> collect_subtree_refs = [&](
const XMLElement * ele) {
1025 for (
const XMLElement * child = ele->FirstChildElement(); child !=
nullptr;
1026 child = child->NextSiblingElement()) {
1027 if (strcmp(child->Name(), SUBTREE_ELEMENT_NAME) == 0) {
1028 if (
const char * ref = child->Attribute(TREE_NAME_ATTRIBUTE_NAME)) pending.push_back(ref);
1030 collect_subtree_refs(child);
1033 collect_subtree_refs(work_doc.getXMLElementForTreeWithName(current));
1036 for (
const std::string & name : work_doc.getAllTreeNames()) {
1037 if (required_tree_names.count(name) == 0) work_doc.removeTree(name);
1044 for (
const std::string & name : work_doc.getAllTreeNames()) {
1046 include_doc.hasTreeName(name) &&
1047 include_doc.getTree(name).writeToString() == work_doc.getTree(name).writeToString()) {
1048 work_doc.removeTree(name);
1051 include_doc.mergeTreeDocument(
static_cast<const XMLDocument &
>(work_doc),
false);
1055 check_duplicates(include_doc.getAllTreeNames(),
"included files");
1058 for (
const XMLElement * child = include_doc.RootElement()->FirstChildElement(TREE_ELEMENT_NAME); child !=
nullptr;
1059 child = child->NextSiblingElement(TREE_ELEMENT_NAME)) {
1063 register_inline_nodes_for_tree(child);
1066 RootElement()->InsertEndChild(child->DeepClone(
this));
1070 check_duplicates(other_tree_names,
"the merged document");
1073 for (
const XMLElement * child = other_root->FirstChildElement(TREE_ELEMENT_NAME); child !=
nullptr;
1074 child = child->NextSiblingElement(TREE_ELEMENT_NAME)) {
1075 verify_tree_structure(child);
1077 register_inline_nodes_for_tree(child);
1080 RootElement()->InsertEndChild(child->DeepClone(
this));
1083 if (adopt_root_tree) {
1086 if (
const char * name = other_root->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
setRootTreeName(name);
1088 }
else if (strcmp(other_root->Name(), TREE_ELEMENT_NAME) == 0) {
1091 verify_tree_structure(other_root);
1094 check_duplicates(other_tree_names,
"the merged document");
1096 register_inline_nodes_for_tree(other_root);
1099 RootElement()->InsertEndChild(other_root->DeepClone(
this));
1101 throw exceptions::TreeDocumentError(
1102 "Cannot merge tree document: Root element of other document must either be <" + std::string(ROOT_ELEMENT_NAME) +
1103 "> or <" + TREE_ELEMENT_NAME +
">.");
1106 if (adopt_root_tree && other_tree_names.size() == 1) {
1117 std::set<std::string> include_stack;
1118 return mergeTreeDocumentImpl(other, adopt_root_tree, include_stack);
1122 const TreeDocument & other,
bool adopt_root_tree,
bool auto_register_nodes)
1124 if (auto_register_nodes) {
1130 std::set<std::string> include_stack;
1131 return mergeTreeDocumentImpl(
static_cast<const XMLDocument &
>(other), adopt_root_tree, include_stack);
1136 XMLDocument other_doc;
1137 if (other_doc.Parse(tree_str.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
1138 throw exceptions::TreeDocumentError(
"Cannot merge tree document from string: " + std::string(other_doc.ErrorStr()));
1140 std::set<std::string> include_stack;
1141 return mergeTreeDocumentImpl(other_doc, adopt_root_tree, include_stack);
1146 std::set<std::string> include_stack;
1147 return mergeFileImpl(path, adopt_root_tree, include_stack);
1151 const std::string & path,
bool adopt_root_tree, std::set<std::string> & include_stack)
1154 std::string canonical_path;
1156 canonical_path = std::filesystem::canonical(path).string();
1157 }
catch (
const std::filesystem::filesystem_error &) {
1160 canonical_path = path;
1164 if (include_stack.count(canonical_path) > 0) {
1165 throw exceptions::TreeDocumentError(
1166 "Cannot merge tree document: Circular include detected for file '" + path +
"'.");
1170 include_stack.insert(canonical_path);
1172 XMLDocument other_doc;
1173 if (other_doc.LoadFile(path.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
1174 throw exceptions::TreeDocumentError(
"Cannot create tree document from file " + path +
": " + other_doc.ErrorStr());
1177 mergeTreeDocumentImpl(other_doc, adopt_root_tree, include_stack);
1180 include_stack.erase(canonical_path);
1185 const TreeResource & resource,
bool adopt_root_tree,
bool auto_register_nodes)
1187 if (auto_register_nodes) {
1193 return mergeFile(resource.build_request_file_path_, adopt_root_tree);
1198 XMLDocument tree_doc;
1199 tree_doc.InsertEndChild(tree.
ele_ptr_->DeepClone(&tree_doc));
1200 if (auto_register_nodes) {
1212 if (tree_name.empty()) {
1213 throw exceptions::TreeDocumentError(
"Cannot create a new tree with an empty name");
1216 throw exceptions::TreeDocumentError(
1217 "Cannot create a new tree with name '" + tree_name +
"' because it already exists.");
1219 TreeDocument::XMLElement * new_ele = RootElement()->InsertNewChildElement(TreeDocument::TREE_ELEMENT_NAME);
1220 new_ele->SetAttribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
1230 const TreeDocument & other,
const std::string & tree_name,
bool auto_register_nodes)
1232 std::string name(tree_name);
1236 }
else if (
const std::vector<std::string> names = other.
getAllTreeNames(); names.size() == 1) {
1239 throw exceptions::TreeDocumentError(
1240 "Failed to create new tree element from another document because argument tree_name was omitted and it was not "
1241 "possible to determine the root tree automatically.");
1251 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
1258 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
1264 const TreeResource & resource,
const std::string & tree_name,
bool auto_register_nodes)
1266 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
1267 new_doc.
mergeFile(resource.build_request_file_path_);
1269 if (auto_register_nodes) {
1286 return TreeElement(
this, getXMLElementForTreeWithName(tree_name));
1291 if (tree_name.empty()) {
1292 throw exceptions::TreeDocumentError(
"Cannot set root tree name with empty string.");
1295 throw exceptions::TreeDocumentError(
1296 "Cannot make tree with name '" + tree_name +
"' the root tree because it doesn't exist.");
1298 RootElement()->SetAttribute(ROOT_TREE_ATTRIBUTE_NAME, tree_name.c_str());
1304 if (RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
return true;
1310 if (
const auto tree_name = RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME))
return tree_name;
1311 throw exceptions::TreeDocumentError(
1312 "Cannot get root tree name because the document's root element has no attribute '" +
1313 std::string(ROOT_TREE_ATTRIBUTE_NAME) +
"'.");
1320 RootElement()->DeleteChild(getXMLElementForTreeWithName(tree_name));
1331 std::set<std::string> old_node_names;
1332 for (
const auto & [name, _] : registered_nodes_manifest_.map()) {
1333 old_node_names.insert(name);
1340 auto rename_recursive = [&old_node_names, &node_namespace, &sep](XMLElement * parent,
auto & self) ->
void {
1341 for (XMLElement * child = parent->FirstChildElement(); child !=
nullptr; child = child->NextSiblingElement()) {
1342 const char * name = child->Name();
1344 if (name && old_node_names.count(name) > 0) {
1345 child->SetName((node_namespace + sep + name).c_str());
1352 for (XMLElement * tree_ele = RootElement()->FirstChildElement(TREE_ELEMENT_NAME); tree_ele !=
nullptr;
1353 tree_ele = tree_ele->NextSiblingElement(TREE_ELEMENT_NAME)) {
1354 rename_recursive(tree_ele, rename_recursive);
1361 std::vector<std::pair<std::string, std::pair<BT::TreeNodeManifest, BT::NodeBuilder>>> registrations_to_update;
1362 const auto & factory_manifests = factory_.manifests();
1363 const auto & factory_builders = factory_.builders();
1364 for (
const auto & old_name : old_node_names) {
1365 const auto manifest_it = factory_manifests.find(old_name);
1366 const auto builder_it = factory_builders.find(old_name);
1367 if (manifest_it == factory_manifests.end() || builder_it == factory_builders.end()) {
1369 throw exceptions::TreeDocumentError(
1370 "Internal error while applying node namespace: Node '" + old_name +
1371 "' not found in BehaviorTreeFactory registrations.");
1374 registrations_to_update.emplace_back(old_name, std::make_pair(manifest_it->second, builder_it->second));
1378 for (
const auto & [old_name, manifest_builder_pair] : registrations_to_update) {
1379 factory_.unregisterBuilder(old_name);
1380 BT::TreeNodeManifest new_manifest = manifest_builder_pair.first;
1381 new_manifest.registration_ID = node_namespace + sep + old_name;
1382 factory_.registerBuilder(new_manifest, manifest_builder_pair.second);
1391 std::set<std::string> all_registration_names;
1392 for (
const auto & [name, _] : node_manifest.
map()) all_registration_names.insert(name);
1393 if (
const std::set<std::string> common =
1396 throw exceptions::TreeDocumentError(
1397 "Found reserved node registration names in the node manifest. The following names are not allowed, because "
1398 "they refer to native behavior tree nodes: [ " +
1399 auto_apms_util::join(std::vector<std::string>(common.begin(), common.end()),
", ") +
" ].");
1402 for (
const auto & [node_name, params] : node_manifest.
map()) {
1403 if (registered_nodes_manifest_.contains(node_name)) {
1406 factory_.unregisterBuilder(node_name);
1407 registered_nodes_manifest_.remove(node_name);
1411 if (registered_nodes_manifest_[node_name] == params) {
1414 throw exceptions::TreeDocumentError(
1415 "Tried to register node '" + node_name +
"' (Class: " + params.class_name +
1416 ") which is already registered with different options. You must make sure that the registration names are "
1417 "unique, or that re-registrations use the same options, or explicitly allow overriding previously registered "
1418 "nodes with the same name by setting override=true.");
1423 if (!tree_node_loader_ptr_->isClassAvailable(params.class_name)) {
1424 if (all_node_classes_package_map_.find(params.class_name) == all_node_classes_package_map_.end()) {
1425 throw exceptions::TreeDocumentError(
1426 "Node '" + node_name +
" (" + params.class_name +
1427 ")' cannot be registered, because the class name is not known to the class loader. "
1428 "Make sure that it's spelled correctly and registered by calling "
1429 "auto_apms_behavior_tree_register_nodes() in the CMakeLists.txt of the "
1430 "corresponding package.");
1432 throw exceptions::TreeDocumentError(
1433 "Node '" + node_name +
" (" + params.class_name +
1434 ")' cannot be registered, because the corresponding resource belongs to excluded package '" +
1435 all_node_classes_package_map_.at(params.class_name) +
"'.");
1438 pluginlib::UniquePtr<NodeRegistrationInterface> plugin_instance;
1440 plugin_instance = tree_node_loader_ptr_->createUniqueInstance(params.class_name);
1441 }
catch (
const pluginlib::CreateClassException & e) {
1442 throw pluginlib::CreateClassException(
1443 "Failed to create an instance of node '" + node_name +
" (" + params.class_name +
1444 ")'. Remember that the AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE "
1445 "macro must be called in the source file for the node class to be discoverable. "
1451 if (plugin_instance->requiresRosNodeContext()) {
1452 if (only_non_ros_nodes_) {
1453 throw exceptions::NodeRegistrationError(
1454 "Node '" + node_name +
1455 "' relies on ROS 2 functionality but this instance only allows to use non-ROS nodes.");
1458 ros_node_wptr_.lock(), tree_node_waitables_callback_group_wptr_.lock(),
1459 tree_node_waitables_executor_wptr_.lock(), params);
1460 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name, &ros_node_context);
1463 RosNodeContext ros_node_context(
nullptr,
nullptr,
nullptr, params);
1464 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name, &ros_node_context);
1466 }
catch (
const std::exception & e) {
1467 throw exceptions::NodeRegistrationError(
1468 "Cannot register node '" + node_name +
" (" + params.class_name +
")': " + e.what() +
".");
1470 registered_nodes_manifest_.add(node_name, params);
1477 std::set<std::string> names;
1478 if (include_native) names = native_node_names_;
1479 for (
const auto & [name, _] : registered_nodes_manifest_.map()) names.insert(name);
1487 std::vector<std::string> tree_names_to_process =
1488 tree_name.empty() ?
getAllTreeNames() : std::vector<std::string>{tree_name};
1489 for (
const std::string & tree_name : tree_names_to_process) {
1490 XMLElement * ptr =
const_cast<XMLElement *
>(getXMLElementForTreeWithName(tree_name));
1499 const std::vector<std::string> names_to_process =
1500 tree_name.empty() ?
getAllTreeNames() : std::vector<std::string>{tree_name};
1501 for (
const std::string & name : names_to_process) {
1510 tinyxml2::XMLElement * model_root = RootElement()->FirstChildElement(TREE_NODE_MODEL_ELEMENT_NAME);
1514 model_root = NewElement(TREE_NODE_MODEL_ELEMENT_NAME);
1515 RootElement()->InsertEndChild(model_root);
1519 for (
const auto & [node_name,
model] : model_map) {
1521 tinyxml2::XMLElement * node_element = NewElement(BT::toStr(
model.type).c_str());
1522 node_element->SetAttribute(
"ID", node_name.c_str());
1525 for (
const auto & port_info :
model.port_infos) {
1526 tinyxml2::XMLElement * port_element =
nullptr;
1529 switch (port_info.port_direction) {
1530 case BT::PortDirection::INPUT:
1531 port_element = NewElement(
"input_port");
1533 case BT::PortDirection::OUTPUT:
1534 port_element = NewElement(
"output_port");
1536 case BT::PortDirection::INOUT:
1537 port_element = NewElement(
"inout_port");
1542 port_element->SetAttribute(
"name", port_info.port_name.c_str());
1544 if (!port_info.port_type.empty()) {
1545 port_element->SetAttribute(
"type", port_info.port_type.c_str());
1548 if (port_info.port_has_default) {
1549 port_element->SetAttribute(
"default", port_info.port_default.c_str());
1553 if (!port_info.port_description.empty()) {
1554 port_element->SetText(port_info.port_description.c_str());
1557 node_element->InsertEndChild(port_element);
1560 model_root->InsertEndChild(node_element);
1568 const tinyxml2::XMLElement * root = doc.RootElement();
1570 throw exceptions::TreeDocumentError(
"Node model document has no root element.");
1572 if (
const char * ver = root->Attribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME)) {
1573 const std::string expected_format = TreeDocument::BTCPP_FORMAT_DEFAULT_VERSION;
1574 if (std::string(ver) != expected_format) {
1575 throw exceptions::TreeDocumentError(
1576 "Cannot parse node model document: Format of model document (" +
1577 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " + ver +
1578 ") doesn't comply with the expected format (" + std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
": " +
1579 expected_format +
").");
1582 throw exceptions::TreeDocumentError(
1583 "Cannot parse node model document: Root element of model document doesn't have required attribute '" +
1584 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) +
"'.");
1586 tinyxml2::XMLElement * model_ele = doc.RootElement()->FirstChildElement(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME);
1588 throw exceptions::TreeDocumentError(
1589 "Element <" + std::string(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME) +
1590 "> doesn't exist in node model document.");
1594 for (tinyxml2::XMLElement * ele = model_ele->FirstChildElement(); ele !=
nullptr; ele = ele->NextSiblingElement()) {
1595 const char * node_name = ele->Attribute(
"ID");
1597 throw exceptions::TreeDocumentError(
1598 "Element '" + std::string(ele->Name()) +
"' in node model document is missing the required attribute 'ID'");
1601 model.type = BT::convertFromString<BT::NodeType>(ele->Name());
1602 for (
const tinyxml2::XMLElement * port_ele = ele->FirstChildElement(); port_ele !=
nullptr;
1603 port_ele = port_ele->NextSiblingElement()) {
1604 const std::string direction = port_ele->Name();
1606 if (direction ==
"input_port") {
1608 }
else if (direction ==
"output_port") {
1610 }
else if (direction ==
"inout_port") {
1613 throw exceptions::TreeDocumentError(
1614 "Unknown port direction in node model for '" + std::string(node_name) +
"': " + direction);
1616 if (
const char * c = port_ele->Attribute(
"name")) {
1619 if (
const char * c = port_ele->Attribute(
"type")) {
1622 if (
const char * c = port_ele->Attribute(
"default")) {
1628 if (
const char * c = port_ele->GetText()) {
1631 model.port_infos.push_back(std::move(port_info));
1637 std::map<std::string, std::vector<std::string>> hidden_ports;
1638 for (
const auto & [node_name, registration_options] : manifest.
map()) {
1640 for (
const std::string & port_name : registration_options.hidden_ports) {
1641 hidden_ports[node_name].push_back(port_name);
1645 for (
const auto & [port_name, _] : registration_options.port_alias) {
1646 hidden_ports[node_name].push_back(port_name);
1651 for (
const auto & [node_name, ports_to_hide] : hidden_ports) {
1652 auto it = model_map.find(node_name);
1653 if (it == model_map.end()) {
1657 model.port_infos.erase(
1659 model.port_infos.begin(),
model.port_infos.end(),
1661 return std::find(ports_to_hide.begin(), ports_to_hide.end(), port_info.port_name) != ports_to_hide.end();
1663 model.port_infos.end());
1672 std::string model_xml;
1674 model_xml = BT::writeTreeNodesModelXML(factory_, include_native);
1675 }
catch (
const std::exception & e) {
1676 throw exceptions::TreeDocumentError(
"Error generating node model XML from factory: " + std::string(e.what()));
1680 tinyxml2::XMLDocument model_doc;
1681 if (model_doc.Parse(model_xml.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
1682 throw exceptions::TreeDocumentError(
1683 "Error parsing the model of the currently registered nodes: " + std::string(model_doc.ErrorStr()));
1687 return getNodeModel(model_doc, registered_nodes_manifest_);
1693 std::unordered_map<std::string, BT::NodeType> registered_nodes;
1694 for (
const auto & [node_name,
model] : model_map) {
1695 registered_nodes[node_name] =
model.type;
1699 }
catch (
const BT::RuntimeError & e) {
1700 return nonstd::make_unexpected(e.what());
1707 tinyxml2::XMLPrinter printer(
nullptr, remove_whitespace);
1709 return printer.CStr();
1715 FILE * f = std::fopen(path.c_str(),
"w");
1717 throw exceptions::TreeDocumentError(
"Failed to open file for writing: " + path);
1719 const std::size_t written = std::fwrite(xml.data(), 1, xml.size(), f);
1720 const int close_err = std::fclose(f);
1721 if (written != xml.size() || close_err != 0) {
1722 throw exceptions::TreeDocumentError(
"Failed to write tree document to file: " + path);
1730 tinyxml2::XMLElement * root_ele = NewElement(TreeDocument::ROOT_ELEMENT_NAME);
1731 root_ele->SetAttribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME, format_version_.c_str());
1732 InsertFirstChild(root_ele);
1735 if (unregister_nodes) {
1737 factory_.unregisterBuilder(node_name);
1744const TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(
const std::string & tree_name)
const
1746 return getXMLElementForTreeWithNameImpl<const XMLElement *>(*
this, tree_name);
1749TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(
const std::string & tree_name)
1751 return getXMLElementForTreeWithNameImpl<XMLElement *>(*
this, tree_name);
1754template <
typename ReturnT,
typename DocumentT>
1755ReturnT TreeDocument::getXMLElementForTreeWithNameImpl(DocumentT & doc,
const std::string & tree_name)
1757 if (tree_name.empty()) {
1758 throw exceptions::TreeDocumentError(
"Cannot get tree with an empty name.");
1760 if (!doc.hasTreeName(tree_name)) {
1761 throw exceptions::TreeDocumentError(
"Cannot get tree with name '" + tree_name +
"' because it doesn't exist.");
1763 auto child = doc.RootElement()->FirstChildElement(TreeDocument::TREE_ELEMENT_NAME);
1764 while (child && !child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str())) {
1765 child = child->NextSiblingElement();
1768 throw std::logic_error(
1769 "Unexpected error trying to get tree element with name '" + tree_name +
1770 "'. Since hasTreeName() returned true, there MUST be a corresponding element.");
const NodeManifest & getNodeManifest() const
Get the node manifest associated with this resource.
Data structure for information about which behavior tree node plugin to load and how to configure the...
NodeManifest & add(const std::string &node_name, const RegistrationOptions &opt)
Add registration options for a behavior tree node to the manifest.
bool contains(const std::string &node_name) const
Determine if a behavior tree node has been added to the manifest.
NodeManifest & merge(const NodeManifest &other, bool replace=false)
Merges another NodeManifest with this one.
const Map & map() const
Get a view of the internal map.
A pluginlib::ClassLoader specifically for loading installed behavior tree node plugins.
Additional parameters specific to ROS 2 determined at runtime by TreeBuilder.
Class that encapsulates behavior tree script expressions.
std::string str() const
Concatenate all expressions of this instance to a single string.
Forward iterator for traversing the first-level children of a node.
ChildIterator & operator++()
Pre-increment: advance to the next sibling element.
ChildIterator()
Create a past-the-end iterator.
value_type operator*() const
Dereference the iterator to obtain a NodeElement handle for the current child.
Handle for a single node of a TreeDocument.
const TreeDocument & getParentDocument() const
Get a const view of this node's parent tree document.
const std::vector< std::string > & getPortNames() const
Get the names of the data ports implemented by the node represented by this element.
virtual std::string getRegistrationName() const
Get the name of this node given during registration representing its dynamic type.
XMLElement * getXMLElement()
Get a pointer to the underlying tinyxml2::XMLElement of this node.
std::function< bool(const NodeElement &)> ConstDeepApplyCallback
Callback invoked for every node found under another node. It cannot modify the current node.
NodeElement & resetPorts()
Delete all currently specified port values and reset with the defaults.
std::function< bool(NodeElement &)> DeepApplyCallback
Callback invoked for every node found under another node. It may modify the current node.
NodeElement & setPorts(const PortValues &port_values)
Populate the the node's data ports.
virtual NodeElement & setName(const std::string &instance_name)
Assign a name for this specific node instance.
NodeElement insertTreeFromResource(const TreeResource &resource, const std::string &tree_name, const NodeElement *before_this=nullptr, bool auto_register_nodes=true)
Concatenate a tree from one of the installed package's behavior tree resources and add its first chil...
NodeElement(TreeDocument *doc_ptr, XMLElement *ele_ptr)
Protected constructor intended for internal use only.
NodeElement insertTreeFromDocument(const TreeDocument &doc, const std::string &tree_name, const NodeElement *before_this=nullptr, bool auto_register_nodes=true)
Concatenate a tree from a document and add its first child node to the children of this node.
std::map< std::string, std::string > PortValues
Mapping of port names and its respective value encoded as string.
const std::vector< NodeElement > deepApplyConst(ConstDeepApplyCallback apply_callback) const
Recursively apply a callback to this node's children.
TreeDocument * doc_ptr_
Pointer to the tree document that created the tree this node belongs to.
NodeElement & setInlineRegistrationOptions()
Encode the registration options for this node element based on the currently registered node manifest...
ChildIterator end() const
Get a past-the-end iterator for the children of this node.
virtual std::string getName() const
Get the name of this node given to this specific instance by the developer.
PortValues getPorts() const
Assemble the values given to each data port implemented by this node.
NodeElement & setConditionalScript(BT::PreCond type, const Script &script)
Specify a script that is evaluated before this node is ticked.
NodeElement insertTreeFromString(const std::string &tree_str, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from a document created from a string and add its first child node to the children...
tinyxml2::XMLElement * ele_ptr_
Pointer to the corresponding XMLElement of the base document.
std::string getFullyQualifiedName() const
Create a string that uniquely identifies this node considering its registration and its instance name...
model::SubTree insertSubTreeNode(const std::string &tree_name, const NodeElement *before_this=nullptr)
Add a subtree node for a specific tree element to the children of this node.
std::optional< NodeRegistrationOptions > getInlineRegistrationOptions() const
Extract the node registration options by parsing the attributes of this node element.
bool hasChildren() const
Determine whether any children have been given to this node.
NodeElement insertTree(const TreeElement &tree, const NodeElement *before_this=nullptr, bool auto_register_nodes=true)
Concatenate a tree and add its first child node to the children of this node.
bool operator==(const NodeElement &other) const
Determine if two node elements refer to the same node.
NodeElement getFirstNode(const std::string ®istration_name="", const std::string &instance_name="", bool deep_search=false) const
Get the first node with a particular registration and instance name.
ChildIterator begin() const
Get an iterator to the first child of this node.
NodeElement insertNode(const std::string &name, const NodeElement *before_this=nullptr)
Add a new node to the children of this node.
NodeElement & removeFirstChild(const std::string ®istration_name="", const std::string &instance_name="", bool deep_search=false)
Remove the first node with a particular registration and instance name.
NodeElement insertTreeFromFile(const std::string &path, const std::string &tree_name, const NodeElement *before_this=nullptr)
Concatenate a tree from a document created from a file and add its first child node to the children o...
NodeElement & removeChildren()
Recursively remove all children of this node element.
NodeElement & operator=(const NodeElement &other)
Replace this node with another.
std::vector< NodeElement > deepApply(DeepApplyCallback apply_callback)
Recursively apply a callback to this node's children.
NodeElement & setInlineRegistrationOptions(const NodeRegistrationOptions ®istration_options)
Encode the given node registration options as attributes of this node element.
Handle for a single behavior tree of a TreeDocument.
TreeElement & removeFirstChild(const std::string ®istration_name="", const std::string &instance_name="", bool deep_search=false)
TreeElement & removeChildren()
TreeElement(TreeDocument *doc_ptr, XMLElement *ele_ptr)
Protected constructor intended to be used only by certain factory methods of TreeDocument.
std::string getName() const override
Get the name of the behavior tree.
std::string writeToString(bool remove_whitespace=false) const
Write this behavior tree to an XML encoded in a string.
BT::Result verify() const
Verify that this behavior tree is structured correctly and can be created successfully.
NodeManifest getRequiredNodeManifest() const
Assemble the node manifest that is required for successfully creating an instance of this tree.
TreeElement & makeRoot()
Set this behavior tree as the root tree of the parent document.
TreeElement & operator=(const TreeElement &other)
Replace the behavior tree represented by this element with another.
TreeElement & setInlineRegistrationOptions()
Encode the registration options for all nodes in this tree based on the currently registered node man...
TreeElement & setName(const std::string &tree_name) override
Set the name of the behavior tree.
Document Object Model (DOM) for the behavior tree XML schema. This class offers a programmatic approa...
TreeElement getRootTree()
Get the corresponding behavior tree element for the root tree of this document.
std::string getRootTreeName() const
Get the name of this document's root tree.
TreeDocument & mergeTreeDocument(const XMLDocument &other, bool adopt_root_tree=false)
Merge another tree document with this one.
TreeDocument & mergeString(const std::string &tree_str, bool adopt_root_tree=false)
Create a tree document from a string and merge it with this one.
TreeElement newTreeFromFile(const std::string &path, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one found inside the XML file.
TreeDocument & applyNodeNamespace(const std::string &node_namespace, const std::string &sep=_AUTO_APMS_BEHAVIOR_TREE_CORE__NODE_NAMESPACE_DEFAULT_SEP)
Prepend a namespace to all nodes associated with this document.
std::string writeToString(bool remove_whitespace=false) const
Write the XML of this tree document to a string.
BT::Result verify() const
Verify that all behavior trees of this document are structured correctly and can be created successfu...
std::set< std::string > getRegisteredNodeNames(bool include_native=true) const
Get the names of all nodes that are known to this document.
TreeDocument & mergeResource(const TreeResource &resource, bool adopt_root_tree=false, bool auto_register_nodes=true)
Merge the behavior trees from one of the installed package's behavior tree resources.
TreeDocument & reset(bool unregister_nodes=true)
Clear this document and reset it to its initial state.
TreeElement newTreeFromString(const std::string &tree_str, const std::string &tree_name="")
Create a new behavior tree inside this document with the content of one found inside the XML string.
void writeToFile(const std::string &path, bool remove_whitespace=false) const
Write the XML of this tree document to a file.
virtual TreeDocument & registerNodes(const NodeManifest &node_manifest, bool override=false)
Load behavior tree node plugins and register them with the internal behavior tree factory.
TreeElement newTreeFromDocument(const TreeDocument &other, const std::string &tree_name="", bool auto_register_nodes=true)
Create a new behavior tree inside this document with the content of one found inside another tree doc...
TreeElement getTree(const std::string &tree_name)
Get the corresponding behavior tree element for a tree inside this document.
TreeDocument & setInlineRegistrationOptions(const std::string &tree_name="")
Encode the registration options for all nodes in this document based on the currently registered node...
TreeDocument & setRootTreeName(const std::string &tree_name)
Define the root tree of this document.
bool hasTreeName(const std::string &tree_name) const
Determine if this document contains a behavior tree with a particular name.
TreeDocument & mergeFile(const std::string &path, bool adopt_root_tree=false)
Create a tree document from a file and merge it with this one.
NodeManifest getRequiredNodeManifest(const std::string &tree_name="") const
Assemble the node manifest that is required for successfully creating an instance of any of the docum...
std::vector< std::string > getAllTreeNames() const
Get the names of all behavior trees inside this document.
TreeDocument & addNodeModel(NodeModelMap model_map)
Add a behavior tree node model element to the document by parsing the contents of model_map.
TreeElement newTreeFromResource(const TreeResource &resource, const std::string &tree_name="", bool auto_register_nodes=true)
Create a new behavior tree inside this document with the content of one the trees found inside a part...
TreeDocument & mergeTree(const TreeElement &tree, bool make_root_tree=false, bool auto_register_nodes=true)
Merge an existing behavior tree with this tree document.
bool hasRootTreeName() const
Determine if this document specifies which of its trees is the root tree.
TreeDocument & removeTree(const std::string &tree_name)
Remove a particular behavior tree from this document.
TreeDocument(const std::string &format_version=BTCPP_FORMAT_DEFAULT_VERSION, NodeRegistrationLoader::SharedPtr tree_node_loader=NodeRegistrationLoader::make_shared())
Create a an empty tree document.
static NodeModelMap getNodeModel(tinyxml2::XMLDocument &doc, const NodeManifest &manifest)
Convert a behavior tree node model document to the corresponding data structure.
TreeElement newTree(const std::string &tree_name)
Create a new behavior tree inside this document.
Class containing behavior tree resource data.
std::string getRootTreeName() const
Get the name of the root tree of this behavior tree resource.
bool hasRootTreeName() const
Determine if this behavior tree resource specifies a root tree.
Subtree behavior tree node model.
bool contains(const ContainerT< ValueT, AllocatorT > &c, const ValueT &val)
Check whether a particular container structure contains a value.
std::set< KeyT, CompareT, AllocatorT > getCommonElements(std::set< KeyT, CompareT, AllocatorT > c1, std::set< KeyT, CompareT, AllocatorT > c2)
Assemble common elements of two sets.
Core API for AutoAPMS's behavior tree implementation.
Models for all available behavior tree nodes.
Powerful tooling for incorporating behavior trees for task development.
std::map< std::string, NodeModel > NodeModelMap
Mapping of node registration names and their implementation details.
Data structure encapsulating the information of all ports implemented by a behavior tree node.
Implementation details of a single data port.
std::string port_default
Default value of the port encoded as string.
bool port_has_default
Flag whether the port implements a default value or not.
std::string port_description
Description of the port.
BT::PortDirection port_direction
Direction of the port.
std::string port_type
String representation of the C++ type given to the port.
std::string port_name
Name of the port.
Parameters for loading and registering a behavior tree node class from a shared library using e....
bool valid() const
Verify that the options are valid (e.g. all required values are set).