61 LOG_INFO(
"LibXMLParser",
"libxml2 threading support enabled");
84 std::ifstream file(filepath);
92 file.seekg(0, std::ios::end);
93 std::streampos fileSize = file.tellg();
94 file.seekg(0, std::ios::beg);
97 if (fileSize < 1024) {
98 sizeStr = std::to_string(fileSize) +
" bytes";
99 }
else if (fileSize < 1024 * 1024) {
100 sizeStr = std::to_string(
static_cast<double>(fileSize) / 1024.0) +
" KB";
102 sizeStr = std::to_string(
static_cast<double>(fileSize) / (1024.0 * 1024.0)) +
" MB";
112 xmlDocPtr doc = xmlParseFile(filepath.c_str());
133 ReportProgress(
"ERROR: Failed to parse document structure", 2, 5);
154 auto validation_result = validator.
Validate(main_tree.get());
157 if (validation_result.HasErrors()) {
159 "Tree validation failed with " + std::to_string(validation_result.ErrorCount()) +
" error(s):");
161 for (
const auto &issue : validation_result.issues) {
164 LOG_ERROR(
"LibXMLParser",
" [" + issue.node_path +
"] " + issue.message);
173 if (validation_result.HasWarnings()) {
175 "Tree has " + std::to_string(validation_result.WarningCount()) +
" warning(s):");
177 for (
const auto &issue : validation_result.issues) {
179 LOG_WARNING(
"LibXMLParser",
" [" + issue.node_path +
"] " + issue.message);
194 auto validation_result = validator.
Validate(first_tree.get());
196 if (validation_result.HasErrors()) {
197 for (
const auto &issue : validation_result.issues) {
219 xmlDocPtr doc = xmlParseMemory(xml_content.c_str(), xml_content.length());
239 auto validation_result = validator.
Validate(main_tree.get());
241 if (validation_result.HasErrors()) {
242 for (
const auto &issue : validation_result.issues) {
259 auto validation_result = validator.
Validate(first_tree.get());
261 if (validation_result.HasErrors()) {
262 for (
const auto &issue : validation_result.issues) {
277std::vector<LibXMLBehaviorTreeParser::ParseResult>
280 std::vector<ParseResult> results;
281 results.reserve(filepaths.size());
283 for (
const auto &filepath : filepaths) {
294 }
catch (
const std::exception &e) {
300 results.push_back(result);
313 if (pair.second.is_implemented) {
332 std::vector<EmberCore::String> filepaths;
362 int total_files =
static_cast<int>(filepaths.size());
363 int current_file = 0;
365 ReportProgress(
"Starting project parsing...", 0, total_files + 2);
367 std::vector<EmberCore::String> main_tree_candidates;
370 for (
const auto &filepath : filepaths) {
374 ReportProgress(
"Parsing file " + std::to_string(current_file) +
"/" + std::to_string(total_files) +
": " +
376 current_file, total_files + 2);
379 std::ifstream file(filepath);
385 failedInfo.
errors.push_back(
"File not found: " + filepath);
396 xmlDocPtr doc = xmlParseFile(filepath.c_str());
402 failedInfo.
errors.push_back(
"Failed to parse XML file - syntax error");
413 xmlNodePtr root = xmlDocGetRootElement(doc);
419 failedInfo.
errors.push_back(
"XML document has no root element");
430 const auto &doc_config =
config_.GetDocumentConfig();
431 const auto &tree_config =
config_.GetTreeConfig();
435 bool root_valid =
false;
436 if (doc_config.case_sensitive) {
437 root_valid = (root_name == doc_config.root_element);
439 auto to_lower = [](
String str) {
440 std::transform(str.begin(), str.end(), str.begin(), ::tolower);
443 root_valid = (to_lower(root_name) == to_lower(doc_config.root_element));
451 failedInfo.
errors.push_back(
"Expected '" + doc_config.root_element +
"' element, found '" + root_name +
458 "' element, found '" + root_name +
"'",
467 if (!main_tree.empty()) {
468 main_tree_candidates.push_back(main_tree);
477 std::set<String> file_tree_ids;
478 std::set<String> file_bb_ids;
479 std::set<String> subtree_refs_set;
480 std::set<String> bb_includes_set;
481 std::map<String, std::vector<String>> per_bb_includes;
484 for (xmlNodePtr child = root->children; child; child = child->next) {
485 if (child->type == XML_ELEMENT_NODE) {
488 if (name == tree_config.behavior_tree_element) {
494 if (tree_id.empty()) {
495 fileInfo.
errors.push_back(
"BehaviorTree element missing required 'ID' attribute");
497 fileInfo.
tree_ids.push_back(tree_id);
500 if (file_tree_ids.count(tree_id) > 0) {
501 fileInfo.
warnings.push_back(
"Duplicate tree ID within file: " + tree_id);
503 file_tree_ids.insert(tree_id);
511 status.
is_implemented = tree->HasRootNode() && tree->GetNodeCount() > 0;
517 if (tree->HasRootNode()) {
521 LOG_INFO(
"LibXMLParser",
"Parsed tree '" + tree_id +
"' from " + filepath);
524 }
else if (name ==
"Blackboard") {
531 fileInfo.
errors.push_back(
"Blackboard element missing required 'ID' attribute");
536 if (file_bb_ids.count(bb_id) > 0) {
537 fileInfo.
warnings.push_back(
"Duplicate blackboard ID within file: " + bb_id);
539 file_bb_ids.insert(bb_id);
543 if (!includes.empty()) {
545 if (includes.front() !=
'{' || includes.back() !=
'}') {
547 "Blackboard '" + bb_id +
548 "' has malformed includes syntax (should be {ID1 ID2 ...})");
551 String includes_content = includes.substr(1, includes.size() - 2);
552 std::istringstream iss(includes_content);
554 std::vector<String> bb_inc_list;
555 while (iss >> token) {
556 if (!token.empty()) {
557 bb_includes_set.insert(token);
559 bb_inc_list.push_back(token);
562 if (!bb_inc_list.empty()) {
563 per_bb_includes[bb_id] = std::move(bb_inc_list);
569 std::set<String> seen_keys;
570 for (xmlNodePtr entryNode = child->children; entryNode; entryNode = entryNode->next) {
571 if (entryNode->type != XML_ELEMENT_NODE)
580 fileInfo.
errors.push_back(
"Blackboard '" + bb_id +
581 "' has Entry missing required 'key' attribute");
585 fileInfo.
errors.push_back(
"Blackboard '" + bb_id +
"' entry '" + key +
586 "' missing required 'type' attribute");
591 if (seen_keys.count(key) > 0) {
592 fileInfo.
warnings.push_back(
"Blackboard '" + bb_id +
593 "' has duplicate entry key: " + key);
595 seen_keys.insert(key);
598 static const std::set<String> validTypes = {
"bool",
616 "vec(vec(uint32_t))",
621 "map(string, string)",
622 "map(string,string)",
633 bool isValidType = validTypes.count(type) > 0;
634 bool isCustomType = !type.empty() && std::isupper(type[0]) &&
635 type.find(
'(') == String::npos && type.find(
')') == String::npos;
637 if (!isValidType && !isCustomType) {
638 fileInfo.
warnings.push_back(
"Blackboard '" + bb_id +
"' entry '" + key +
639 "' has unsupported type: " + type);
647 LOG_INFO(
"LibXMLParser",
"Parsed blackboard '" + bb_id +
"' from " + filepath);
655 fileInfo.
subtree_refs.assign(subtree_refs_set.begin(), subtree_refs_set.end());
657 for (
auto &kv : per_bb_includes) {
663 fileInfo.
warnings.push_back(
"No BehaviorTree or Blackboard elements found in file");
676 size_t best_ref_count = 0;
678 auto countRefs = [](
Node *root) ->
size_t {
681 std::set<EmberCore::String> refs;
682 std::vector<Node *> stack = {root};
683 while (!stack.empty()) {
684 Node *n = stack.back();
695 for (
const auto &candidate : main_tree_candidates) {
697 if (it ==
parsed_trees_.end() || !it->second || !it->second->HasRootNode())
699 size_t refCount = countRefs(it->second->GetRootNode());
700 if (best_candidate.empty() || refCount > best_ref_count) {
701 best_candidate = candidate;
702 best_ref_count = refCount;
706 if (!best_candidate.empty()) {
708 }
else if (!main_tree_candidates.empty()) {
720 for (
const auto &err :
errors_) {
721 result.
errors.push_back(err);
726 ReportProgress(
"Collecting SubTree references...", total_files + 1, total_files + 2);
728 std::set<EmberCore::String> all_references;
730 if (tree_pair.second && tree_pair.second->HasRootNode()) {
736 std::set<String> implemented_tree_ids;
738 implemented_tree_ids.insert(tree_pair.first);
742 for (
const auto &ref : all_references) {
751 result.
warnings.push_back(
"Tree '" + ref +
"' is referenced but not implemented");
752 LOG_WARNING(
"LibXMLParser",
"Tree '" + ref +
"' is referenced but not implemented");
756 for (
const auto &subtree_ref : fileInfo.subtree_refs) {
757 if (subtree_ref == ref) {
758 fileInfo.warnings.push_back(
"References unimplemented tree: " + ref);
767 ReportProgress(
"Expanding SubTree references...", total_files + 2, total_files + 2);
771 ReportProgress(
"Validating cross-file references...", total_files + 2, total_files + 2);
774 std::map<String, std::vector<String>> tree_id_to_files;
775 for (
const auto &fileInfo : result.
file_infos) {
776 for (
const auto &tree_id : fileInfo.tree_ids) {
777 tree_id_to_files[tree_id].push_back(fileInfo.filepath);
780 for (
const auto &pair : tree_id_to_files) {
781 if (pair.second.size() > 1) {
783 result.
warnings.push_back(
"Duplicate tree ID found across files: " + pair.first);
788 std::map<String, std::vector<String>> bb_id_to_files;
789 std::set<String> all_blackboard_ids;
790 for (
const auto &fileInfo : result.
file_infos) {
791 for (
const auto &bb_id : fileInfo.blackboard_ids) {
792 bb_id_to_files[bb_id].push_back(fileInfo.filepath);
793 all_blackboard_ids.insert(bb_id);
796 for (
const auto &pair : bb_id_to_files) {
797 if (pair.second.size() > 1) {
799 result.
warnings.push_back(
"Duplicate blackboard ID found across files: " + pair.first);
804 std::set<String> unresolved_includes;
805 for (
const auto &fileInfo : result.
file_infos) {
806 for (
const auto &include : fileInfo.blackboard_includes) {
807 if (all_blackboard_ids.count(include) == 0) {
808 unresolved_includes.insert(include);
813 for (
const auto &include : unresolved_includes) {
814 result.
warnings.push_back(
"Unresolved blackboard include: " + include);
827 result.
parsed_blackboards[pair.first] = std::shared_ptr<Blackboard>(std::move(pair.second));
836 for (
const auto &err :
errors_) {
837 result.
errors.push_back(err);
846 total_files + 2, total_files + 2);
857 if (!subtree_ref.empty()) {
858 references.insert(subtree_ref);
863 if (std::find(status.referenced_in_files.begin(), status.referenced_in_files.end(),
current_file_path_) ==
864 status.referenced_in_files.end()) {
883 auto &tree = tree_pair.second;
884 if (tree && tree->HasRootNode()) {
897 bool expanded_any =
false;
901 if (!subtree_ref.empty()) {
906 if (referenced_tree && referenced_tree->HasRootNode()) {
907 Node *referenced_root = referenced_tree->GetRootNode();
915 String cycle_msg =
"Circular reference: ";
920 cycle_msg += tree_in_stack;
923 cycle_msg +=
" -> " + subtree_ref;
927 LOG_ERROR(
"LibXMLParser",
"Circular SubTree reference detected: " + cycle_msg);
931 if (!already_expanded) {
947 for (
size_t i = 0; i < referenced_root->
GetChildCount(); ++i) {
959 node->
SetName(
"SubTree: " + subtree_ref +
" [EMPTY]");
960 LOG_WARNING(
"LibXMLParser",
"SubTree '" + subtree_ref +
"' is empty (no root node)");
967 node->
SetName(
"SubTree: " + subtree_ref +
" [NOT IMPLEMENTED]");
968 LOG_WARNING(
"LibXMLParser",
"SubTree '" + subtree_ref +
"' not found (unimplemented reference)");
985 return it->second.is_implemented;
991 std::vector<EmberCore::String> result;
993 result.push_back(ref);
1003 xmlNodePtr root = xmlDocGetRootElement(doc);
1010 const auto &doc_config =
config_.GetDocumentConfig();
1013 if (doc_config.case_sensitive) {
1014 if (root_name != doc_config.root_element) {
1016 "' element, found '" + root_name +
"'");
1021 auto to_lower = [](
String str) {
1022 std::transform(str.begin(), str.end(), str.begin(), ::tolower);
1025 if (to_lower(root_name) != to_lower(doc_config.root_element)) {
1027 "' element, found '" + root_name +
"'");
1042 const auto &tree_config =
config_.GetTreeConfig();
1043 const auto &blackboard_config =
config_.GetBlackboardConfig();
1046 int totalElements = 0;
1047 for (xmlNodePtr child = root->children; child; child = child->next) {
1048 if (child->type == XML_ELEMENT_NODE) {
1050 if (name == tree_config.behavior_tree_element || name == blackboard_config.blackboard_element) {
1055 ReportProgress(
"Found " + std::to_string(totalElements) +
" element(s) to parse", 2, 5);
1059 int blackboardCount = 0;
1060 for (xmlNodePtr child = root->children; child; child = child->next) {
1061 if (child->type == XML_ELEMENT_NODE) {
1063 if (name == tree_config.behavior_tree_element) {
1067 if (!tree_id.empty()) {
1071 EmberCore::String progressMsg =
"Parsed tree " + std::to_string(treeCount) +
"/" +
1072 std::to_string(totalElements) +
": " + tree_id;
1076 if (tree->HasRootNode()) {
1077 size_t nodeCount = tree->GetNodeCount();
1078 ReportProgress(
" " + std::to_string(nodeCount) +
" node(s) in tree", 2, 5);
1082 tree_config.behavior_tree_element +
" missing " + tree_config.tree_id_attribute +
1088 }
else if (name == blackboard_config.blackboard_element) {
1096 }
else if (!doc_config.allow_unknown_elements) {
1097 LOG_WARNING(
"LibXMLParser",
"Unknown element '" + name +
"' in root (ignored)");
1105 ReportProgress(
"Parsed " + std::to_string(treeCount) +
" tree(s) and " + std::to_string(blackboardCount) +
1118 const auto &blackboard = blackboard_pair.second;
1120 tree_pair.second->AddBlackboard(blackboard->Clone());
1126 LOG_ERROR(
"LibXMLParser",
"Parsing failed with " + std::to_string(
errors_.size()) +
" error(s):");
1127 for (
size_t i = 0; i <
errors_.size(); ++i) {
1128 LOG_ERROR(
"LibXMLParser",
" " + std::to_string(i + 1) +
": " +
errors_[i].message);
1133 LOG_INFO(
"LibXMLParser",
"Parsing completed successfully");
1139 if (!node || !node->name)
1141 return std::string(
reinterpret_cast<const char *
>(node->name));
1148 xmlChar *value = xmlGetProp(node,
reinterpret_cast<const xmlChar *
>(attr_name.c_str()));
1184 if (!context.empty()) {
1186 error.
message +=
" (Context: " + context +
")";
1190 LOG_ERROR(
"LibXMLParser",
"Parse error: " + message +
"");
1198 xmlNodePtr current = node;
1200 while (current && current->parent) {
1206 node_name +=
"[ID=" +
id +
"]";
1207 }
else if (!name.empty()) {
1208 node_name +=
"[name=" + name +
"]";
1214 path = node_name +
"/" + path;
1217 current = current->parent;
1232 const auto &tree_config =
config_.GetTreeConfig();
1239 auto behavior_tree = std::make_shared<BehaviorTree>();
1242 behavior_tree->SetName(tree_id);
1248 xmlNodePtr child = tree_node->children;
1249 std::unique_ptr<Node> root_node =
nullptr;
1252 if (child->type == XML_ELEMENT_NODE) {
1256 root_node = std::move(node);
1259 tree_config.behavior_tree_element +
" can only have one root node", child);
1264 child = child->next;
1267 if (!root_node && tree_config.require_root_node) {
1269 EmberCore::String(tree_config.behavior_tree_element) +
" '" + tree_id +
"' has no root node");
1278 xmlAttrPtr attr = tree_node->properties;
1282 wrapper_node->SetAttribute(attr_name, attr_value);
1287 wrapper_node->AddChild(std::move(root_node));
1290 behavior_tree->SetRootNode(std::move(wrapper_node));
1292 return behavior_tree;
1296 if (!xml_node || xml_node->type != XML_ELEMENT_NODE) {
1300 const auto &node_config =
config_.GetNodeConfig();
1301 const auto &tree_config =
config_.GetTreeConfig();
1304 std::unique_ptr<Node> node =
nullptr;
1308 if (element_name == node_config.control_element) {
1310 }
else if (element_name == node_config.action_element) {
1312 }
else if (element_name == node_config.condition_element) {
1314 }
else if (element_name == node_config.decorator_element) {
1316 }
else if (element_name == tree_config.subtree_element) {
1318 }
else if (element_name == node_config.generic_node_element) {
1323 const auto &classification_config =
config_.GetClassificationConfig();
1324 switch (classification_config.unknown_behavior) {
1329 LOG_WARNING(
"LibXMLParser",
"Unknown node element: " + element_name +
" (skipped)");
1332 LOG_WARNING(
"LibXMLParser",
"Unknown node element: " + element_name +
" (creating generic action node)");
1341 if (element_name != tree_config.subtree_element) {
1351 const auto &node_config =
config_.GetNodeConfig();
1360 if (!
config_.IsControlType(control_type)) {
1361 const auto &classification_config =
config_.GetClassificationConfig();
1366 LOG_WARNING(
"LibXMLParser",
"Unknown control type '" + control_type +
"', creating generic control node");
1370 LOG_TRACE(
"LibXMLParser",
"Created control node: " + control_type);
1376 const auto &node_config =
config_.GetNodeConfig();
1385 if (!
config_.IsActionType(action_type)) {
1386 const auto &classification_config =
config_.GetClassificationConfig();
1387 if (!classification_config.action_types.empty() &&
1392 LOG_TRACE(
"LibXMLParser",
"Unknown action type '" + action_type +
"', creating generic Action node");
1396 LOG_TRACE(
"LibXMLParser",
"Created action node: " + action_type);
1402 const auto &node_config =
config_.GetNodeConfig();
1411 if (!
config_.IsConditionType(condition_type)) {
1412 const auto &classification_config =
config_.GetClassificationConfig();
1413 if (!classification_config.condition_types.empty() &&
1418 LOG_TRACE(
"LibXMLParser",
"Unknown condition type '" + condition_type +
"', creating generic Condition node");
1422 LOG_TRACE(
"LibXMLParser",
"Created condition node: " + condition_type);
1428 const auto &node_config =
config_.GetNodeConfig();
1437 if (!
config_.IsDecoratorType(decorator_type)) {
1438 const auto &classification_config =
config_.GetClassificationConfig();
1443 LOG_WARNING(
"LibXMLParser",
"Unknown decorator type '" + decorator_type +
"', creating generic decorator node");
1447 LOG_TRACE(
"LibXMLParser",
"Created decorator node: " + decorator_type);
1453 const auto &tree_config =
config_.GetTreeConfig();
1465 placeholder->SetAttribute(
"__subtree_ref__", subtree_id);
1466 placeholder->SetAttribute(
"__is_placeholder__",
"true");
1469 "Created SubTree placeholder for '" + subtree_id +
"' (will expand after all trees parsed)");
1478 const auto &blackboard_config =
config_.GetBlackboardConfig();
1485 auto blackboard = std::make_unique<Blackboard>(blackboard_id);
1489 if (!parent_id.empty()) {
1490 blackboard->SetParentId(parent_id);
1491 LOG_TRACE(
"LibXMLParser",
"Blackboard '" + blackboard_id +
"' inherits from '" + parent_id +
"'");
1495 xmlNodePtr child = blackboard_node->children;
1497 if (child->type == XML_ELEMENT_NODE &&
GetNodeName(child) == blackboard_config.entry_element) {
1500 blackboard->AddEntry(std::move(entry));
1503 child = child->next;
1506 LOG_TRACE(
"LibXMLParser",
"Parsed blackboard '" + blackboard_id +
"' with " +
1507 std::to_string(blackboard->GetEntryCount()) +
" entries");
1513 const auto &blackboard_config =
config_.GetBlackboardConfig();
1526 if (!blackboard_config.allow_undefined_keys) {
1530 LOG_WARNING(
"LibXMLParser",
"Unknown blackboard type '" + type_str +
"', using AUTO");
1534 auto entry = std::make_unique<BlackboardEntry>(key, data_type, value);
1536 if (!entry->IsValidValue()) {
1539 LOG_WARNING(
"LibXMLParser",
"Empty or invalid value '" + value +
"' for type '" + type_str +
"' (key: " + key +
1540 ") - will use default");
1545 "Parsed blackboard entry: " + key +
" (" + type_str +
") = " + (value.empty() ?
"<empty>" : value));
1551 const auto &node_config =
config_.GetNodeConfig();
1555 if (!name.empty()) {
1560 if (!
id.empty() && name.empty()) {
1570 if (node_config.allow_custom_attributes) {
1571 xmlAttrPtr attr = xml_node->properties;
1573 EmberCore::String attr_name = std::string(
reinterpret_cast<const char *
>(attr->name));
1574 EmberCore::String attr_value = std::string(
reinterpret_cast<const char *
>(attr->children->content));
1578 if (attr_name != node_config.node_name_attribute) {
1580 LOG_TRACE(
"LibXMLParser",
"Stored node attribute: " + attr_name +
" = " + attr_value);
1589 xmlNodePtr child = xml_parent->children;
1590 size_t children_added = 0;
1593 if (child->type == XML_ELEMENT_NODE) {
1597 parent->
AddChild(std::move(child_node));
1604 child = child->next;
1608 if (children_added == 0 && xml_parent->children) {
1610 "Node '" + parent->
GetName() +
1611 "' ended up with no children after parsing (likely due to skipped SubTree nodes)");
1621 auto &tree = tree_pair.second;
1622 if (tree && tree->HasRootNode()) {
1632 bool expanded_any =
false;
1636 if (!subtree_ref.empty()) {
1641 if (referenced_tree && referenced_tree->HasRootNode()) {
1642 Node *referenced_root = referenced_tree->GetRootNode();
1652 LOG_ERROR(
"LibXMLParser",
"Circular SubTree reference detected: " + subtree_ref);
1656 if (!already_expanded) {
1681 for (
size_t i = 0; i < referenced_root->
GetChildCount(); ++i) {
1691 expanded_any =
true;
1696 node->
SetName(
"SubTree: " + subtree_ref +
" [EMPTY]");
1697 LOG_WARNING(
"LibXMLParser",
"SubTree '" + subtree_ref +
"' is empty (no root node)");
1704 node->
SetName(
"SubTree: " + subtree_ref +
" [NOT IMPLEMENTED]");
1705 LOG_WARNING(
"LibXMLParser",
"SubTree '" + subtree_ref +
"' not found (unimplemented reference)");
1711 expanded_any =
true;
1716 return expanded_any;
1726 if (value.empty()) {
1735 if (!parent || !tree) {
1742 for (xmlNodePtr child = parent->children; child; child = child->next) {
1743 if (child->type == XML_COMMENT_NODE) {
1745 xmlChar *content = xmlNodeGetContent(child);
1751 bool before_node =
true;
1752 xmlNodePtr next_element = child->next;
1753 while (next_element && next_element->type != XML_ELEMENT_NODE) {
1754 next_element = next_element->next;
1757 if (!next_element) {
1758 before_node =
false;
1764 comment.
text = comment_text;
1766 xml_metadata.node_comments[node->
GetId()].push_back(comment);
1770 xml_metadata.header_comments.push_back(comment_text);
1772 xml_metadata.footer_comments.push_back(comment_text);
#define LOG_ERROR(category, message)
#define LOG_TRACE(category, message)
#define LOG_WARNING(category, message)
#define LOG_INFO(category, message)
Represents a BehaviorTree project containing multiple XML resources.
void SetTreeImplementationStatus(const std::map< String, TreeImplementationStatus > &statuses)
const std::vector< String > & GetResources() const
String ResolveResourcePath(const String &resource_path) const
Unified validation system for behavior trees.
ValidationResult Validate(const BehaviorTree *tree) const
Validate a behavior tree against the parser profile.
Represents a complete behavior tree data structure.
XMLMetadata & GetXMLMetadata()
static DataType ParseDataType(const EmberCore::String &typeStr)
DataType
Supported data types for blackboard entries.
std::unique_ptr< Node > ParseNode(xmlNodePtr xml_node)
std::shared_ptr< BehaviorTree > ParseFromString(const EmberCore::String &xml_content)
std::vector< EmberCore::String > GetUnimplementedReferences() const
bool ExpandSubTreePlaceholder(Node *node)
ProjectParseResult ParseFilesWithSharedRegistry(const std::vector< EmberCore::String > &filepaths)
std::set< EmberCore::String > unimplemented_references_
~LibXMLBehaviorTreeParser()
std::set< EmberCore::String > circular_references_
std::unique_ptr< Node > ParseSubTreeNode(xmlNodePtr xml_node)
std::map< EmberCore::String, TreeImplementationStatus > tree_implementation_statuses_
std::unique_ptr< Node > ParseActionNode(xmlNodePtr xml_node)
bool ExpandSubTreePlaceholderForProject(Node *node)
bool project_parsing_mode_
void ParseChildNodes(Node *parent, xmlNodePtr xml_parent)
IParseProgressCallback * progress_callback_
static std::mutex destruction_mutex_
void ExpandAllSubTreePlaceholdersForProject()
bool ParseXMLDocument(xmlDocPtr doc, const EmberCore::String &source_path="")
void AddError(ParseError::Type type, const EmberCore::String &message, xmlNodePtr node=nullptr, const EmberCore::String &context="")
void ExpandAllSubTreePlaceholders()
EmberCore::String current_file_path_
std::unique_ptr< BlackboardEntry > ParseBlackboardEntry(xmlNodePtr entry_node)
static bool global_libxml2_initialized_
std::unique_ptr< Node > ParseConditionNode(xmlNodePtr xml_node)
void CaptureComments(xmlNodePtr parent, BehaviorTree *tree, Node *node=nullptr)
std::set< EmberCore::String > expansion_stack_
void CollectSubTreeReferences(Node *node, std::set< EmberCore::String > &references)
EmberCore::String main_tree_name_
std::unique_ptr< Node > ParseControlNode(xmlNodePtr xml_node)
LibXMLBehaviorTreeParser()
std::map< EmberCore::String, std::shared_ptr< BehaviorTree > > parsed_trees_
const std::vector< ParseError > & GetErrors() const
EmberCore::String GetNodeAttribute(xmlNodePtr node, const EmberCore::String &attr_name)
EmberCore::String GetNodePath(xmlNodePtr node)
std::unique_ptr< Blackboard > ParseBlackboard(xmlNodePtr blackboard_node)
EmberCore::String GetNodeName(xmlNodePtr node)
ProjectParseResult ParseProject(BehaviorTreeProject *project)
void SetNodeAttributes(Node *node, xmlNodePtr xml_node)
std::vector< ParseError > errors_
std::shared_ptr< BehaviorTree > ParseFromFile(const EmberCore::String &filepath)
bool IsTreeImplemented(const EmberCore::String &tree_id) const
std::set< EmberCore::String > expanded_subtree_cache_
std::shared_ptr< BehaviorTree > ParseBehaviorTree(xmlNodePtr tree_node)
std::map< EmberCore::String, std::unique_ptr< Blackboard > > parsed_blackboards_
std::unique_ptr< Node > ParseDecoratorNode(xmlNodePtr xml_node)
bool libxml2_initialized_
std::vector< ParseResult > ParseMultipleFiles(const std::vector< EmberCore::String > &filepaths)
bool ReportProgress(const EmberCore::String &message, int current=0, int total=0)
bool ValidateRequiredAttribute(xmlNodePtr node, const EmberCore::String &attr_name)
static std::once_flag libxml2_init_flag_
Represents a node in a behavior tree structure.
void SetName(const String &name)
const std::map< String, String > & GetAllAttributes() const
String GetAttribute(const String &name, const String &default_value="") const
Node * GetChild(size_t index) const
const String & GetName() const
void SetAttribute(const String &name, const String &value)
void RemoveAttribute(const String &name)
void AddChild(std::unique_ptr< Node > child)
size_t GetChildCount() const
std::unique_ptr< Node > DeepCopy() const
Configuration for XML parser behavior and element/attribute mappings.
Main types header for EmberCore.
static void SuppressLibXMLErrors(void *ctx, const char *msg,...)
std::string String
Framework-agnostic string type.
Per-file parsing information for project validation.
std::vector< String > blackboard_includes
std::vector< String > blackboard_ids
std::vector< String > errors
std::vector< String > subtree_refs
std::vector< String > tree_ids
std::vector< String > warnings
Error information for parsing failures (enhanced with libxml2 details)
EmberCore::String context
EmberCore::String node_path
EmberCore::String message
Result of parallel parsing operation.
std::shared_ptr< BehaviorTree > tree
EmberCore::String file_path
std::vector< ParseError > errors
Result of project parsing operation.
std::vector< FileParseInfo > file_infos
std::vector< EmberCore::String > warnings
std::map< EmberCore::String, std::vector< EmberCore::String > > blackboard_includes_map
std::map< EmberCore::String, TreeImplementationStatus > tree_statuses
std::vector< EmberCore::String > duplicate_blackboard_ids
std::map< EmberCore::String, std::shared_ptr< BehaviorTree > > parsed_trees
int GetImplementedTreeCount() const
std::vector< EmberCore::String > unimplemented_references
std::vector< EmberCore::String > circular_references
std::vector< ParseError > errors
int GetTotalTreeCount() const
std::map< EmberCore::String, std::shared_ptr< Blackboard > > parsed_blackboards
EmberCore::String main_tree_name
std::vector< EmberCore::String > duplicate_tree_ids
std::vector< EmberCore::String > unresolved_blackboard_includes
Status of a tree's implementation in the project.
String defined_in_file
Which file defines this tree (empty if not defined)
bool has_root_node
Whether tree has a root node.
bool is_implemented
Has actual implementation (not just empty/placeholder)