Ember
Loading...
Searching...
No Matches
LibXMLBehaviorTreeSerializer.cpp
Go to the documentation of this file.
2#include "Core/BehaviorTree.h"
4#include "Core/Node.h"
5#include "Utils/Logger.h"
6#include <libxml/xmlsave.h>
7
8namespace EmberCore {
9
11
13 // Cleanup handled by libxml2
14}
15
16bool LibXMLBehaviorTreeSerializer::SerializeToFile(std::shared_ptr<BehaviorTree> tree,
17 const EmberCore::String &filepath) {
19
20 if (!tree) {
21 AddError(SerializeError::INVALID_TREE, "Behavior tree is null");
22 return false;
23 }
24
25 if (!tree->HasRootNode()) {
26 AddError(SerializeError::MISSING_ROOT_NODE, "Behavior tree has no root node");
27 return false;
28 }
29
30 LOG_INFO("LibXMLSerializer", "Serializing tree to file: " + filepath);
31
32 // Create XML document
33 xmlDocPtr doc = CreateXMLDocument(tree);
34 if (!doc) {
35 AddError(SerializeError::XML_CREATE_ERROR, "Failed to create XML document");
36 return false;
37 }
38
39 // Save to file with pretty printing
40 // Note: indent_size from metadata could be used with xmlSaveFormatFile if needed
41 int result = xmlSaveFormatFileEnc(filepath.c_str(), doc, "UTF-8", 1);
42
43 // Cleanup
44 xmlFreeDoc(doc);
45
46 if (result == -1) {
47 AddError(SerializeError::FILE_WRITE_ERROR, "Failed to write XML file: " + filepath);
48 return false;
49 }
50
51 LOG_INFO("LibXMLSerializer", "Successfully serialized tree to: " + filepath);
52 return true;
53}
54
57
58 if (!tree) {
59 AddError(SerializeError::INVALID_TREE, "Behavior tree is null");
60 return "";
61 }
62
63 if (!tree->HasRootNode()) {
64 AddError(SerializeError::MISSING_ROOT_NODE, "Behavior tree has no root node");
65 return "";
66 }
67
68 // Create XML document
69 xmlDocPtr doc = CreateXMLDocument(tree);
70 if (!doc) {
71 AddError(SerializeError::XML_CREATE_ERROR, "Failed to create XML document");
72 return "";
73 }
74
75 // Convert to string
76 xmlChar *xml_buffer = nullptr;
77 int buffer_size = 0;
78 xmlDocDumpFormatMemoryEnc(doc, &xml_buffer, &buffer_size, "UTF-8", 1);
79
80 EmberCore::String result;
81 if (xml_buffer) {
82 result = reinterpret_cast<const char *>(xml_buffer);
83 xmlFree(xml_buffer);
84 }
85
86 // Cleanup
87 xmlFreeDoc(doc);
88
89 return result;
90}
91
92xmlDocPtr LibXMLBehaviorTreeSerializer::CreateXMLDocument(std::shared_ptr<BehaviorTree> tree) {
93 const auto &doc_config = config_.GetDocumentConfig();
94
95 // Create new XML document
96 xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
97 if (!doc) {
98 return nullptr;
99 }
100
101 // Create root element
102 xmlNodePtr root = xmlNewNode(nullptr, BAD_CAST doc_config.root_element.c_str());
103 xmlDocSetRootElement(doc, root);
104
105 // Add main_tree_to_execute attribute to root
106 EmberCore::String tree_name = tree->GetName();
107 if (!tree_name.empty()) {
108 xmlNewProp(root, BAD_CAST doc_config.main_tree_attribute.c_str(), BAD_CAST tree_name.c_str());
109 }
110
111 // Add header comments
112 const auto &xml_metadata = tree->GetXMLMetadata();
113 for (const auto &comment : xml_metadata.header_comments) {
114 xmlNodePtr comment_node = xmlNewComment(BAD_CAST comment.c_str());
115 xmlAddPrevSibling(root, comment_node);
116 }
117
118 // Serialize the behavior tree
119 SerializeBehaviorTree(tree, root);
120
121 // Add footer comments
122 for (const auto &comment : xml_metadata.footer_comments) {
123 xmlNodePtr comment_node = xmlNewComment(BAD_CAST comment.c_str());
124 xmlAddSibling(root, comment_node);
125 }
126
127 return doc;
128}
129
130xmlNodePtr LibXMLBehaviorTreeSerializer::SerializeBehaviorTree(std::shared_ptr<BehaviorTree> tree, xmlNodePtr parent) {
131 const auto &tree_config = config_.GetTreeConfig();
132
133 // Create BehaviorTree element
134 xmlNodePtr tree_node = xmlNewChild(parent, nullptr, BAD_CAST tree_config.behavior_tree_element.c_str(), nullptr);
135
136 // Add tree ID attribute
137 EmberCore::String tree_name = tree->GetName();
138 if (tree_name.empty()) {
139 tree_name = "MainTree";
140 }
141 xmlNewProp(tree_node, BAD_CAST tree_config.tree_id_attribute.c_str(), BAD_CAST tree_name.c_str());
142
143 // Serialize root node
144 // Note: The parser creates a BehaviorTree wrapper node that contains the actual
145 // root control node as its child. We need to skip this wrapper and serialize
146 // its children directly to produce correct round-trip serialization.
147 Node *root = tree->GetRootNode();
148 if (root) {
149 if (root->GetType() == Node::Type::BehaviorTree) {
150 // This is the BehaviorTree wrapper node - serialize its children directly
151 for (size_t i = 0; i < root->GetChildCount(); ++i) {
152 Node *child = root->GetChild(i);
153 if (child) {
154 SerializeNode(child, tree_node);
155 }
156 }
157 } else {
158 // No wrapper, serialize the root node directly
159 SerializeNode(root, tree_node);
160 }
161 }
162
163 // Serialize blackboards
164 const auto &blackboards = tree->GetBlackboards();
165 for (const auto &bb_pair : blackboards) {
166 SerializeBlackboard(bb_pair.second.get(), parent);
167 }
168
169 return tree_node;
170}
171
172xmlNodePtr LibXMLBehaviorTreeSerializer::SerializeNode(Node *node, xmlNodePtr parent) {
173 if (!node) {
174 return nullptr;
175 }
176
177 // Get element name based on node type
178 EmberCore::String element_name = GetElementNameForNode(node);
179
180 // Create XML node
181 xmlNodePtr xml_node = xmlNewChild(parent, nullptr, BAD_CAST element_name.c_str(), nullptr);
182
183 const auto &node_config = config_.GetNodeConfig();
184 const auto &tree_config = config_.GetTreeConfig();
185
186 // Check if this is a SubTree placeholder node
187 EmberCore::String subtree_ref = node->GetAttribute("__subtree_ref__");
188 if (!subtree_ref.empty()) {
189 // SubTree node - use the subtree reference as the ID
190 xmlNewProp(xml_node, BAD_CAST tree_config.subtree_reference_attribute.c_str(), BAD_CAST subtree_ref.c_str());
191 // SubTree nodes don't have children or regular attributes - they're just references
192 return xml_node;
193 }
194
195 // Add node ID attribute (if configured)
196 if (!node_config.node_id_attribute.empty()) {
197 // Get the specific behavior type from the stored ID attribute (set during parsing)
198 // This should always be present for properly parsed nodes (e.g., "Sequence", "SetInt")
199 EmberCore::String node_type_id = node->GetAttribute("ID");
200 if (!node_type_id.empty()) {
201 xmlNewProp(xml_node, BAD_CAST node_config.node_id_attribute.c_str(), BAD_CAST node_type_id.c_str());
202 } else {
203 // ID is missing - this shouldn't happen for parsed nodes
204 // Log a warning and skip the ID attribute
205 LOG_WARNING("LibXMLSerializer", "Node '" + node->GetName() + "' has no ID attribute - skipping");
206 }
207 }
208
209 // Add node name attribute
210 xmlNewProp(xml_node, BAD_CAST node_config.node_name_attribute.c_str(), BAD_CAST node->GetName().c_str());
211
212 // Serialize all custom attributes
213 SerializeAttributes(node, xml_node);
214
215 // Serialize children recursively
216 for (size_t i = 0; i < node->GetChildCount(); ++i) {
217 Node *child = node->GetChild(i);
218 if (child) {
219 SerializeNode(child, xml_node);
220 }
221 }
222
223 return xml_node;
224}
225
227 const auto &node_config = config_.GetNodeConfig();
228
229 // Get all custom attributes from the node
230 const auto &attributes = node->GetAllAttributes();
231
232 for (const auto &attr_pair : attributes) {
233 // Skip the name and ID attributes as they're already added explicitly
234 if (attr_pair.first == node_config.node_name_attribute || attr_pair.first == node_config.node_id_attribute) {
235 continue;
236 }
237
238 // Skip internal parser attributes (these are implementation details, not real XML attributes)
239 if (attr_pair.first == "__subtree_ref__" || attr_pair.first == "__is_placeholder__") {
240 continue;
241 }
242
243 // Add attribute to XML node
244 xmlNewProp(xml_node, BAD_CAST attr_pair.first.c_str(), BAD_CAST attr_pair.second.c_str());
245 }
246}
247
248xmlNodePtr LibXMLBehaviorTreeSerializer::SerializeBlackboard(Blackboard *blackboard, xmlNodePtr parent) {
249 if (!blackboard) {
250 return nullptr;
251 }
252
253 const auto &blackboard_config = config_.GetBlackboardConfig();
254
255 // Create Blackboard element
256 xmlNodePtr bb_node = xmlNewChild(parent, nullptr, BAD_CAST blackboard_config.blackboard_element.c_str(), nullptr);
257
258 // Add blackboard ID using configured attribute name
259 xmlNewProp(bb_node, BAD_CAST blackboard_config.blackboard_id_attribute.c_str(),
260 BAD_CAST blackboard->GetId().c_str());
261
262 // Serialize entries
263 const auto &entries = blackboard->GetEntries();
264 for (const auto &entry_pair : entries) {
265 xmlNodePtr entry_node =
266 xmlNewChild(bb_node, nullptr, BAD_CAST blackboard_config.entry_element.c_str(), nullptr);
267
268 xmlNewProp(entry_node, BAD_CAST blackboard_config.entry_key_attribute.c_str(),
269 BAD_CAST entry_pair.first.c_str());
270 xmlNewProp(entry_node, BAD_CAST blackboard_config.entry_type_attribute.c_str(),
271 BAD_CAST entry_pair.second->GetTypeString().c_str());
272
273 // Add value as attribute (only if not empty)
274 const EmberCore::String &value = entry_pair.second->GetValue();
275 if (!value.empty()) {
276 xmlNewProp(entry_node, BAD_CAST blackboard_config.entry_value_attribute.c_str(), BAD_CAST value.c_str());
277 }
278 }
279
280 return bb_node;
281}
282
284 errors_.emplace_back(type, message);
285 LOG_ERROR("LibXMLSerializer", message);
286}
287
289 if (!node) {
290 return "";
291 }
292
293 const auto &node_config = config_.GetNodeConfig();
294 const auto &tree_config = config_.GetTreeConfig();
295
296 // Check if this is a SubTree placeholder node (has __subtree_ref__ attribute)
297 if (node->GetType() == Node::Type::BehaviorTree) {
298 EmberCore::String subtree_ref = node->GetAttribute("__subtree_ref__");
299 if (!subtree_ref.empty()) {
300 return tree_config.subtree_element; // "SubTree"
301 }
302 // Non-placeholder BehaviorTree nodes shouldn't reach here (they're skipped in SerializeBehaviorTree)
303 LOG_WARNING("LibXMLSerializer", "Unexpected BehaviorTree node in serialization: " + node->GetName());
304 return node_config.generic_node_element;
305 }
306
307 // Always return the generic category name based on node type
308 // The specific behavior is stored in the ID attribute, not the element name
309 switch (node->GetType()) {
311 return node_config.action_element; // "Action"
313 return node_config.control_element; // "Control"
315 return node_config.condition_element; // "Condition"
317 return node_config.decorator_element; // "Decorator"
318 default:
319 return node_config.generic_node_element; // "Node"
320 }
321}
322
323} // namespace EmberCore
#define LOG_ERROR(category, message)
Definition Logger.h:116
#define LOG_WARNING(category, message)
Definition Logger.h:115
#define LOG_INFO(category, message)
Definition Logger.h:114
Represents a blackboard containing multiple entries.
const EmberCore::String & GetId() const
const std::map< EmberCore::String, std::unique_ptr< BlackboardEntry > > & GetEntries() const
void SerializeAttributes(Node *node, xmlNodePtr xml_node)
Serialize node attributes.
LibXMLBehaviorTreeSerializer(const ParserConfig &config)
Constructor with parser configuration.
EmberCore::String SerializeToString(std::shared_ptr< BehaviorTree > tree)
Serialize behavior tree to string.
EmberCore::String GetElementNameForNode(Node *node) const
Get node element name based on node type and config.
xmlDocPtr CreateXMLDocument(std::shared_ptr< BehaviorTree > tree)
Create XML document from behavior tree.
xmlNodePtr SerializeBehaviorTree(std::shared_ptr< BehaviorTree > tree, xmlNodePtr parent)
Serialize a single behavior tree to XML node.
xmlNodePtr SerializeNode(Node *node, xmlNodePtr parent)
Recursively serialize a node and its children.
bool SerializeToFile(std::shared_ptr< BehaviorTree > tree, const EmberCore::String &filepath)
Serialize behavior tree to file.
void AddError(SerializeError::Type type, const EmberCore::String &message)
Error handling.
xmlNodePtr SerializeBlackboard(Blackboard *blackboard, xmlNodePtr parent)
Serialize blackboard.
Represents a node in a behavior tree structure.
Definition Node.h:20
const std::map< String, String > & GetAllAttributes() const
Definition Node.cpp:462
String GetAttribute(const String &name, const String &default_value="") const
Definition Node.cpp:451
Node * GetChild(size_t index) const
Definition Node.cpp:175
Type GetType() const
Definition Node.h:84
const String & GetName() const
Definition Node.h:81
size_t GetChildCount() const
Definition Node.h:76
Configuration for XML parser behavior and element/attribute mappings.
Main types header for EmberCore.
std::string String
Framework-agnostic string type.
Definition String.h:14