source: finroc_plugins_parameters/tConfigFile.cpp @ 87:495d52531f18

17.03
Last change on this file since 87:495d52531f18 was 87:495d52531f18, checked in by Max Reichardt <max.reichardt@…>, 4 years ago

Prevents reordering of inserted XML nodes when including config files

File size: 18.8 KB
Line 
1//
2// You received this file as part of Finroc
3// A framework for intelligent robot control
4//
5// Copyright (C) Finroc GbR (finroc.org)
6//
7// This program is free software; you can redistribute it and/or modify
8// it under the terms of the GNU General Public License as published by
9// the Free Software Foundation; either version 2 of the License, or
10// (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15// GNU General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License along
18// with this program; if not, write to the Free Software Foundation, Inc.,
19// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20//
21//----------------------------------------------------------------------
22/*!\file    plugins/parameters/tConfigFile.cpp
23 *
24 * \author  Max Reichardt
25 *
26 * \date    2012-11-28
27 *
28 */
29//----------------------------------------------------------------------
30#include "plugins/parameters/tConfigFile.h"
31
32//----------------------------------------------------------------------
33// External includes (system with <>, local with "")
34//----------------------------------------------------------------------
35#include "rrlib/rtti/rtti.h"
36#include "core/file_lookup.h"
37
38//----------------------------------------------------------------------
39// Internal includes with ""
40//----------------------------------------------------------------------
41#include "plugins/parameters/internal/tParameterInfo.h"
42
43//----------------------------------------------------------------------
44// Debugging
45//----------------------------------------------------------------------
46#include <cassert>
47
48//----------------------------------------------------------------------
49// Namespace usage
50//----------------------------------------------------------------------
51
52//----------------------------------------------------------------------
53// Namespace declaration
54//----------------------------------------------------------------------
55namespace finroc
56{
57namespace parameters
58{
59
60//----------------------------------------------------------------------
61// Forward declarations / typedefs / enums
62//----------------------------------------------------------------------
63#ifdef _LIB_RRLIB_XML_PRESENT_
64typedef rrlib::xml::tNode tXMLNode;
65
66//----------------------------------------------------------------------
67// Const values
68//----------------------------------------------------------------------
69/*! Separator entries are divided with */
70static const std::string cSEPARATOR("/");
71
72/*! Branch name in XML */
73static const std::string cXML_BRANCH_NAME("node");
74
75/*! Leaf name in XML */
76static const std::string cXML_LEAF_NAME("value");
77
78/*! Override attribute name in config files */
79static const std::string cXML_OVERRIDE_ATTRIBUTE_NAME("override");
80
81#endif
82
83/*! Initializes annotation type so that it can be transferred to finstruct */
84__attribute__((unused))
85static rrlib::rtti::tDataType<tConfigFile> cTYPE;
86
87//----------------------------------------------------------------------
88// Implementation
89//----------------------------------------------------------------------
90
91namespace
92{
93
94#ifdef _LIB_RRLIB_XML_PRESENT_
95
96rrlib::xml::tDocument LoadConfigFile(const std::string& file);
97
98bool ProcessIncludes(rrlib::xml::tNode& node, const std::string& source_file)
99{
100  if (node.Name() == "include")
101  {
102    if (!node.HasAttribute("file"))
103    {
104      FINROC_LOG_PRINT_STATIC(DEBUG_WARNING, "Found 'include' node without 'file' attribute in '", source_file, "'");
105    }
106    else
107    {
108      std::string file_name = node.GetStringAttribute("file");
109      try
110      {
111        rrlib::xml::tDocument insert = LoadConfigFile(file_name);
112        auto& parent = node.Parent();
113        rrlib::xml::tNode* insert_after = &node;
114        for (auto it = insert.RootNode().ChildrenBegin(); it != insert.RootNode().ChildrenEnd(); ++it)
115        {
116          insert_after = &insert_after->AddNextSibling(*it, true); // not using copy resulted in erroneous behavior
117        }
118        parent.RemoveChildNode(node);
119        return true;
120      }
121      catch (const std::exception& e)
122      {
123        FINROC_LOG_PRINT(ERROR, "Error including '", file_name, "' in config file: ", e);
124      }
125    }
126  }
127
128  long int index = 0;
129  for (auto it = node.ChildrenBegin(); it != node.ChildrenEnd();)
130  {
131    long int number_of_children = node.GetNumberOfChildren();
132    if (ProcessIncludes(*it, source_file))
133    {
134      // Iterator could be invalid -> reinitialize
135      long int size_difference = node.GetNumberOfChildren() - number_of_children;
136      assert(size_difference >= -1);
137      index += (size_difference + 1);
138      it = node.ChildrenBegin();
139      for (auto i = 0; i < index; i++)
140      {
141        ++it;
142      }
143    }
144    else
145    {
146      ++index;
147      ++it;
148    }
149  }
150  return false;
151}
152
153void ProcessIncludes(rrlib::xml::tDocument& document, const std::string& source_file)
154{
155  ProcessIncludes(document.RootNode(), source_file);
156}
157
158rrlib::xml::tDocument LoadConfigFile(const std::string& filename)
159{
160  rrlib::xml::tDocument result = core::GetFinrocXMLDocument(filename, false); // false = do not validate with dtd
161  ProcessIncludes(result, filename);
162  //FINROC_LOG_PRINT(DEBUG, result.RootNode().GetXMLDump(true));
163  return result;
164}
165
166bool OverrideContext(rrlib::xml::tNode& node, bool in_override_context)
167{
168  if (node.HasAttribute(cXML_OVERRIDE_ATTRIBUTE_NAME))
169  {
170    return node.GetBoolAttribute(cXML_OVERRIDE_ATTRIBUTE_NAME);
171  }
172  return in_override_context;
173}
174
175/*! Result of GetEntryImplementation function */
176struct GetEntryImplementationResult
177{
178  struct tEntry
179  {
180    rrlib::xml::tNode* node;
181    bool overrides;
182  };
183  tEntry first_entry, last_override_entry;
184  size_t entry_count = 0, override_entry_count = 0;
185};
186
187/*!
188 * Implementation of GetEntry() - called recursively
189 *
190 * \param result Object that will contain the result(s) as additional entry/entries after the call
191 * \param entry Entry
192 * \param node Current node
193 * \param entry_string_index Current index in entry string
194 * \param override_context Whether override="true" has been set in any parent now
195 */
196void GetEntryImplementation(GetEntryImplementationResult& result, const std::string& entry, rrlib::xml::tNode& node, size_t entry_string_index, bool override_context = false)
197{
198  if (entry_string_index >= entry.length())
199  {
200    return;
201  }
202
203  // Check for slash at beginning
204  if (entry[entry_string_index] == '/')
205  {
206    if (entry_string_index > 0)
207    {
208      FINROC_LOG_PRINT_STATIC(WARNING, "Entry '", entry, "' seems to be not clean (sequential slashes). Skipping one slash now, as this is typically intended. Please fix this!");
209    }
210    entry_string_index++;
211  }
212
213  // Search child nodes
214  for (rrlib::xml::tNode::iterator child = node.ChildrenBegin(); child != node.ChildrenEnd(); ++child)
215  {
216    if (child->Name() == cXML_BRANCH_NAME || child->Name() == cXML_LEAF_NAME)
217    {
218      std::string name_attribute;
219      try
220      {
221        name_attribute = child->GetStringAttribute("name");
222      }
223      catch (const std::exception& e)
224      {
225        FINROC_LOG_PRINT_STATIC(WARNING, "Encountered tree node without name");
226      }
227
228      try
229      {
230        if (entry.compare(entry_string_index, name_attribute.length(), name_attribute) == 0) // starts_with name attribute?
231        {
232          size_t new_entry_string_index = entry_string_index + name_attribute.length();
233          if (new_entry_string_index != entry.length())
234          {
235            if (entry[new_entry_string_index] == '/')
236            {
237              new_entry_string_index++;
238              GetEntryImplementation(result, entry, *child, new_entry_string_index, OverrideContext(*child, override_context));
239            }
240          }
241          else
242          {
243            // Entry found - include in result
244            GetEntryImplementationResult::tEntry new_entry = { &(*child), OverrideContext(*child, override_context) };
245            if (!result.entry_count)
246            {
247              result.first_entry = new_entry;
248              if (new_entry.overrides)
249              {
250                FINROC_LOG_PRINT_STATIC(WARNING, "First config entry with path '", entry, "' marked 'override', but does not override.");
251              }
252            }
253            if (new_entry.overrides)
254            {
255              result.last_override_entry = new_entry;
256            }
257            result.entry_count++;
258            result.override_entry_count += (new_entry.overrides ? 1 : 0);
259          }
260        }
261      }
262      catch (const std::exception& e)
263      {
264        FINROC_LOG_PRINT_STATIC(ERROR, "Error looking for config entry with path '", entry, "'. Reason: ", e.what());
265      }
266    }
267  }
268}
269
270rrlib::xml::tNode* ChooseNodeFromResult(GetEntryImplementationResult& result, const std::string& path)
271{
272  if (result.entry_count > 1)
273  {
274    assert(result.entry_count >= result.override_entry_count);
275    if (!result.override_entry_count)
276    {
277      FINROC_LOG_PRINT_STATIC(WARNING, "There are ", result.entry_count, " entries in config file with the path '", path, "'. Using the first one. Hint: Add attribute 'override=\"true\"' to nodes and values if the later entries are intended be used. This will also remove any warning messages.");
278    }
279    else if (result.entry_count - result.override_entry_count > 1)
280    {
281      FINROC_LOG_PRINT_STATIC(WARNING, "There are ", result.entry_count, " entries in config file with the path '", path, "'. Only ", result.override_entry_count, " are marked 'override'. Using the last one marked 'override'.");
282    }
283    return result.override_entry_count ? result.last_override_entry.node : result.first_entry.node;
284  }
285  return result.entry_count ? result.first_entry.node : nullptr;
286}
287
288#endif
289
290}
291
292tConfigFile::tConfigFile() :
293#ifdef _LIB_RRLIB_XML_PRESENT_
294  wrapped(),
295#endif
296  filename(),
297  active(true)
298{
299#ifdef _LIB_RRLIB_XML_PRESENT_
300  wrapped.AddRootNode(cXML_BRANCH_NAME);
301#endif
302}
303
304tConfigFile::tConfigFile(const std::string& filename, bool optional) :
305#ifdef _LIB_RRLIB_XML_PRESENT_
306  wrapped(),
307#endif
308  filename(filename),
309  active(true)
310{
311#ifdef _LIB_RRLIB_XML_PRESENT_
312  if (core::FinrocFileExists(filename))
313  {
314    try
315    {
316      wrapped = LoadConfigFile(filename);
317      return;
318    }
319    catch (const std::exception& e)
320    {
321      FINROC_LOG_PRINT(ERROR, e);
322    }
323  }
324  else if (!optional)
325  {
326    FINROC_LOG_PRINT(WARNING, "Specified config file not found: ", filename);
327  }
328  wrapped = rrlib::xml::tDocument();
329  wrapped.AddRootNode(cXML_BRANCH_NAME);
330#endif
331}
332
333void tConfigFile::Append(const std::string& filename)
334{
335#ifdef _LIB_RRLIB_XML_PRESENT_
336  if (core::FinrocFileExists(filename))
337  {
338    // merge entries into first document
339    auto document = LoadConfigFile(filename);
340    auto& root_node = document.RootNode();
341    for (auto it = root_node.ChildrenBegin(); it != root_node.ChildrenEnd(); ++it)
342    {
343      this->wrapped.RootNode().AddChildNode(*it, true); // not using copy resulted in erroneous behavior
344    }
345  }
346  else
347  {
348    throw std::runtime_error("Specified config file not found: " + filename);
349  }
350#endif
351}
352
353#ifdef _LIB_RRLIB_XML_PRESENT_
354rrlib::xml::tNode& tConfigFile::CreateEntry(const std::string& entry, bool leaf)
355{
356  if (!leaf)
357  {
358    GetEntryImplementationResult result;
359    GetEntryImplementation(result, entry, wrapped.RootNode(), 0);
360    rrlib::xml::tNode* result_node = ChooseNodeFromResult(result, entry);
361    if (result_node)
362    {
363      // do we want to warn if node is a leaf node? - I currently do not think so
364      return *result_node;
365    }
366  }
367
368  size_t slash_index = entry.rfind('/');
369  tXMLNode& parent = (slash_index == std::string::npos || slash_index == 0) ? wrapped.RootNode() : CreateEntry(entry.substr(0, slash_index), false);
370  tXMLNode& created = parent.AddChildNode(leaf ? cXML_LEAF_NAME : cXML_BRANCH_NAME);
371  created.SetAttribute("name", (slash_index == std::string::npos) ? entry : entry.substr(slash_index + 1));
372  return created;
373}
374#endif
375
376tConfigFile* tConfigFile::Find(const core::tFrameworkElement& element)
377{
378  tConfigFile* config_file = element.GetAnnotation<tConfigFile>();
379  if (config_file && config_file->active == true)
380  {
381    return config_file;
382  }
383  core::tFrameworkElement* parent = element.GetParent();
384  if (parent)
385  {
386    return Find(*parent);
387  }
388  return NULL;
389}
390
391#ifdef _LIB_RRLIB_XML_PRESENT_
392rrlib::xml::tNode& tConfigFile::GetEntry(const std::string& entry, bool create)
393{
394  GetEntryImplementationResult result;
395  GetEntryImplementation(result, entry, wrapped.RootNode(), 0);
396  rrlib::xml::tNode* result_node = ChooseNodeFromResult(result, entry);
397
398  if (!create)
399  {
400    if (!result_node)
401    {
402      throw std::runtime_error("Config node not found: " + entry);
403    }
404    if (result_node->Name() != cXML_LEAF_NAME)
405    {
406      throw std::runtime_error("Config node is no leaf: " + entry);
407    }
408    return *result_node;
409  }
410
411  // create node...
412  if (result.entry_count > 0)
413  {
414    // recreate existing node
415    std::string name = result_node->GetStringAttribute("name");
416    tXMLNode& parent = result_node->Parent();
417    tXMLNode& new_node = result_node->AddNextSibling(cXML_LEAF_NAME);
418    new_node.SetAttribute("name", name);
419    parent.RemoveChildNode(*result_node);
420    return new_node;
421  }
422  else
423  {
424    return CreateEntry(entry, true);
425  }
426}
427
428std::vector<std::string> tConfigFile::GetChildrenNames(const std::string &entry)
429{
430  GetEntryImplementationResult result;
431  GetEntryImplementation(result, entry, wrapped.RootNode(), 0);
432  rrlib::xml::tNode* result_node = ChooseNodeFromResult(result, entry);
433
434  if (!result_node)
435  {
436    throw std::runtime_error("Config node not found: " + entry);
437  }
438  if (result_node->Name() == cXML_LEAF_NAME)
439  {
440    throw std::runtime_error("Config node is a leaf: " + entry);
441  }
442
443  std::vector<std::string> children_names;
444  for (auto it = result_node->ChildrenBegin(); it != result_node->ChildrenEnd(); ++it)
445  {
446    children_names.push_back(it->GetStringAttribute("name"));
447  }
448  return children_names;
449}
450
451#endif
452
453std::string tConfigFile::GetStringEntry(const std::string& entry)
454{
455#ifdef _LIB_RRLIB_XML_PRESENT_
456  try
457  {
458    return GetEntry(entry).GetTextContent();
459  }
460  catch (const std::exception& e)
461  {
462    return "";
463  }
464#else
465  return "";
466#endif
467}
468
469bool tConfigFile::HasEntry(const std::string& entry)
470{
471#ifdef _LIB_RRLIB_XML_PRESENT_
472  // TODO: could be implemented more efficiently
473  try
474  {
475    GetEntry(entry);
476    return true;
477  }
478  catch (const std::exception& e)
479  {
480    return false;
481  }
482#else
483  return false;
484#endif
485}
486
487void tConfigFile::LoadParameterValues()
488{
489  LoadParameterValues(*GetAnnotated<core::tFrameworkElement>());
490}
491
492void tConfigFile::LoadParameterValues(core::tFrameworkElement& fe)
493{
494  rrlib::thread::tLock lock(fe.GetStructureMutex());  // nothing should change while we're doing this
495  for (auto it = fe.SubElementsBegin(true); it != fe.SubElementsEnd(); ++it)
496  {
497    if (it->IsPort() && it->IsReady() && Find(*it) == this)    // Does element belong to this configuration file?
498    {
499      internal::tParameterInfo* pi = it->GetAnnotation<internal::tParameterInfo>();
500      if (pi)
501      {
502        try
503        {
504          pi->LoadValue();
505        }
506        catch (const std::exception& e)
507        {
508          FINROC_LOG_PRINT_STATIC(ERROR, e);
509        }
510      }
511    }
512  }
513}
514
515void tConfigFile::Reload()
516{
517  if (core::FinrocFileExists(filename))
518  {
519    try
520    {
521      wrapped = LoadConfigFile(filename);
522      return;
523    }
524    catch (const std::exception& e)
525    {
526      FINROC_LOG_PRINT(ERROR, e);
527    }
528  }
529}
530
531void tConfigFile::SaveFile(const std::string& new_filename)
532{
533#ifdef _LIB_RRLIB_XML_PRESENT_
534  // first: update tree
535  core::tFrameworkElement* ann = GetAnnotated<core::tFrameworkElement>();
536  if (ann)
537  {
538    rrlib::thread::tLock lock(ann->GetStructureMutex()); // nothing should change while we're doing this
539    for (auto it = ann->SubElementsBegin(true); it != ann->SubElementsEnd(); ++it)
540    {
541      if (it->IsPort() && it->IsReady() && Find(*it) == this)    // Does element belong to this configuration file?
542      {
543        internal::tParameterInfo* pi = it->GetAnnotation<internal::tParameterInfo>();
544        if (pi)
545        {
546          try
547          {
548            pi->SaveValue();
549          }
550          catch (const std::exception& e)
551          {
552            FINROC_LOG_PRINT_STATIC(ERROR, e);
553          }
554        }
555      }
556    }
557  }
558
559  try
560  {
561    if (new_filename.length() > 0)
562    {
563      this->filename = new_filename;
564    }
565    std::string save_to = core::GetFinrocFileToSaveTo(this->filename);
566    if (save_to.length() == 0)
567    {
568      std::string save_to_alt = save_to;
569      std::replace(save_to_alt.begin(), save_to_alt.end(), '/', '_'); // Replace '/' characters with '_'
570      save_to_alt = core::GetFinrocFileToSaveTo(save_to_alt);
571      FINROC_LOG_PRINT(ERROR, "There does not seem to be any suitable location for: '", this->filename, "' . For now, using '", save_to_alt, "'.");
572      save_to = save_to_alt;
573    }
574
575    // write new tree to file
576    wrapped.WriteToFile(save_to);
577  }
578  catch (const std::exception& e)
579  {
580    FINROC_LOG_PRINT(ERROR, e);
581  }
582#endif
583}
584
585rrlib::serialization::tOutputStream& operator << (rrlib::serialization::tOutputStream& stream, const tConfigFile& config_file)
586{
587#ifdef _LIB_RRLIB_XML_PRESENT_
588  stream.WriteBoolean(config_file.IsActive());
589  stream.WriteString(config_file.GetFilename());
590
591  try
592  {
593    stream.WriteString(config_file.wrapped.RootNode().GetXMLDump());
594  }
595  catch (const std::exception& e)
596  {
597    FINROC_LOG_PRINT_STATIC(ERROR, e); // Should never occur
598  }
599#endif
600  return stream;
601}
602
603rrlib::serialization::tInputStream& operator >> (rrlib::serialization::tInputStream& stream, tConfigFile& config_file)
604{
605#ifdef _LIB_RRLIB_XML_PRESENT_
606  config_file.active = stream.ReadBoolean();
607  std::string file = stream.ReadString();
608  std::string content = stream.ReadString();
609
610  if (config_file.active && file.length() > 0 && content.length() == 0 && (file != config_file.filename))
611  {
612    // load file
613    if (core::FinrocFileExists(file))
614    {
615      try
616      {
617        config_file.wrapped = LoadConfigFile(file);
618      }
619      catch (const std::exception& e)
620      {
621        FINROC_LOG_PRINT_STATIC(ERROR, e);
622        config_file.wrapped = rrlib::xml::tDocument();
623        try
624        {
625          config_file.wrapped.AddRootNode(cXML_BRANCH_NAME);
626        }
627        catch (const std::exception& e1)
628        {
629          FINROC_LOG_PRINT_STATIC(ERROR, e1);
630        }
631      }
632    }
633    config_file.filename = file;
634  }
635  else if (config_file.active && content.length() > 0)
636  {
637    if (file.length() > 0)
638    {
639      config_file.filename = file;
640    }
641
642    try
643    {
644      config_file.wrapped = rrlib::xml::tDocument(content.c_str(), content.length() + 1);
645    }
646    catch (const std::exception& e)
647    {
648      FINROC_LOG_PRINT_STATIC(ERROR, e);
649    }
650  }
651#endif
652  return stream;
653}
654
655//----------------------------------------------------------------------
656// End of namespace declaration
657//----------------------------------------------------------------------
658}
659}
Note: See TracBrowser for help on using the repository browser.