AutoAPMS
Streamlining behaviors in ROS 2
Loading...
Searching...
No Matches
tree_document.cpp
1// Copyright 2024 Robin Müller
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "auto_apms_behavior_tree_core/tree/tree_document.hpp"
16
17#include <algorithm>
18#include <cstdio>
19#include <filesystem>
20
21#include "ament_index_cpp/get_package_share_directory.hpp"
22#include "auto_apms_behavior_tree_core/builder.hpp"
23#include "auto_apms_behavior_tree_core/exceptions.hpp"
24#include "auto_apms_behavior_tree_core/node/node_model_type.hpp"
25#include "auto_apms_behavior_tree_core/tree/tree_resource.hpp"
26#include "auto_apms_util/container.hpp"
27#include "auto_apms_util/logging.hpp"
28#include "auto_apms_util/string.hpp"
29#include "behaviortree_cpp/xml_parsing.h"
30
32{
33
34std::vector<std::string> getAllTreeNamesImpl(const tinyxml2::XMLDocument & doc)
35{
36 std::vector<std::string> names;
37 if (const tinyxml2::XMLElement * root = doc.RootElement()) {
38 if (strcmp(root->Name(), TreeDocument::ROOT_ELEMENT_NAME) == 0) {
39 // Found root element
40 for (const tinyxml2::XMLElement * child = root->FirstChildElement(); child != nullptr;
41 child = child->NextSiblingElement()) {
42 if (strcmp(TreeDocument::TREE_ELEMENT_NAME, child->Name()) == 0) {
43 if (const char * name = child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) {
44 names.push_back(name);
45 } else {
46 throw exceptions::TreeDocumentError(
47 "Cannot get tree name, because required attribute '" +
48 std::string(TreeDocument::TREE_NAME_ATTRIBUTE_NAME) + "' is missing.");
49 }
50 }
51 }
52 } else if (strcmp(root->Name(), TreeDocument::TREE_ELEMENT_NAME) == 0) {
53 // Found behavior tree element as root
54 if (const char * name = root->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME)) names.push_back(name);
55 }
56 }
57 return names;
58}
59
61: doc_ptr_(doc_ptr), ele_ptr_(ele_ptr)
62{
63 if (!doc_ptr) {
64 throw exceptions::TreeDocumentError("Cannot create an instance of NodeElement with doc_ptr=nullptr.");
65 }
66 if (!ele_ptr) {
67 throw exceptions::TreeDocumentError("Cannot create an instance of NodeElement with ele_ptr=nullptr.");
68 }
69 const NodeModelMap model = doc_ptr_->getNodeModel(true);
70 NodeModelMap::const_iterator it = model.find(ele_ptr_->Name());
71 std::vector<NodePortInfo> port_infos_str_vec;
72 if (it != model.end()) port_infos_str_vec = it->second.port_infos;
73
74 // There should always be a corresponding element in the nodes model. However, if the constructor is called e.g.
75 // before the document registered the respective node, that's not the case and setPorts() won't work as expected.
76 // Therefore, we must ensure that all factory methods for a NodeElement verify that the node is known to the document
77 // before creating an instance. NOTE: We must not throw, since the constructor must also succeed for the derived
78 // TreeElement class for which there is no node model and setPorts() is deleted anyways.
79 if (!port_infos_str_vec.empty()) {
80 for (const NodePortInfo & port_info : port_infos_str_vec) {
81 port_names_.push_back(port_info.port_name);
82 if (!port_info.port_default.empty()) port_default_values_[port_info.port_name] = port_info.port_default;
83 }
84
85 // Set the node's attributes according to the default values for all the ports that have a default value and have
86 // not been set previously.
87 PortValues existing_port_values = getPorts();
88 for (const auto & [name, val] : port_default_values_) {
89 if (existing_port_values.find(name) == existing_port_values.end()) {
90 ele_ptr_->SetAttribute(name.c_str(), val.c_str());
91 }
92 }
93 }
94}
95
97{
98 XMLElement * other_ele = other.ele_ptr_;
99 if (ele_ptr_ != other_ele) {
100 // Copy the other element and all its children
101 other_ele = other_ele->DeepClone(doc_ptr_)->ToElement();
102 // Insert new element in place of this
103 XMLElement * prev = ele_ptr_->PreviousSiblingElement();
104 if (prev) {
105 ele_ptr_->Parent()->InsertAfterChild(ele_ptr_->PreviousSibling(), other_ele);
106 } else {
107 ele_ptr_->Parent()->InsertFirstChild(other_ele);
108 }
109 // Delete this element
110 doc_ptr_->DeleteNode(ele_ptr_);
111 // Update the internal pointer
112 ele_ptr_ = other_ele;
113 }
114 port_names_ = other.port_names_;
115 port_default_values_ = other.port_default_values_;
116 return *this;
117}
118
119bool TreeDocument::NodeElement::operator==(const NodeElement & other) const { return ele_ptr_ == other.ele_ptr_; }
120
121bool TreeDocument::NodeElement::operator!=(const NodeElement & other) const { return !this->operator==(other); }
122
124 const std::string & name, const NodeElement * before_this)
125{
126 if (const std::set<std::string> names = doc_ptr_->getRegisteredNodeNames(true); names.find(name) == names.end()) {
127 throw exceptions::TreeDocumentError(
128 "Cannot insert unknown node <" + name +
129 ">. Before inserting a new node, the associated document must register the corresponding behavior tree "
130 "node. Consider using a signature of insertNode() that does this automatically.");
131 }
132 XMLElement * ele = doc_ptr_->NewElement(name.c_str());
133 return insertBeforeImpl(before_this, ele);
134}
135
137 const std::string & name, const NodeRegistrationOptions & registration_options, const NodeElement * before_this)
138{
139 // Check if the node name is known. If the user tries to register a node with the same name as one of the native
140 // nodes, we want registerNodes to throw since those names are reserved (Therefore we set include_native to false).
141 if (const std::set<std::string> names = doc_ptr_->getRegisteredNodeNames(false); names.find(name) == names.end()) {
142 doc_ptr_->registerNodes(NodeManifest({{name, registration_options}}));
143 }
144 return insertNode(name, before_this);
145}
146
148 const std::string & tree_name, const NodeElement * before_this)
149{
150 if (!doc_ptr_->hasTreeName(tree_name)) {
151 throw exceptions::TreeDocumentError(
152 "Cannot insert subtree node for tree '" + tree_name + "' because no tree with that name exists.");
153 }
154 NodeElement ele = insertNode(SUBTREE_ELEMENT_NAME, before_this);
155 ele.ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
156 return model::SubTree(ele.doc_ptr_, ele.ele_ptr_);
157}
158
160{
161 const std::string tree_name = tree.getName();
162 if (doc_ptr_->hasTreeName(tree_name)) {
163 // A tree with this name already exists in the document.
164 // If the provided tree element is from THIS document, we can compare directly using pointer equality.
165 // If it's from an external document, we compare the XML content to detect conflicting trees.
166 const TreeElement existing_tree = doc_ptr_->getTree(tree_name);
167 if (tree.doc_ptr_ == doc_ptr_) {
168 // Tree element is from this document - they must be the same tree element
169 if (existing_tree != tree) {
170 throw exceptions::TreeDocumentError(
171 "Cannot insert subtree node using the provided tree element, because another tree with name '" + tree_name +
172 "' already exists.");
173 }
174 } else {
175 // Tree element is from an external document - compare XML content
176 const std::string existing_xml = existing_tree.writeToString();
177 const std::string incoming_xml = tree.writeToString();
178 if (existing_xml != incoming_xml) {
179 throw exceptions::TreeDocumentError(
180 "Cannot insert subtree node using the provided tree element, because another tree with name '" + tree_name +
181 "' already exists with different content.");
182 }
183 }
184 // If we reach here, the trees are equivalent - proceed to insert the subtree node
185 } else {
186 // Add the tree provided as argument to the document
187 doc_ptr_->mergeTree(tree, false);
188 }
189 return insertSubTreeNode(tree_name, before_this);
190}
191
193 const TreeElement & tree, const NodeElement * before_this, bool auto_register_nodes)
194{
195 const XMLElement * root_child = tree.ele_ptr_->FirstChildElement();
196 if (!root_child) {
197 throw exceptions::TreeDocumentError(
198 "Cannot insert tree element '" + tree.getFullyQualifiedName() + "' because it has no child nodes.");
199 }
200 if (root_child->NextSibling()) {
201 throw exceptions::TreeDocumentError(
202 "Cannot insert tree element '" + tree.getFullyQualifiedName() + "' because it has more than one child node.");
203 }
204 return insertTreeFromDocument(*tree.doc_ptr_, tree.getName(), before_this, auto_register_nodes);
205}
206
208 const TreeDocument & doc, const std::string & tree_name, const NodeElement * before_this, bool auto_register_nodes)
209{
210 const std::vector<std::string> other_tree_names = doc.getAllTreeNames();
211 if (other_tree_names.empty()) {
212 throw exceptions::TreeDocumentError(
213 "Cannot insert tree '" + tree_name + "' because document has no <" + TREE_ELEMENT_NAME + "> elements.");
214 }
215 if (!auto_apms_util::contains(other_tree_names, tree_name)) {
216 throw exceptions::TreeDocumentError(
217 "Cannot insert tree '" + tree_name + "' because document doesn't specify a tree with that name.");
218 }
219
220 if (auto_register_nodes) {
221 // We register all associated node plugins beforehand, so that the user doesn't have to do that manually. This
222 // means, that also potentially unused nodes are available and registered with the factory. This seems unnecessary,
223 // but it's very convenient and the performance probably doesn't suffer too much.
224 doc_ptr_->registerNodes(doc.getRequiredNodeManifest(tree_name), false);
225 }
226
227 // List including the target tree name and the names of its dependencies (Trees required by SubTree nodes)
228 std::set<std::string> required_tree_names = {tree_name};
229
230 // Find all subtree nodes and push back the associated tree to required_tree_names.
231 // NOTE: All tree elements within a TreeDocument instance always have exactly one child, so we don't have to verify
232 // that here again
233 TreeDocument temp_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_); // Temporary working document
234 doc.DeepCopy(&temp_doc);
235 const TreeElement other_tree(&temp_doc, temp_doc.getXMLElementForTreeWithName(tree_name));
236 ConstDeepApplyCallback collect_dependency_tree_names;
237 collect_dependency_tree_names = [&required_tree_names, &collect_dependency_tree_names](const NodeElement & ele) {
238 if (ele.getRegistrationName() == SUBTREE_ELEMENT_NAME) {
239 if (const char * name = ele.ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
240 // Add the tree name to the list
241 required_tree_names.insert(name);
242
243 // Search for more tree dependencies under the tree pointed by this subtree node
244 ele.doc_ptr_->getTree(name).deepApplyConst(collect_dependency_tree_names);
245 } else {
246 throw exceptions::TreeDocumentError("Subtree element has no name attribute.");
247 };
248 }
249 // The return value doesn't matter here
250 return false;
251 };
252 other_tree.deepApplyConst(collect_dependency_tree_names);
253
254 // Verify that all child nodes of the trees listed in required_tree_names are known to the builder
255 ConstDeepApplyCallback apply = [available_names = doc_ptr_->getRegisteredNodeNames(true)](const NodeElement & ele) {
256 return available_names.find(ele.getRegistrationName()) == available_names.end();
257 };
258 for (const std::string & name : required_tree_names) {
259 const NodeElement ele(&temp_doc, temp_doc.getXMLElementForTreeWithName(name)->FirstChildElement());
260 if (const std::vector<NodeElement> found = ele.deepApplyConst(apply); !found.empty()) {
261 std::vector<std::string> names;
262 for (const NodeElement & ele : found) names.push_back(ele.getFullyQualifiedName());
263 throw exceptions::TreeDocumentError(
264 "Cannot insert tree '" + tree_name + "' because the following nodes found in tree '" + name +
265 "' are unknown to the builder:\n\t- " + auto_apms_util::join(names, "\n\t- "));
266 }
267 }
268
269 // Remove the data from each tree that the target tree doesn't directly depend on and merge the working document. This
270 // approach ensures, that the verification logic inside merge() is applied to the working document.
271 required_tree_names.erase(tree_name); // Don't add the target tree twice
272 for (const std::string & name : other_tree_names) {
273 if (required_tree_names.find(name) == required_tree_names.end()) {
274 // Remove if not required
275 temp_doc.removeTree(name);
276 }
277 }
278 doc_ptr_->mergeTreeDocument(static_cast<const XMLDocument &>(temp_doc), false);
279
280 // Clone and insert the target tree
281 return insertBeforeImpl(
282 before_this, doc.getXMLElementForTreeWithName(tree_name)->FirstChildElement()->DeepClone(doc_ptr_)->ToElement());
283}
284
286 const TreeDocument & doc, const NodeElement * before_this, bool auto_register_nodes)
287{
288 return insertTreeFromDocument(doc, doc.getRootTreeName(), before_this, auto_register_nodes);
289}
290
292 const std::string & tree_str, const std::string & tree_name, const NodeElement * before_this)
293{
294 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
295 insert_doc.mergeString(tree_str);
296 return insertTreeFromDocument(insert_doc, tree_name, before_this);
297}
298
300 const std::string & tree_str, const NodeElement * before_this)
301{
302 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
303 insert_doc.mergeString(tree_str);
304 return insertTreeFromDocument(insert_doc, before_this);
305}
306
308 const std::string & path, const std::string & tree_name, const NodeElement * before_this)
309{
310 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
311 insert_doc.mergeFile(path);
312 return insertTreeFromDocument(insert_doc, tree_name, before_this);
313}
314
316 const std::string & path, const NodeElement * before_this)
317{
318 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
319 insert_doc.mergeFile(path);
320 return insertTreeFromDocument(insert_doc, before_this);
321}
322
324 const TreeResource & resource, const std::string & tree_name, const NodeElement * before_this,
325 bool auto_register_nodes)
326{
327 TreeDocument insert_doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
328 insert_doc.mergeFile(resource.build_request_file_path_);
329 return insertTreeFromDocument(insert_doc, tree_name, before_this, auto_register_nodes);
330}
331
333 const TreeResource & resource, const NodeElement * before_this, bool auto_register_nodes)
334{
335 return insertTreeFromResource(resource, resource.getRootTreeName(), before_this, auto_register_nodes);
336}
337
338bool TreeDocument::NodeElement::hasChildren() const { return ele_ptr_->FirstChild() == nullptr ? false : true; }
339
341 const std::string & registration_name, const std::string & instance_name, bool deep_search) const
342{
343 if (registration_name.empty() && instance_name.empty()) return NodeElement(doc_ptr_, ele_ptr_->FirstChildElement());
344
345 ConstDeepApplyCallback apply = [&registration_name, &instance_name](const NodeElement & ele) {
346 if (registration_name.empty()) return ele.getName() == instance_name;
347 if (instance_name.empty()) return ele.getRegistrationName() == registration_name;
348 return ele.getRegistrationName() == registration_name && ele.getName() == instance_name;
349 };
350
351 if (deep_search) {
352 if (const std::vector<NodeElement> found = deepApplyConst(apply); !found.empty()) return found[0];
353 } else {
354 for (auto child : *this) {
355 if (apply(child)) return child;
356 }
357 }
358
359 // Cannot find node in children of this
360 throw exceptions::TreeDocumentError(
361 "Cannot find a child node that matches the search arguments (registration_name: '" + registration_name +
362 "' - instance_name: '" + instance_name + "') in parent element '" + getFullyQualifiedName() + "'.");
363}
364
366 const std::string & registration_name, const std::string & instance_name, bool deep_search)
367{
368 XMLElement * found = getFirstNode(registration_name, instance_name, deep_search).ele_ptr_;
369 found->Parent()->DeleteChild(found);
370 return *this;
371}
372
374{
375 ele_ptr_->DeleteChildren();
376 return *this;
377}
378
379const std::vector<std::string> & TreeDocument::NodeElement::getPortNames() const { return port_names_; }
380
382{
383 PortValues values;
384 for (const tinyxml2::XMLAttribute * attr = ele_ptr_->FirstAttribute(); attr != nullptr; attr = attr->Next()) {
385 if (const std::string attr_name = attr->Name(); auto_apms_util::contains(port_names_, attr_name)) {
386 values[attr_name] = attr->Value();
387 }
388 }
389 return values;
390}
391
393{
394 // Verify port_values
395 std::vector<std::string> unknown_keys;
396 for (const auto & [key, _] : port_values) {
397 if (!auto_apms_util::contains(port_names_, key)) unknown_keys.push_back(key);
398 }
399 if (!unknown_keys.empty()) {
400 throw exceptions::TreeDocumentError(
401 "Cannot set ports. According to the node model, the following ports are not implemented by '" +
402 std::string(ele_ptr_->Name()) + "': [ " + auto_apms_util::join(unknown_keys, ", ") + " ].");
403 }
404
405 // Populate attributes according to the content of port_values
406 for (const auto & [key, val] : port_values) {
407 ele_ptr_->SetAttribute(key.c_str(), val.c_str());
408 }
409 return *this;
410}
411
413{
414 for (const std::string & name : port_names_) {
415 PortValues::const_iterator it = port_default_values_.find(name);
416 if (it != port_default_values_.end()) {
417 ele_ptr_->SetAttribute(it->first.c_str(), it->second.c_str());
418 } else {
419 ele_ptr_->DeleteAttribute(name.c_str());
420 }
421 }
422 return *this;
423}
424
426{
427 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.str().c_str());
428 return *this;
429}
430
432{
433 ele_ptr_->SetAttribute(BT::toStr(type).c_str(), script.str().c_str());
434 return *this;
435}
436
438{
439 ele_ptr_->SetAttribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME, instance_name.c_str());
440 return *this;
441}
442
443std::string TreeDocument::NodeElement::getRegistrationName() const { return ele_ptr_->Name(); }
444
446{
447 if (const char * name = ele_ptr_->Attribute(NODE_INSTANCE_NAME_ATTRIBUTE_NAME)) return name;
448 return getRegistrationName();
449}
450
452{
453 std::string registration_name = getRegistrationName();
454 std::string instance_name = getName();
455 if (registration_name == instance_name) return registration_name;
456 return instance_name + " (" + registration_name + ")";
457}
458
460
461TreeDocument::XMLElement * TreeDocument::NodeElement::getXMLElement() { return ele_ptr_; }
462
463const std::vector<TreeDocument::NodeElement> TreeDocument::NodeElement::deepApplyConst(
464 ConstDeepApplyCallback apply_callback) const
465{
466 std::vector<NodeElement> found;
467 deepApplyImpl(*this, apply_callback, found);
468 return found;
469}
470
471std::vector<TreeDocument::NodeElement> TreeDocument::NodeElement::deepApply(DeepApplyCallback apply_callback)
472{
473 std::vector<NodeElement> found;
474 deepApplyImpl(*this, apply_callback, found);
475 return found;
476}
477
478TreeDocument::NodeElement TreeDocument::NodeElement::insertBeforeImpl(
479 const NodeElement * before_this, XMLElement * add_this)
480{
481 if (before_this) {
482 XMLElement * prev = nullptr;
483 XMLElement * curr = ele_ptr_->FirstChildElement();
484
485 // Traverse through siblings of first child to find the before_this node.
486 // If there are no siblings, add the first child to this;
487 bool found = false;
488 while (curr) {
489 if (curr == before_this->ele_ptr_) {
490 found = true;
491 break;
492 }
493 prev = curr;
494 curr = curr->NextSiblingElement();
495 }
496 if (prev) {
497 if (found) {
498 ele_ptr_->InsertAfterChild(prev, add_this);
499 } else {
500 throw exceptions::TreeDocumentError(
501 "NodeElement before_this (" + before_this->getFullyQualifiedName() + ") is not a child of " +
502 getFullyQualifiedName() + ".");
503 }
504 } else {
505 ele_ptr_->InsertFirstChild(add_this);
506 }
507 } else {
508 ele_ptr_->InsertEndChild(add_this);
509 }
510 return NodeElement(doc_ptr_, add_this);
511}
512
513void TreeDocument::NodeElement::deepApplyImpl(
514 const NodeElement & parent, ConstDeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
515{
516 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child != nullptr;
517 child = child->NextSiblingElement()) {
518 const NodeElement child_ele(parent.doc_ptr_, child);
519
520 // Apply on current child element
521 if (apply_callback(child_ele)) vec.push_back(child_ele);
522
523 // Search children of child before evaluating the siblings
524 deepApplyImpl(child_ele, apply_callback, vec);
525 }
526}
527
528void TreeDocument::NodeElement::deepApplyImpl(
529 NodeElement & parent, DeepApplyCallback apply_callback, std::vector<NodeElement> & vec)
530{
531 for (XMLElement * child = parent.ele_ptr_->FirstChildElement(); child != nullptr;
532 child = child->NextSiblingElement()) {
533 NodeElement child_ele(parent.doc_ptr_, child);
534
535 // Apply on current child element
536 if (apply_callback(child_ele)) vec.push_back(child_ele);
537
538 // Search children of child before evaluating the siblings
539 deepApplyImpl(child_ele, apply_callback, vec);
540 }
541}
542
543TreeDocument::NodeElement::ChildIterator::ChildIterator() : doc_ptr_(nullptr), current_(nullptr) {}
544
545TreeDocument::NodeElement::ChildIterator::ChildIterator(TreeDocument * doc_ptr, tinyxml2::XMLElement * current)
546: doc_ptr_(doc_ptr), current_(current)
547{
548}
549
550TreeDocument::NodeElement::ChildIterator::value_type TreeDocument::NodeElement::ChildIterator::operator*() const
551{
552 return NodeElement(doc_ptr_, current_);
553}
554
556{
557 if (current_) current_ = current_->NextSiblingElement();
558 return *this;
559}
560
567
568bool TreeDocument::NodeElement::ChildIterator::operator==(const ChildIterator & other) const
569{
570 return current_ == other.current_;
571}
572
573bool TreeDocument::NodeElement::ChildIterator::operator!=(const ChildIterator & other) const
574{
575 return current_ != other.current_;
576}
577
582
584
585TreeDocument::TreeElement::TreeElement(TreeDocument * doc_ptr, XMLElement * ele_ptr) : NodeElement(doc_ptr, ele_ptr)
586{
587 if (!ele_ptr->Attribute(TREE_NAME_ATTRIBUTE_NAME)) {
588 throw exceptions::TreeDocumentError(
589 "Cannot create tree element without a '" + std::string(TREE_NAME_ATTRIBUTE_NAME) + "' attribute.");
590 }
591}
592
594{
595 const std::string other_tree_name = other.getName();
596 // For a tree replacement to be allowed, the other tree name must be the same or at least not already taken by another
597 // tree inside this document.
598 if (getName() != other_tree_name && doc_ptr_->hasTreeName(other.getName())) {
599 throw exceptions::TreeDocumentError(
600 "Cannot copy tree '" + other.getName() + "' because another tree with this name already exists.");
601 }
603 return *this;
604}
605
607{
608 ele_ptr_->SetAttribute(TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
609 return *this;
610}
611
613{
614 if (const char * name = ele_ptr_->Attribute(TREE_NAME_ATTRIBUTE_NAME)) return name;
615 return "unknown";
616}
617
619{
620 doc_ptr_->setRootTreeName(getName());
621 return *this;
622}
623
625{
626 NodeManifest m;
627 deepApplyConst([this, &m](const NodeElement & node) {
628 const std::string name = node.getRegistrationName();
629 const bool is_native_node = doc_ptr_->native_node_names_.find(name) != doc_ptr_->native_node_names_.end();
630 if (!is_native_node && !m.contains(name)) {
631 if (!doc_ptr_->registered_nodes_manifest_.contains(name)) {
632 throw exceptions::NodeManifestError(
633 "Cannot assemble the required node manifest for tree '" + getName() +
634 "' since there are no registration options for node '" + name + "'.");
635 }
636 m.add(name, doc_ptr_->registered_nodes_manifest_[name]);
637 }
638 return false;
639 });
640 return m;
641}
642
644{
645 TreeDocument doc(doc_ptr_->format_version_, doc_ptr_->tree_node_loader_ptr_);
646 return doc.mergeTree(*this, true).verify();
647}
648
649std::string TreeDocument::TreeElement::writeToString(bool remove_whitespace) const
650{
651 XMLDocument tree_doc;
652 tree_doc.InsertEndChild(ele_ptr_->DeepClone(&tree_doc));
653 tinyxml2::XMLPrinter printer(nullptr, remove_whitespace);
654 tree_doc.Print(&printer);
655 return printer.CStr();
656}
657
659 const std::string & registration_name, const std::string & instance_name, bool deep_search)
660{
661 NodeElement::removeFirstChild(registration_name, instance_name, deep_search);
662 return *this;
663}
664
670
671TreeDocument::TreeDocument(const std::string & format_version, NodeRegistrationLoader::SharedPtr tree_node_loader)
672// It's important to initialize XMLDocument using PRESERVE_WHITESPACE, since encoded port data (like
673// NodeRegistrationOptions) may be sensitive to changes in the whitespaces (think of the YAML format).
674: XMLDocument(true, tinyxml2::PRESERVE_WHITESPACE),
675 all_node_classes_package_map_(auto_apms_behavior_tree::core::NodeRegistrationLoader().getClassPackageMap()),
676 native_node_names_(BT::BehaviorTreeFactory().builtinNodes()),
677 format_version_(format_version),
678 tree_node_loader_ptr_(tree_node_loader),
679 registered_nodes_manifest_(),
680 factory_(),
681 logger_(rclcpp::get_logger(LOGGER_NAME))
682{
683 reset();
684}
685
686TreeDocument & TreeDocument::mergeTreeDocument(const XMLDocument & other, bool adopt_root_tree)
687{
688 std::set<std::string> include_stack;
689 return mergeTreeDocumentImpl(other, adopt_root_tree, include_stack);
690}
691
692TreeDocument & TreeDocument::mergeTreeDocumentImpl(
693 const XMLDocument & other, bool adopt_root_tree, std::set<std::string> & include_stack)
694{
695 const XMLElement * other_root = other.RootElement();
696 if (!other_root) {
697 throw exceptions::TreeDocumentError("Cannot merge tree documents: other_root is nullptr.");
698 };
699
700 auto verify_tree_structure = [](const XMLElement * tree_ele) {
701 const char * tree_id = tree_ele->Attribute(TREE_NAME_ATTRIBUTE_NAME);
702 if (!tree_id) {
703 throw exceptions::TreeDocumentError(
704 "Cannot merge tree document: Found a <" + std::string(TREE_ELEMENT_NAME) +
705 "> element that doesn't specify the required attribute '" + TREE_NAME_ATTRIBUTE_NAME + "'.");
706 }
707 const XMLElement * tree_root_child = tree_ele->FirstChildElement();
708 if (!tree_root_child) {
709 throw exceptions::TreeDocumentError(
710 "Cannot merge tree document: Tree '" + std::string(tree_id) + "' has no child nodes.");
711 }
712 if (tree_root_child->NextSibling()) {
713 throw exceptions::TreeDocumentError(
714 "Cannot merge tree document: Tree '" + std::string(tree_id) + "' has more than one child node.");
715 }
716 };
717
718 // Helper lambda to check for duplicate tree names and throw with a descriptive message
719 auto check_duplicates = [this](const std::vector<std::string> & new_tree_names, const std::string & source) {
720 const auto common = auto_apms_util::getCommonElements(getAllTreeNames(), new_tree_names);
721 if (!common.empty()) {
722 throw exceptions::TreeDocumentError(
723 "Cannot merge tree document: The following trees from " + source + " are already defined: [ " +
724 auto_apms_util::join(common, ", ") + " ].");
725 }
726 };
727
728 // Get tree names from the other document (used for duplicate check and adopt_root_tree logic)
729 const std::vector<std::string> other_tree_names = getAllTreeNamesImpl(other);
730
731 if (strcmp(other_root->Name(), ROOT_ELEMENT_NAME) == 0) {
732 // Verify format
733 if (const char * ver = other_root->Attribute(BTCPP_FORMAT_ATTRIBUTE_NAME)) {
734 if (std::string(ver) != format_version_) {
735 throw exceptions::TreeDocumentError(
736 "Cannot merge tree document: Format of other document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) + ": " +
737 ver + ") is not compatible with this document (" + std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) + ": " +
738 format_version_ + ").");
739 }
740 } else {
741 throw exceptions::TreeDocumentError(
742 "Cannot merge tree document: Root element of other document doesn't have required attribute '" +
743 std::string(BTCPP_FORMAT_ATTRIBUTE_NAME) + "'.");
744 }
745
746 // Iterate over all include elements and store the associated content to a temporary buffer document
747 TreeDocument include_doc(format_version_, tree_node_loader_ptr_);
748 for (const XMLElement * include_ele = other_root->FirstChildElement(INCLUDE_ELEMENT_NAME); include_ele != nullptr;
749 include_ele = include_ele->NextSiblingElement(INCLUDE_ELEMENT_NAME)) {
750 const char * path = include_ele->Attribute(INCLUDE_PATH_ATTRIBUTE_NAME);
751 const char * resource_identity = include_ele->Attribute(INCLUDE_RESOURCE_ATTRIBUTE_NAME);
752
753 if (path && resource_identity) {
754 throw exceptions::TreeDocumentError(
755 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
756 "> element that specifies both the '" + INCLUDE_PATH_ATTRIBUTE_NAME + "' and the '" +
757 INCLUDE_RESOURCE_ATTRIBUTE_NAME + "' attribute. Please specify only one of them.");
758 }
759
760 std::string absolute_path;
761 // For a resource include this holds the single tree to cherry-pick from the file; it stays empty for a path
762 // include, which merges the whole file.
763 std::string cherry_pick_tree_name;
764 if (resource_identity) {
765 // Resolve the include by looking up an installed tree resource by its identity. Unlike a file path, this
766 // decouples the include from any concrete install location: the resource system knows where the associated tree
767 // file lives. The 'ros_pkg' attribute is meaningless here (the package is already part of the identity).
768 if (std::string(resource_identity).empty()) {
769 throw exceptions::TreeDocumentError(
770 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
771 "> element that specifies an empty resource identity in attribute '" + INCLUDE_RESOURCE_ATTRIBUTE_NAME +
772 "'.");
773 }
774 if (include_ele->Attribute(INCLUDE_ROS_PKG_ATTRIBUTE_NAME)) {
775 throw exceptions::TreeDocumentError(
776 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
777 "> element that specifies the '" + INCLUDE_ROS_PKG_ATTRIBUTE_NAME + "' attribute together with '" +
778 INCLUDE_RESOURCE_ATTRIBUTE_NAME + "'. The '" + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
779 "' attribute is only allowed in combination with '" + INCLUDE_PATH_ATTRIBUTE_NAME + "'.");
780 }
781 try {
782 const TreeResource resource{std::string(resource_identity)};
783 absolute_path = resource.build_request_file_path_;
784 // The identity always carries a <tree_name> (a fully qualified tree resource identity requires it), so this
785 // yields the specific tree to cherry-pick rather than parsing the file for a root tree.
786 cherry_pick_tree_name = resource.getRootTreeName();
787 } catch (const std::exception & e) {
788 throw exceptions::TreeDocumentError(
789 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
790 "> element that specifies the tree resource identity '" + std::string(resource_identity) +
791 "' in attribute '" + INCLUDE_RESOURCE_ATTRIBUTE_NAME + "' which cannot be resolved: " + e.what());
792 }
793 } else if (path) {
794 if (std::string(path).empty()) {
795 throw exceptions::TreeDocumentError(
796 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
797 "> element that specifies an empty path in attribute '" + INCLUDE_PATH_ATTRIBUTE_NAME + "'.");
798 }
799 absolute_path = path;
800 if (const char * ros_pkg = include_ele->Attribute(INCLUDE_ROS_PKG_ATTRIBUTE_NAME)) {
801 if (std::string(ros_pkg).empty()) {
802 throw exceptions::TreeDocumentError(
803 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
804 "> element that specifies an empty ROS package name in attribute '" + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
805 "'.");
806 }
807 if (std::filesystem::path(path).is_absolute()) {
808 throw exceptions::TreeDocumentError(
809 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
810 "> element that specifies an absolute path '" + std::string(path) + "' in attribute '" +
811 INCLUDE_PATH_ATTRIBUTE_NAME + "' together with a ROS package name in attribute '" +
812 INCLUDE_ROS_PKG_ATTRIBUTE_NAME + "'. Please remove the attribute " + INCLUDE_ROS_PKG_ATTRIBUTE_NAME +
813 " or use a relative path if you want to refer to a location "
814 "relative to the package's share directory.");
815 }
816 try {
817 absolute_path =
818 (std::filesystem::path(ament_index_cpp::get_package_share_directory(ros_pkg)) / path).string();
819 } catch (const ament_index_cpp::PackageNotFoundError & e) {
820 throw exceptions::TreeDocumentError(
821 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
822 "> element that specifies a non-existing ROS package '" + std::string(ros_pkg) + "' in attribute '" +
823 INCLUDE_ROS_PKG_ATTRIBUTE_NAME + "'.");
824 }
825 }
826 } else {
827 throw exceptions::TreeDocumentError(
828 "Cannot merge tree document: Found an <" + std::string(INCLUDE_ELEMENT_NAME) +
829 "> element that doesn't specify any of the required attributes '" + INCLUDE_PATH_ATTRIBUTE_NAME + "' or '" +
830 INCLUDE_RESOURCE_ATTRIBUTE_NAME + "'.");
831 }
832
833 // Load the included file into a temporary working document (resolving its own nested includes and passing the
834 // include stack for circular detection). Both include kinds go through this same working document; a resource
835 // include additionally narrows it down to a single tree below.
836 TreeDocument work_doc(format_version_, tree_node_loader_ptr_);
837 try {
838 work_doc.mergeFileImpl(absolute_path, false, include_stack);
839 } catch (const std::exception & e) {
840 throw exceptions::TreeDocumentError(
841 "Cannot merge tree document: Failed to include file '" + absolute_path + "': " + e.what());
842 }
843
844 // For a resource include, cherry-pick: reduce the working document to the referenced tree and the trees it
845 // transitively depends on via <SubTree> nodes. A path include keeps the whole file (empty cherry_pick_tree_name).
846 if (!cherry_pick_tree_name.empty()) {
847 if (!work_doc.hasTreeName(cherry_pick_tree_name)) {
848 throw exceptions::TreeDocumentError(
849 "Cannot merge tree document: The tree resource '" + std::string(resource_identity) + "' refers to tree '" +
850 cherry_pick_tree_name + "' which is not defined in file '" + absolute_path + "'.");
851 }
852
853 // Compute the dependency closure of the referenced tree. References to trees not defined in this file are left
854 // out, so they can be resolved by the including document or another include.
855 std::set<std::string> required_tree_names;
856 std::vector<std::string> pending{cherry_pick_tree_name};
857 while (!pending.empty()) {
858 const std::string current = pending.back();
859 pending.pop_back();
860 if (!work_doc.hasTreeName(current)) continue; // resolved elsewhere
861 if (!required_tree_names.insert(current).second) continue; // already visited
862 std::function<void(const XMLElement *)> collect_subtree_refs = [&](const XMLElement * ele) {
863 for (const XMLElement * child = ele->FirstChildElement(); child != nullptr;
864 child = child->NextSiblingElement()) {
865 if (strcmp(child->Name(), SUBTREE_ELEMENT_NAME) == 0) {
866 if (const char * ref = child->Attribute(TREE_NAME_ATTRIBUTE_NAME)) pending.push_back(ref);
867 }
868 collect_subtree_refs(child);
869 }
870 };
871 collect_subtree_refs(work_doc.getXMLElementForTreeWithName(current));
872 }
873
874 for (const std::string & name : work_doc.getAllTreeNames()) {
875 if (required_tree_names.count(name) == 0) work_doc.removeTree(name);
876 }
877 }
878
879 // Merge the working document into the buffer. A tree that already exists in the buffer with identical content is
880 // dropped beforehand (this lets several includes share a common helper subtree - e.g. two behaviors reusing the
881 // same subtree); a same-named tree with differing content is left in place so the merge below reports the clash.
882 for (const std::string & name : work_doc.getAllTreeNames()) {
883 if (
884 include_doc.hasTreeName(name) &&
885 include_doc.getTree(name).writeToString() == work_doc.getTree(name).writeToString()) {
886 work_doc.removeTree(name);
887 }
888 }
889 include_doc.mergeTreeDocument(static_cast<const XMLDocument &>(work_doc), false);
890 }
891
892 // Check for duplicates from included files before inserting
893 check_duplicates(include_doc.getAllTreeNames(), "included files");
894
895 // Iterate over all the tree elements of the include buffer and include them in this document
896 for (const XMLElement * child = include_doc.RootElement()->FirstChildElement(TREE_ELEMENT_NAME); child != nullptr;
897 child = child->NextSiblingElement(TREE_ELEMENT_NAME)) {
898 // We do not need to verify the tree structure here, since it has already been verified during the recursive
899 // mergeFile() calls by the for loop below
900
901 // If tree element is valid, append to this document
902 RootElement()->InsertEndChild(child->DeepClone(this));
903 }
904
905 // Check for duplicates from other document's direct trees before inserting
906 check_duplicates(other_tree_names, "the merged document");
907
908 // Iterate over all the tree elements of other root and verify them
909 for (const XMLElement * child = other_root->FirstChildElement(TREE_ELEMENT_NAME); child != nullptr;
910 child = child->NextSiblingElement(TREE_ELEMENT_NAME)) {
911 verify_tree_structure(child);
912
913 // If tree element is valid, append to this document
914 RootElement()->InsertEndChild(child->DeepClone(this));
915 }
916
917 if (adopt_root_tree) {
918 // After (!) all tree elements have been inserted, adopt the name specified in the corresponding attribute of the
919 // root element
920 if (const char * name = other_root->Attribute(ROOT_TREE_ATTRIBUTE_NAME)) setRootTreeName(name);
921 }
922 } else if (strcmp(other_root->Name(), TREE_ELEMENT_NAME) == 0) {
923 // Allow a single behavior tree without <root> element.
924 // We assume that the the format complies with the version configured at construction time
925 verify_tree_structure(other_root);
926
927 // Check for duplicates before inserting
928 check_duplicates(other_tree_names, "the merged document");
929
930 // If tree element is valid, append to this document
931 RootElement()->InsertEndChild(other_root->DeepClone(this));
932 } else {
933 throw exceptions::TreeDocumentError(
934 "Cannot merge tree document: Root element of other document must either be <" + std::string(ROOT_ELEMENT_NAME) +
935 "> or <" + TREE_ELEMENT_NAME + ">.");
936 }
937
938 if (adopt_root_tree && other_tree_names.size() == 1) {
939 // If there is only one tree element, we change the root tree to the respective tree after (!) all tree elements
940 // have been inserted
941 setRootTreeName(other_tree_names[0]);
942 }
943
944 return *this;
945}
946
948 const TreeDocument & other, bool adopt_root_tree, bool auto_register_nodes)
949{
950 if (auto_register_nodes) {
951 // We register all associated node plugins beforehand, so that the user doesn't have to do that manually. This
952 // means, that also potentially unused nodes are available and registered with the factory. This seems unnecessary,
953 // but it's very convenient and the performance probably doesn't suffer too much.
955 }
956 std::set<std::string> include_stack;
957 return mergeTreeDocumentImpl(static_cast<const XMLDocument &>(other), adopt_root_tree, include_stack);
958}
959
960TreeDocument & TreeDocument::mergeString(const std::string & tree_str, bool adopt_root_tree)
961{
962 XMLDocument other_doc;
963 if (other_doc.Parse(tree_str.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
964 throw exceptions::TreeDocumentError("Cannot merge tree document from string: " + std::string(other_doc.ErrorStr()));
965 }
966 std::set<std::string> include_stack;
967 return mergeTreeDocumentImpl(other_doc, adopt_root_tree, include_stack);
968}
969
970TreeDocument & TreeDocument::mergeFile(const std::string & path, bool adopt_root_tree)
971{
972 std::set<std::string> include_stack;
973 return mergeFileImpl(path, adopt_root_tree, include_stack);
974}
975
976TreeDocument & TreeDocument::mergeFileImpl(
977 const std::string & path, bool adopt_root_tree, std::set<std::string> & include_stack)
978{
979 // Get canonical path for consistent circular include detection
980 std::string canonical_path;
981 try {
982 canonical_path = std::filesystem::canonical(path).string();
983 } catch (const std::filesystem::filesystem_error &) {
984 // If canonical fails (e.g., file doesn't exist), use the original path
985 // The error will be caught later when loading the file
986 canonical_path = path;
987 }
988
989 // Check for circular includes
990 if (include_stack.count(canonical_path) > 0) {
991 throw exceptions::TreeDocumentError(
992 "Cannot merge tree document: Circular include detected for file '" + path + "'.");
993 }
994
995 // Add to include stack
996 include_stack.insert(canonical_path);
997
998 XMLDocument other_doc;
999 if (other_doc.LoadFile(path.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
1000 throw exceptions::TreeDocumentError("Cannot create tree document from file " + path + ": " + other_doc.ErrorStr());
1001 }
1002
1003 mergeTreeDocumentImpl(other_doc, adopt_root_tree, include_stack);
1004
1005 // Remove from include stack (for recursive calls that continue after this one)
1006 include_stack.erase(canonical_path);
1007 return *this;
1008}
1009
1011 const TreeResource & resource, bool adopt_root_tree, bool auto_register_nodes)
1012{
1013 if (auto_register_nodes) {
1014 // We register all associated node plugins beforehand, so that the user doesn't have to do that manually. This
1015 // means, that also potentially unused nodes are available and registered with the factory. This seems unnecessary,
1016 // but it's very convenient and the performance probably doesn't suffer too much.
1017 registerNodes(resource.getNodeManifest(), false);
1018 }
1019 return mergeFile(resource.build_request_file_path_, adopt_root_tree);
1020}
1021
1022TreeDocument & TreeDocument::mergeTree(const TreeElement & tree, bool make_root_tree, bool auto_register_nodes)
1023{
1024 XMLDocument tree_doc;
1025 tree_doc.InsertEndChild(tree.ele_ptr_->DeepClone(&tree_doc));
1026 if (auto_register_nodes) {
1027 // We register all associated node plugins beforehand, so that the user doesn't have to do that manually. This
1028 // means, that also potentially unused nodes are available and registered with the factory. This seems unnecessary,
1029 // but it's very convenient and the performance probably doesn't suffer too much.
1031 }
1032 mergeTreeDocument(tree_doc, make_root_tree);
1033 return *this;
1034}
1035
1037{
1038 if (tree_name.empty()) {
1039 throw exceptions::TreeDocumentError("Cannot create a new tree with an empty name");
1040 }
1041 if (hasTreeName(tree_name)) {
1042 throw exceptions::TreeDocumentError(
1043 "Cannot create a new tree with name '" + tree_name + "' because it already exists.");
1044 }
1045 TreeDocument::XMLElement * new_ele = RootElement()->InsertNewChildElement(TreeDocument::TREE_ELEMENT_NAME);
1046 new_ele->SetAttribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str());
1047 return TreeElement(this, new_ele);
1048}
1049
1050TreeDocument::TreeElement TreeDocument::newTree(const TreeElement & other, bool auto_register_nodes)
1051{
1052 return mergeTree(other, false, auto_register_nodes).getTree(other.getName());
1053}
1054
1056 const TreeDocument & other, const std::string & tree_name, bool auto_register_nodes)
1057{
1058 std::string name(tree_name);
1059 if (name.empty()) {
1060 if (other.hasRootTreeName()) {
1061 name = other.getRootTreeName();
1062 } else if (const std::vector<std::string> names = other.getAllTreeNames(); names.size() == 1) {
1063 name = names[0];
1064 } else {
1065 throw exceptions::TreeDocumentError(
1066 "Failed to create new tree element from another document because argument tree_name was omitted and it was not "
1067 "possible to determine the root tree automatically.");
1068 }
1069 }
1070 TreeElement tree_ele = newTree(name);
1071 tree_ele.insertTreeFromDocument(other, name, nullptr, auto_register_nodes);
1072 return tree_ele;
1073}
1074
1075TreeDocument::TreeElement TreeDocument::newTreeFromString(const std::string & tree_str, const std::string & tree_name)
1076{
1077 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
1078 new_doc.mergeString(tree_str, true);
1079 return newTreeFromDocument(new_doc, tree_name, false);
1080}
1081
1082TreeDocument::TreeElement TreeDocument::newTreeFromFile(const std::string & path, const std::string & tree_name)
1083{
1084 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
1085 new_doc.mergeFile(path, true);
1086 return newTreeFromDocument(new_doc, tree_name, false);
1087}
1088
1090 const TreeResource & resource, const std::string & tree_name, bool auto_register_nodes)
1091{
1092 TreeDocument new_doc(format_version_, tree_node_loader_ptr_);
1093 new_doc.mergeFile(resource.build_request_file_path_);
1094 if (resource.hasRootTreeName()) new_doc.setRootTreeName(resource.getRootTreeName());
1095 if (auto_register_nodes) {
1096 // We register all associated node plugins beforehand, so that the user doesn't have to do that manually. This
1097 // means, that also potentially unused nodes are available and registered with the factory. This seems unnecessary,
1098 // but it's very convenient and the performance probably doesn't suffer too much.
1099 registerNodes(resource.getNodeManifest(), false);
1100 }
1101 return newTreeFromDocument(new_doc, tree_name, false);
1102}
1103
1104bool TreeDocument::hasTreeName(const std::string & tree_name) const
1105{
1106 if (auto_apms_util::contains(getAllTreeNames(), tree_name)) return true;
1107 return false;
1108}
1109
1111{
1112 return TreeElement(this, getXMLElementForTreeWithName(tree_name));
1113}
1114
1115TreeDocument & TreeDocument::setRootTreeName(const std::string & tree_name)
1116{
1117 if (tree_name.empty()) {
1118 throw exceptions::TreeDocumentError("Cannot set root tree name with empty string.");
1119 }
1120 if (!hasTreeName(tree_name)) {
1121 throw exceptions::TreeDocumentError(
1122 "Cannot make tree with name '" + tree_name + "' the root tree because it doesn't exist.");
1123 }
1124 RootElement()->SetAttribute(ROOT_TREE_ATTRIBUTE_NAME, tree_name.c_str());
1125 return *this;
1126}
1127
1129{
1130 if (RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME)) return true;
1131 return false;
1132}
1133
1135{
1136 if (const auto tree_name = RootElement()->Attribute(ROOT_TREE_ATTRIBUTE_NAME)) return tree_name;
1137 throw exceptions::TreeDocumentError(
1138 "Cannot get root tree name because the document's root element has no attribute '" +
1139 std::string(ROOT_TREE_ATTRIBUTE_NAME) + "'.");
1140}
1141
1143
1144TreeDocument & TreeDocument::removeTree(const std::string & tree_name)
1145{
1146 RootElement()->DeleteChild(getXMLElementForTreeWithName(tree_name));
1147 return *this;
1148}
1149
1151
1152std::vector<std::string> TreeDocument::getAllTreeNames() const { return getAllTreeNamesImpl(*this); }
1153
1154TreeDocument & TreeDocument::applyNodeNamespace(const std::string & node_namespace, const std::string & sep)
1155{
1156 // Get the old node names before applying namespace
1157 std::set<std::string> old_node_names;
1158 for (const auto & [name, _] : registered_nodes_manifest_.map()) {
1159 old_node_names.insert(name);
1160 }
1161
1162 // Apply namespace to the registered nodes manifest
1163 registered_nodes_manifest_.applyNodeNamespace(node_namespace, sep);
1164
1165 // Helper to recursively rename nodes in the XML
1166 auto rename_recursive = [&old_node_names, &node_namespace, &sep](XMLElement * parent, auto & self) -> void {
1167 for (XMLElement * child = parent->FirstChildElement(); child != nullptr; child = child->NextSiblingElement()) {
1168 const char * name = child->Name();
1169 // Rename if the node name is in the old registered nodes - we don't want to rename native nodes.
1170 if (name && old_node_names.count(name) > 0) {
1171 child->SetName((node_namespace + sep + name).c_str());
1172 }
1173 self(child, self);
1174 }
1175 };
1176
1177 // Process all trees
1178 for (XMLElement * tree_ele = RootElement()->FirstChildElement(TREE_ELEMENT_NAME); tree_ele != nullptr;
1179 tree_ele = tree_ele->NextSiblingElement(TREE_ELEMENT_NAME)) {
1180 rename_recursive(tree_ele, rename_recursive);
1181 }
1182
1183 // Also update the BehaviorTreeFactory registrations so that the factory
1184 // recognizes the new (namespaced) node IDs used in the XML.
1185 // We must copy the manifest and builder data BEFORE unregistering, as unregisterBuilder
1186 // invalidates any references to the internal maps.
1187 std::vector<std::pair<std::string, std::pair<BT::TreeNodeManifest, BT::NodeBuilder>>> registrations_to_update;
1188 const auto & factory_manifests = factory_.manifests();
1189 const auto & factory_builders = factory_.builders();
1190 for (const auto & old_name : old_node_names) {
1191 const auto manifest_it = factory_manifests.find(old_name);
1192 const auto builder_it = factory_builders.find(old_name);
1193 if (manifest_it == factory_manifests.end() || builder_it == factory_builders.end()) {
1194 // This should never happen since we derived old_node_names from registered_nodes_manifest_
1195 throw exceptions::TreeDocumentError(
1196 "Internal error while applying node namespace: Node '" + old_name +
1197 "' not found in BehaviorTreeFactory registrations.");
1198 }
1199 // Copy the data before we modify the factory
1200 registrations_to_update.emplace_back(old_name, std::make_pair(manifest_it->second, builder_it->second));
1201 }
1202
1203 // Now perform the unregister/register operations using the copied data
1204 for (const auto & [old_name, manifest_builder_pair] : registrations_to_update) {
1205 factory_.unregisterBuilder(old_name);
1206 BT::TreeNodeManifest new_manifest = manifest_builder_pair.first;
1207 new_manifest.registration_ID = node_namespace + sep + old_name;
1208 factory_.registerBuilder(new_manifest, manifest_builder_pair.second);
1209 }
1210
1211 return *this;
1212}
1213
1214TreeDocument & TreeDocument::registerNodes(const NodeManifest & tree_node_manifest, bool override)
1215{
1216 // Make sure that there are no node names that are reserved for native nodes
1217 std::set<std::string> all_registration_names;
1218 for (const auto & [name, _] : tree_node_manifest.map()) all_registration_names.insert(name);
1219 if (const std::set<std::string> common =
1220 auto_apms_util::getCommonElements(all_registration_names, native_node_names_);
1221 !common.empty()) {
1222 throw exceptions::TreeDocumentError(
1223 "Found reserved node registration names in the node manifest. The following names are not allowed, because "
1224 "they refer to native behavior tree nodes: [ " +
1225 auto_apms_util::join(std::vector<std::string>(common.begin(), common.end()), ", ") + " ].");
1226 }
1227
1228 for (const auto & [node_name, params] : tree_node_manifest.map()) {
1229 // By design, we require the user to maintain unique registration names for nodes. Theoretically, we could tolerate
1230 // multiple registrations of the same node name if both registration options are identical, but this would
1231 // complicate the implementation (comparing every option one by one) and indicates bad structuring of node manifests
1232 // anyways. So we discourage this practice by throwing an error.
1233 if (registered_nodes_manifest_.contains(node_name)) {
1234 if (override) {
1235 // If override is true, register the new node plugin instead of the current one
1236 factory_.unregisterBuilder(node_name);
1237 registered_nodes_manifest_.remove(node_name);
1238 } else {
1239 // If overriding is not explicitly wanted, we must throw
1240 throw exceptions::TreeDocumentError(
1241 "Tried to register node '" + node_name + "' (Class: " + params.class_name +
1242 ") which is already known. You must make sure that the registration names are unique or explicitly allow "
1243 "overriding previously registered nodes with the same name by setting override=true.");
1244 }
1245 }
1246
1247 // Check if the class we search for is actually available with the loader.
1248 if (!tree_node_loader_ptr_->isClassAvailable(params.class_name)) {
1249 if (all_node_classes_package_map_.find(params.class_name) == all_node_classes_package_map_.end()) {
1250 throw exceptions::TreeDocumentError(
1251 "Node '" + node_name + " (" + params.class_name +
1252 ")' cannot be registered, because the class name is not known to the class loader. "
1253 "Make sure that it's spelled correctly and registered by calling "
1254 "auto_apms_behavior_tree_register_nodes() in the CMakeLists.txt of the "
1255 "corresponding package.");
1256 }
1257 throw exceptions::TreeDocumentError(
1258 "Node '" + node_name + " (" + params.class_name +
1259 ")' cannot be registered, because the corresponding resource belongs to excluded package '" +
1260 all_node_classes_package_map_.at(params.class_name) + "'.");
1261 }
1262
1263 pluginlib::UniquePtr<NodeRegistrationInterface> plugin_instance;
1264 try {
1265 plugin_instance = tree_node_loader_ptr_->createUniqueInstance(params.class_name);
1266 } catch (const pluginlib::CreateClassException & e) {
1267 throw pluginlib::CreateClassException(
1268 "Failed to create an instance of node '" + node_name + " (" + params.class_name +
1269 ")'. Remember that the AUTO_APMS_BEHAVIOR_TREE_REGISTER_NODE "
1270 "macro must be called in the source file for the node class to be discoverable. "
1271 "Error message: " +
1272 e.what() + ".");
1273 }
1274
1275 try {
1276 if (plugin_instance->requiresRosNodeContext()) {
1277 if (only_non_ros_nodes_) {
1278 throw exceptions::NodeRegistrationError(
1279 "Node '" + node_name +
1280 "' relies on ROS 2 functionality but this instance only allows to use non-ROS nodes.");
1281 }
1282 RosNodeContext ros_node_context(
1283 ros_node_wptr_.lock(), tree_node_waitables_callback_group_wptr_.lock(),
1284 tree_node_waitables_executor_wptr_.lock(), params);
1285 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name, &ros_node_context);
1286 } else {
1287 // Create a dummy object of RosNodeContext to allow for parsing the registration params nevertheless
1288 RosNodeContext ros_node_context(nullptr, nullptr, nullptr, params);
1289 plugin_instance->registerWithBehaviorTreeFactory(factory_, node_name, &ros_node_context);
1290 }
1291 } catch (const std::exception & e) {
1292 throw exceptions::NodeRegistrationError(
1293 "Cannot register node '" + node_name + " (" + params.class_name + ")': " + e.what() + ".");
1294 }
1295 registered_nodes_manifest_.add(node_name, params);
1296 }
1297 return *this;
1298}
1299
1300std::set<std::string> TreeDocument::getRegisteredNodeNames(bool include_native) const
1301{
1302 std::set<std::string> names;
1303 if (include_native) names = native_node_names_;
1304 for (const auto & [name, _] : registered_nodes_manifest_.map()) names.insert(name);
1305 return names;
1306}
1307
1308NodeManifest TreeDocument::getRequiredNodeManifest(const std::string & tree_name) const
1309{
1310 NodeManifest m;
1311 TreeDocument * doc = const_cast<TreeDocument *>(this);
1312 std::vector<std::string> tree_names_to_process =
1313 tree_name.empty() ? getAllTreeNames() : std::vector<std::string>{tree_name};
1314 for (const std::string & tree_name : tree_names_to_process) {
1315 XMLElement * ptr = const_cast<XMLElement *>(getXMLElementForTreeWithName(tree_name));
1316 const TreeElement ele(doc, ptr);
1317 m.merge(ele.getRequiredNodeManifest(), true);
1318 }
1319 return m;
1320}
1321
1323{
1324 // Create or get the TreeNodesModel element
1325 tinyxml2::XMLElement * model_root = RootElement()->FirstChildElement(TREE_NODE_MODEL_ELEMENT_NAME);
1326
1327 // If no TreeNodesModel element exists, create one
1328 if (!model_root) {
1329 model_root = NewElement(TREE_NODE_MODEL_ELEMENT_NAME);
1330 RootElement()->InsertEndChild(model_root);
1331 }
1332
1333 // Iterate through the model_map and create XML elements for each node
1334 for (const auto & [node_name, model] : model_map) {
1335 // Create the element with the node type as the tag name
1336 tinyxml2::XMLElement * node_element = NewElement(BT::toStr(model.type).c_str());
1337 node_element->SetAttribute("ID", node_name.c_str());
1338
1339 // Add port information
1340 for (const auto & port_info : model.port_infos) {
1341 tinyxml2::XMLElement * port_element = nullptr;
1342
1343 // Create the appropriate port element based on direction
1344 switch (port_info.port_direction) {
1345 case BT::PortDirection::INPUT:
1346 port_element = NewElement("input_port");
1347 break;
1348 case BT::PortDirection::OUTPUT:
1349 port_element = NewElement("output_port");
1350 break;
1351 case BT::PortDirection::INOUT:
1352 port_element = NewElement("inout_port");
1353 break;
1354 }
1355
1356 // Set port attributes
1357 port_element->SetAttribute("name", port_info.port_name.c_str());
1358
1359 if (!port_info.port_type.empty()) {
1360 port_element->SetAttribute("type", port_info.port_type.c_str());
1361 }
1362
1363 if (port_info.port_has_default) {
1364 port_element->SetAttribute("default", port_info.port_default.c_str());
1365 }
1366
1367 // Set port description as text content
1368 if (!port_info.port_description.empty()) {
1369 port_element->SetText(port_info.port_description.c_str());
1370 }
1371
1372 node_element->InsertEndChild(port_element);
1373 }
1374
1375 model_root->InsertEndChild(node_element);
1376 }
1377
1378 return *this;
1379}
1380
1381NodeModelMap TreeDocument::getNodeModel(tinyxml2::XMLDocument & doc, const NodeManifest & manifest)
1382{
1383 const tinyxml2::XMLElement * root = doc.RootElement();
1384 if (!root) {
1385 throw exceptions::TreeDocumentError("Node model document has no root element.");
1386 }
1387 if (const char * ver = root->Attribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME)) {
1388 const std::string expected_format = TreeDocument::BTCPP_FORMAT_DEFAULT_VERSION;
1389 if (std::string(ver) != expected_format) {
1390 throw exceptions::TreeDocumentError(
1391 "Cannot parse node model document: Format of model document (" +
1392 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) + ": " + ver +
1393 ") doesn't comply with the expected format (" + std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) + ": " +
1394 expected_format + ").");
1395 }
1396 } else {
1397 throw exceptions::TreeDocumentError(
1398 "Cannot parse node model document: Root element of model document doesn't have required attribute '" +
1399 std::string(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME) + "'.");
1400 }
1401 tinyxml2::XMLElement * model_ele = doc.RootElement()->FirstChildElement(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME);
1402 if (!model_ele) {
1403 throw exceptions::TreeDocumentError(
1404 "Element <" + std::string(TreeDocument::TREE_NODE_MODEL_ELEMENT_NAME) +
1405 "> doesn't exist in node model document.");
1406 }
1407
1408 NodeModelMap model_map;
1409 for (tinyxml2::XMLElement * ele = model_ele->FirstChildElement(); ele != nullptr; ele = ele->NextSiblingElement()) {
1410 const char * node_name = ele->Attribute("ID");
1411 if (!node_name) {
1412 throw exceptions::TreeDocumentError(
1413 "Element '" + std::string(ele->Name()) + "' in node model document is missing the required attribute 'ID'");
1414 }
1415 NodeModel & model = model_map[node_name];
1416 model.type = BT::convertFromString<BT::NodeType>(ele->Name());
1417 for (const tinyxml2::XMLElement * port_ele = ele->FirstChildElement(); port_ele != nullptr;
1418 port_ele = port_ele->NextSiblingElement()) {
1419 const std::string direction = port_ele->Name();
1420 NodePortInfo port_info;
1421 if (direction == "input_port") {
1422 port_info.port_direction = BT::PortDirection::INPUT;
1423 } else if (direction == "output_port") {
1424 port_info.port_direction = BT::PortDirection::OUTPUT;
1425 } else if (direction == "inout_port") {
1426 port_info.port_direction = BT::PortDirection::INOUT;
1427 } else {
1428 throw exceptions::TreeDocumentError(
1429 "Unknown port direction in node model for '" + std::string(node_name) + "': " + direction);
1430 }
1431 if (const char * c = port_ele->Attribute("name")) {
1432 port_info.port_name = c;
1433 }
1434 if (const char * c = port_ele->Attribute("type")) {
1435 port_info.port_type = c;
1436 }
1437 if (const char * c = port_ele->Attribute("default")) {
1438 port_info.port_has_default = true;
1439 port_info.port_default = c;
1440 } else {
1441 port_info.port_has_default = false;
1442 }
1443 if (const char * c = port_ele->GetText()) {
1444 port_info.port_description = c;
1445 }
1446 model.port_infos.push_back(std::move(port_info));
1447 }
1448 // Port infos may be empty if there are no ports
1449 }
1450
1451 // Collect hidden ports from the node manifest
1452 std::map<std::string, std::vector<std::string>> hidden_ports;
1453 for (const auto & [node_name, registration_options] : manifest.map()) {
1454 // Extract hidden_ports from registration options
1455 for (const std::string & port_name : registration_options.hidden_ports) {
1456 hidden_ports[node_name].push_back(port_name);
1457 }
1458
1459 // Implicitly hide ports that are specified in port_alias
1460 for (const auto & [port_name, _] : registration_options.port_alias) {
1461 hidden_ports[node_name].push_back(port_name);
1462 }
1463 }
1464
1465 // Apply port hiding
1466 for (const auto & [node_name, ports_to_hide] : hidden_ports) {
1467 auto it = model_map.find(node_name);
1468 if (it == model_map.end()) {
1469 continue;
1470 }
1471 NodeModel & model = it->second;
1472 model.port_infos.erase(
1473 std::remove_if(
1474 model.port_infos.begin(), model.port_infos.end(),
1475 [&ports_to_hide](const NodePortInfo & port_info) {
1476 return std::find(ports_to_hide.begin(), ports_to_hide.end(), port_info.port_name) != ports_to_hide.end();
1477 }),
1478 model.port_infos.end());
1479 }
1480
1481 return model_map;
1482}
1483
1485{
1486 // Generate XML from the factory
1487 std::string model_xml;
1488 try {
1489 model_xml = BT::writeTreeNodesModelXML(factory_, include_native);
1490 } catch (const std::exception & e) {
1491 throw exceptions::TreeDocumentError("Error generating node model XML from factory: " + std::string(e.what()));
1492 }
1493
1494 // Parse the XML
1495 tinyxml2::XMLDocument model_doc;
1496 if (model_doc.Parse(model_xml.c_str()) != tinyxml2::XMLError::XML_SUCCESS) {
1497 throw exceptions::TreeDocumentError(
1498 "Error parsing the model of the currently registered nodes: " + std::string(model_doc.ErrorStr()));
1499 }
1500
1501 // Get the node model with hidden ports applied
1502 return getNodeModel(model_doc, registered_nodes_manifest_);
1503}
1504
1505BT::Result TreeDocument::verify() const
1506{
1507 NodeModelMap model_map = getNodeModel(true);
1508 std::unordered_map<std::string, BT::NodeType> registered_nodes;
1509 for (const auto & [node_name, model] : model_map) {
1510 registered_nodes[node_name] = model.type;
1511 }
1512 try {
1513 BT::VerifyXML(writeToString(), registered_nodes);
1514 } catch (const BT::RuntimeError & e) {
1515 return nonstd::make_unexpected(e.what());
1516 }
1517 return {};
1518}
1519
1520std::string TreeDocument::writeToString(bool remove_whitespace) const
1521{
1522 tinyxml2::XMLPrinter printer(nullptr, remove_whitespace);
1523 Print(&printer);
1524 return printer.CStr();
1525}
1526
1527void TreeDocument::writeToFile(const std::string & path, bool remove_whitespace) const
1528{
1529 const std::string xml = writeToString(remove_whitespace);
1530 FILE * f = std::fopen(path.c_str(), "w");
1531 if (!f) {
1532 throw exceptions::TreeDocumentError("Failed to open file for writing: " + path);
1533 }
1534 const std::size_t written = std::fwrite(xml.data(), 1, xml.size(), f);
1535 const int close_err = std::fclose(f);
1536 if (written != xml.size() || close_err != 0) {
1537 throw exceptions::TreeDocumentError("Failed to write tree document to file: " + path);
1538 }
1539}
1540
1541TreeDocument & TreeDocument::reset(bool unregister_nodes)
1542{
1543 // Reset XML document
1544 Clear();
1545 tinyxml2::XMLElement * root_ele = NewElement(TreeDocument::ROOT_ELEMENT_NAME);
1546 root_ele->SetAttribute(TreeDocument::BTCPP_FORMAT_ATTRIBUTE_NAME, format_version_.c_str());
1547 InsertFirstChild(root_ele);
1548
1549 // Reset node registration state if desired
1550 if (unregister_nodes) {
1551 for (const auto & node_name : getRegisteredNodeNames(false)) {
1552 factory_.unregisterBuilder(node_name);
1553 }
1554 registered_nodes_manifest_ = NodeManifest();
1555 }
1556 return *this;
1557}
1558
1559const TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(const std::string & tree_name) const
1560{
1561 return getXMLElementForTreeWithNameImpl<const XMLElement *>(*this, tree_name);
1562}
1563
1564TreeDocument::XMLElement * TreeDocument::getXMLElementForTreeWithName(const std::string & tree_name)
1565{
1566 return getXMLElementForTreeWithNameImpl<XMLElement *>(*this, tree_name);
1567}
1568
1569template <typename ReturnT, typename DocumentT>
1570ReturnT TreeDocument::getXMLElementForTreeWithNameImpl(DocumentT & doc, const std::string & tree_name)
1571{
1572 if (tree_name.empty()) {
1573 throw exceptions::TreeDocumentError("Cannot get tree with an empty name.");
1574 }
1575 if (!doc.hasTreeName(tree_name)) {
1576 throw exceptions::TreeDocumentError("Cannot get tree with name '" + tree_name + "' because it doesn't exist.");
1577 }
1578 auto child = doc.RootElement()->FirstChildElement(TreeDocument::TREE_ELEMENT_NAME);
1579 while (child && !child->Attribute(TreeDocument::TREE_NAME_ATTRIBUTE_NAME, tree_name.c_str())) {
1580 child = child->NextSiblingElement();
1581 }
1582 if (!child) {
1583 throw std::logic_error(
1584 "Unexpected error trying to get tree element with name '" + tree_name +
1585 "'. Since hasTreeName() returned true, there MUST be a corresponding element.");
1586 }
1587 return child;
1588}
1589
1590} // namespace auto_apms_behavior_tree::core
const NodeManifest & getNodeManifest() const
Get the node manifest associated with this resource.
Definition behavior.hpp:471
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.
Definition script.hpp:30
std::string str() const
Concatenate all expressions of this instance to a single string.
Definition script.cpp:41
Forward iterator for traversing the first-level children of a node.
ChildIterator & operator++()
Pre-increment: advance to the next sibling element.
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.
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.
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 &registration_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 &registration_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.
Handle for a single behavior tree of a TreeDocument.
TreeElement & removeFirstChild(const std::string &registration_name="", const std::string &instance_name="", bool deep_search=false)
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 & 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.
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.
virtual TreeDocument & registerNodes(const NodeManifest &tree_node_manifest, bool override=false)
Load behavior tree node plugins and register them with the internal behavior tree factory.
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.
Definition container.hpp:36
std::set< KeyT, CompareT, AllocatorT > getCommonElements(std::set< KeyT, CompareT, AllocatorT > c1, std::set< KeyT, CompareT, AllocatorT > c2)
Assemble common elements of two sets.
Definition container.hpp:53
Core API for AutoAPMS's behavior tree implementation.
Definition behavior.hpp:32
Models for all available behavior tree nodes.
Powerful tooling for incorporating behavior trees for task development.
Definition behavior.hpp:32
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....