BARE2D
XMLDataManager.cpp
Go to the documentation of this file.
1 #include "XMLDataManager.hpp"
2 
3 #include <algorithm>
4 #include <glm/glm.hpp>
5 #include <rapidxml/rapidxml_print.hpp> // For the overloaded << operator
6 #include <string>
7 #include <vector>
8 
9 #include "Filesystem.hpp"
10 #include "Logger.hpp"
11 
12 class string;
13 
14 namespace BARE2D {
15 Cache<std::string, Cache<unsigned int, XMLData>> XMLDataManager::m_storedData;
16 std::unordered_map<std::string, std::function<XMLData*()>> XMLDataManager::m_dataTypingFunctions;
17 
18 void XMLDataManager::loadXML(std::string filepath)
19 {
20  Logger::getInstance()->log("Beginning XML Data Load.");
21 
22  // Get all the different file names
23  std::vector<std::string> files = Filesystem::getFilesIn(filepath);
24  std::vector<std::string> xmlTypes{};
25 
26  for(unsigned int i = 0; i < files.size(); i++)
27  {
28  // Convert file names - (foobar.xml) - to data types - (foobar)
29  std::string fileNameNoExt = files[i].substr(0, files[i].size() - 4);
30  std::string fileNameExt = files[i].substr(files[i].size() - 4, 4);
31 
32  if(fileNameExt == ".xml")
33  {
34  // It ends with .xml, so it's probably an XML file. This is dirty and bad and we should probably do
35  // this better. But we won't.
36  xmlTypes.push_back(fileNameNoExt);
37  }
38  }
39 
40  // Now load each file.
41  for(unsigned int i = 0; i < xmlTypes.size(); i++)
42  {
43  std::ifstream file(filepath + "/" + xmlTypes[i] + ".xml");
44 
45  if(file.fail())
47  "Could not read from file: " + filepath + "/" + xmlTypes[i] + ".xml");
48 
49  readXMLData(file, xmlTypes[i]);
50 
51  file.close();
52  }
53 
54  Logger::getInstance()->log("XML Data Load Complete.");
55 }
56 
57 void XMLDataManager::readXMLData(std::ifstream& file, std::string dataType)
58 {
59  Logger::getInstance()->log("Starting XML data read from file");
60 
61  // Create an XML document for the file
62  rapidxml::xml_document<> doc;
63  // Read the file
64  std::string line;
65  std::string totalContent;
66  while(std::getline(file, line))
67  {
68  totalContent += line + "\n";
69  }
70  doc.parse<rapidxml::parse_full>((char*)totalContent.c_str());
71 
72  // Use the data-specific read function for every node found
73  for(rapidxml::xml_node<>* node = doc.first_node(); node; node = node->next_sibling())
74  {
75  if(node->type() != rapidxml::node_comment)
76  {
77  // Figure out what attributes we actually need to read, and add them to the data.
78  std::string nodeName = std::string(node->name());
79  // Find the function to create a new, unsliced datatype.
80  auto it = m_dataTypingFunctions.find(nodeName);
81  if(it == m_dataTypingFunctions.end())
82  {
83  throwFatalError(BAREError::XML_FAILURE, "Failed to construct XML data of type " + nodeName +
84  ". Is this type registered?");
85  }
86 
87  // Actually call the derived class' cloneType() function
88  XMLData* d = (it->second)();
89  d->nodeName = nodeName;
90 
91  d->read(node);
92 
93  addData(d);
94 
95  Logger::getInstance()->log("\tRead XML: " + d->nodeName + ": " + d->name + " (" +
96  std::to_string(d->id) + ")");
97  }
98  }
99 
100  Logger::getInstance()->log("Finished XML data read from file");
101 }
102 
103 void XMLDataManager::saveXML(std::string filepath)
104 {
105  Logger::getInstance()->log("Beginning XML Data Write.");
106 
107  for(auto& cache : m_storedData)
108  {
109  // For every cache of data, we need to write to their own file
110  std::string dataType = cache.first;
111 
112  // First, make sure the file exists and is empty
113  std::ofstream file(filepath + "/" + dataType + ".xml");
114 
115  // Make sure the file was created/opened A-okay.
116  if(file.fail())
118  "Could not save to file: " + filepath + "/" + dataType + ".xml");
119 
120  // Now actually write the data to the open file
121  writeXMLData(file, cache.second);
122 
123  // And finally close the file
124  file.close();
125  }
126 
127  Logger::getInstance()->log("XML Data Write Complete.");
128 }
129 
131 {
132  Logger::getInstance()->log("Starting XML data save to file");
133 
134  // Create an XML document for the file
135  rapidxml::xml_document<> doc;
136 
137  // Now just write every piece of data to the doc
138  for(auto& e : *data)
139  {
140  // Use the data-specific write function
141  e.second->write(&doc);
142 
143  Logger::getInstance()->log("\tWrote XML: " + e.second->nodeName + ": " + e.second->name + " (" +
144  std::to_string(e.second->id) + ")");
145  }
146 
147  // Actually write the XML doc to the file, using rapidXML's overloaded << operator.
148  file << doc;
149 
150  Logger::getInstance()->log("Finished XML data save to file");
151 }
152 
153 unsigned int XMLDataManager::getDataCount(std::string dataType)
154 {
155  return getDataCache(dataType)->getCount();
156 }
157 
159 {
161 
162  XMLData* dataPointer = c->findItem(data->id);
163 
164  if(dataPointer)
165  {
166  throwError(BAREError::XML_FAILURE, "Tried to add a data twice - " + data->name);
167  }
168 
169  // So it doesn't exist yet. Add it to the cache.
170  c->addItem(data->id, data);
171 }
172 
174 {
176 
177  c->setItem(data->id, data);
178 }
179 
180 template <> bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, std::string& variable)
181 {
182  // Find the node that has a name valueName
183  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
184 
185  // Make sure that such a node actually exists
186  if(!node)
187  {
188  return false;
189  }
190 
191  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
192  // value.
193  if(std::string(node->value()) != std::string(""))
194  {
195  variable = std::string(node->value());
196  }
197 
198  return true;
199 }
200 template <> bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, unsigned int& variable)
201 {
202  // Find the node that has a name valueName
203  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
204 
205  // Make sure that such a node actually exists
206  if(!node)
207  {
208  return false;
209  }
210 
211  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
212  // value.
213  if(std::string(node->value()) != std::string(""))
214  {
215  variable = std::stoi(std::string(node->value()));
216  }
217 
218  return true;
219 }
220 template <>
221 bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, std::vector<unsigned int>& variable)
222 {
223  // Find the node that has a name valueName
224  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
225 
226  // Make sure that such a node actually exists
227  if(!node)
228  {
229  return false;
230  }
231 
232  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
233  // value.
234  if(std::string(node->value()) != std::string(""))
235  {
236  variable = std::vector<unsigned int>{};
237 
238  std::string value = node->value();
239 
240  // Now we need to actually interpret the string
241  // First, figure out how many ints we have
242  unsigned int elementCount = std::count(value.begin(), value.end(), '{');
243 
244  // Now, for every element, actually read it!
245  for(unsigned int i = 0; i < elementCount; i++)
246  {
247  // Find the first substring that represents our vector value
248  std::string uintStr = value.substr(value.find('{') + 1, value.find('}') - value.find('{') - 1);
249 
250  // Interpret the string
251  unsigned int val = std::stoi(uintStr);
252 
253  // Actually add the values to the vector.
254  variable.emplace_back(val);
255 
256  // Change what our search range actually is.
257  value = value.substr(value.find('{') + 1, value.size() - value.find('{') - 1);
258  }
259  }
260 
261  return true;
262 }
263 template <> bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, int& variable)
264 {
265  // Find the node that has a name valueName
266  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
267 
268  // Make sure that such a node actually exists
269  if(!node)
270  {
271  return false;
272  }
273 
274  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
275  // value.
276  if(std::string(node->value()) != std::string(""))
277  {
278  variable = std::stoi(std::string(node->value()));
279  }
280 
281  return true;
282 }
283 template <> bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, float& variable)
284 {
285  // Find the node that has a name valueName
286  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
287 
288  // Make sure that such a node actually exists
289  if(!node)
290  {
291  return false;
292  }
293 
294  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
295  // value.
296  if(std::string(node->value()) != std::string(""))
297  {
298  variable = std::stof(std::string(node->value()));
299  }
300 
301  return true;
302 }
303 template <>
304 bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, std::vector<float>& variable)
305 {
306  // Find the node that has a name valueName
307  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
308 
309  // Make sure that such a node actually exists
310  if(!node)
311  {
312  return false;
313  }
314 
315  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
316  // value.
317  if(std::string(node->value()) != std::string(""))
318  {
319  variable = std::vector<float>{};
320 
321  std::string value = node->value();
322 
323  // Now we need to actually interpret the string
324  // First, figure out how many floats we have
325  unsigned int elementCount = std::count(value.begin(), value.end(), '{');
326 
327  // Now, for every element, actually read it!
328  for(unsigned int i = 0; i < elementCount; i++)
329  {
330  // Find the first substring that represents our vector value
331  std::string floatStr = value.substr(value.find('{') + 1, value.find('}') - value.find('{') - 1);
332 
333  // Interpret the string
334  float val = std::stof(floatStr);
335 
336  // Actually add the values to the vector.
337  variable.emplace_back(val);
338 
339  // Change what our search range actually is.
340  value = value.substr(value.find('{') + 1, value.size() - value.find('{') - 1);
341  }
342  }
343 
344  return true;
345 }
346 template <> bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, bool& variable)
347 {
348  // Find the node that has a name valueName
349  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
350 
351  // Make sure that such a node actually exists
352  if(!node)
353  {
354  return false;
355  }
356 
357  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
358  // value.
359  if(std::string(node->value()) != std::string(""))
360  {
361  variable = (std::string(node->value()) != "0");
362  }
363 
364  return true;
365 }
366 template <>
367 bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, std::vector<bool>& variable)
368 {
369  // Find the node that has a name valueName
370  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
371 
372  // Make sure that such a node actually exists
373  if(!node)
374  {
375  return false;
376  }
377 
378  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
379  // value.
380  if(std::string(node->value()) != std::string(""))
381  {
382  variable = std::vector<bool>{};
383 
384  std::string value = node->value();
385 
386  // Now we need to actually interpret the string
387  // First, figure out how many bools's we have
388  unsigned int elementCount = std::count(value.begin(), value.end(), '{');
389 
390  // Now, for every element, actually read it!
391  for(unsigned int i = 0; i < elementCount; i++)
392  {
393  // Find the first substring that represents our value
394  std::string boolStr = value.substr(value.find('{') + 1, value.find('}') - value.find('{') - 1);
395 
396  // Interpret the string
397  bool val = (boolStr != "0");
398 
399  // Actually add the values to the vector.
400  variable.emplace_back(val);
401 
402  // Change what our search range actually is.
403  value = value.substr(value.find('{') + 1, value.size() - value.find('{') - 1);
404  }
405  }
406 
407  return true;
408 }
409 template <> bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, glm::vec2& variable)
410 {
411  // Find the node that has a name valueName
412  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
413 
414  // Make sure that such a node actually exists
415  if(!node)
416  {
417  return false;
418  }
419 
420  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
421  // value.
422  if(std::string(node->value()) != std::string(""))
423  {
424  std::string value = node->value();
425 
426  // Interpret the string
427  float val0 = std::stof(value.substr(0, value.find(',')));
428  float val1 = std::stof(value.substr(value.find(',') + 1, value.size() - value.find(',') - 1));
429 
430  // Actually add the values to the vector.
431  variable = glm::vec2(val0, val1);
432  }
433 
434  return true;
435 }
436 template <>
437 bool XMLDataManager::readValue(rapidxml::xml_node<>* parent, std::string valueName, std::vector<glm::vec2>& variable)
438 {
439  // Find the node that has a name valueName
440  rapidxml::xml_node<>* node = parent->first_node(valueName.c_str());
441 
442  // Make sure that such a node actually exists
443  if(!node)
444  {
445  return false;
446  }
447 
448  // We're good if we reach here, so its time to just read the value and interpret it, as long as it actually has a
449  // value.
450  if(std::string(node->value()) != std::string(""))
451  {
452  variable = std::vector<glm::vec2>{};
453 
454  std::string value = node->value();
455 
456  // Now we need to actually interpret the string
457  // First, figure out how many vec2's we have
458  unsigned int elementCount = std::count(value.begin(), value.end(), '{');
459 
460  // Now, for every element, actually read it!
461  for(unsigned int i = 0; i < elementCount; i++)
462  {
463  // Find the first substring that represents our vector, and nip off the brackets on either end
464  std::string vec2Str = value.substr(value.find('{') + 1, value.find('}') - value.find('{') - 1);
465 
466  // Interpret the string
467  float val0 = std::stof(vec2Str.substr(0, vec2Str.find(',')));
468  float val1 =
469  std::stof(vec2Str.substr(vec2Str.find(',') + 1, vec2Str.size() - vec2Str.find(',') - 1));
470 
471  // Actually add the values to the vector.
472  variable.emplace_back(val0, val1);
473 
474  // Change what our search range actually is.
475  value = value.substr(value.find('}') + 1, value.size() - value.find('}') - 1);
476  }
477  }
478 
479  return true;
480 }
481 
483 {
484  Cache<unsigned int, XMLData>* cache = m_storedData[dataType];
485 
486  if(!cache)
487  {
488  Logger::getInstance()->log("Creating new cache for XML Data: " + dataType);
489  cache = m_storedData.createItem(dataType);
490  }
491  return cache;
492 }
493 
495 {
496  m_storedData.clear();
497  m_dataTypingFunctions.clear();
498 }
499 } // namespace BARE2D
BARE2D::XMLDataManager::readValue
static bool readValue(rapidxml::xml_node<> *parent, std::string valueName, T &variable)
Reads a value from a node. Templated for almost any primitive.
BARE2D::Filesystem::getFilesIn
static std::vector< std::string > getFilesIn(std::string path)
Gets all the names of files at the given path.
Definition: Filesystem.cpp:29
BARE2D::BAREError::XML_FAILURE
@ XML_FAILURE
BARE2D
Definition: App.cpp:13
BARE2D::Logger::getInstance
static Logger * getInstance()
Definition: Logger.cpp:34
BARE2D::XMLDataManager::setData
static void setData(XMLData *data)
Adds or overwrites data in the cache/sub-cache.
Definition: XMLDataManager.cpp:173
BARE2D::XMLData::name
std::string name
Definition: XMLDataTypes.hpp:150
BARE2D::Cache::findItem
T * findItem(S &key)
Finds an item based on a key.
BARE2D::XMLData::nodeName
std::string nodeName
Definition: XMLDataTypes.hpp:152
BARE2D::XMLDataManager::getDataCache
static Cache< unsigned int, XMLData > * getDataCache(std::string dataType)
Returns a cache for a certain type of data.
Definition: XMLDataManager.cpp:482
BARE2D::XMLDataManager::m_storedData
static Cache< std::string, Cache< unsigned int, XMLData > > m_storedData
Definition: XMLDataManager.hpp:107
BARE2D::Cache::addItem
bool addItem(S &key, T *item)
Simply adds an item.
BARE2D::XMLDataManager::loadXML
static void loadXML(std::string filepath)
Loads all of the files from the filepath that end in .xml to the caches. Does not clear caches.
Definition: XMLDataManager.cpp:18
BARE2D::XMLDataManager::m_dataTypingFunctions
static std::unordered_map< std::string, std::function< XMLData *()> > m_dataTypingFunctions
Definition: XMLDataManager.hpp:108
BARE2D::Logger::log
void log(std::string message, bool important=false)
Logs a message to a file and the terminal.
Definition: Logger.cpp:42
BARE2D::XMLDataManager::addData
static void addData(XMLData *data)
Adds a piece of data to the cache, in the appropriate sub-cache.
Definition: XMLDataManager.cpp:158
BARE2D::Cache::setItem
bool setItem(S &key, T *item)
Adds or overwrites an item.
BARE2D::XMLDataManager::getDataCount
static unsigned int getDataCount(std::string dataType)
Definition: XMLDataManager.cpp:153
BARE2D::XMLDataManager::clearCache
static void clearCache()
Clears the various caches and subcaches that the class uses. Useful for refreshes.
Definition: XMLDataManager.cpp:494
BARE2D::Cache
This is a skeleton cache class. This can only be used by the ResourceManager or other classes who act...
Definition: Cache.hpp:15
BARE2D::throwError
void throwError(BAREError err, std::string message)
Throws an error silently. Adds it to the pile.
Definition: BAREErrors.cpp:190
Filesystem.hpp
BARE2D::XMLDataManager::writeXMLData
static void writeXMLData(std::ofstream &file, Cache< unsigned int, XMLData > *data)
Writes a single subcache of data to a single file.
Definition: XMLDataManager.cpp:130
BARE2D::XMLData::id
unsigned int id
Definition: XMLDataTypes.hpp:151
XMLDataManager.hpp
BARE2D::XMLData::read
void read(rapidxml::xml_node<> *node)
Reads the entire piece of data from a given XML node.
Definition: XMLDataTypes.cpp:222
BARE2D::XMLDataManager::readXMLData
static void readXMLData(std::ifstream &file, std::string dataType)
Reads a single subcache from a single file.
Definition: XMLDataManager.cpp:57
BARE2D::XMLDataManager::saveXML
static void saveXML(std::string filepath)
Writes all of the currently cached data to the data's respective files in a folder at filepath....
Definition: XMLDataManager.cpp:103
BARE2D::throwFatalError
void throwFatalError(BAREError err, std::string message)
Throws an error (fatal). Also calls displayErrors and exits the program.
Definition: BAREErrors.cpp:178
Logger.hpp
BARE2D::XMLData
Holds all the very basic information for XML data. Designed to be a base class from which a user can ...
Definition: XMLDataTypes.hpp:108