]> git.mxchange.org Git - simgear.git/blob - simgear/props/props_io.cxx
Fixed a crash: the singleton needs to be instantiated the first time SGCommandMgr...
[simgear.git] / simgear / props / props_io.cxx
1 /**
2  * \file props_io.cxx
3  * Started Fall 2000 by David Megginson, david@megginson.com
4  * This code is released into the Public Domain.
5  *
6  * See props.html for documentation [replace with URL when available].
7  *
8  * $Id$
9  */
10
11 #ifdef HAVE_CONFIG_H
12 #  include <simgear_config.h>
13 #endif
14
15 #include <simgear/compiler.h>
16
17 #include <stdlib.h>     // atof() atoi()
18
19 #include <simgear/sg_inlines.h>
20 #include <simgear/debug/logstream.hxx>
21 #include <simgear/misc/sg_path.hxx>
22 #include <simgear/xml/easyxml.hxx>
23 #include <simgear/misc/ResourceManager.hxx>
24
25 #include "props.hxx"
26 #include "props_io.hxx"
27 #include "vectorPropTemplates.hxx"
28
29 #include <iostream>
30 #include <fstream>
31 #include <string>
32 #include <cstring>      // strcmp()
33 #include <vector>
34 #include <map>
35
36 using std::istream;
37 using std::ifstream;
38 using std::ostream;
39 using std::ofstream;
40 using std::string;
41 using std::vector;
42 using std::map;
43
44 using std::endl;
45
46 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
47
48
49 \f
50 ////////////////////////////////////////////////////////////////////////
51 // Property list visitor, for XML parsing.
52 ////////////////////////////////////////////////////////////////////////
53
54 class PropsVisitor : public XMLVisitor
55 {
56 public:
57
58   PropsVisitor (SGPropertyNode * root, const string &base, int default_mode = 0,
59                 bool extended = false)
60     : _default_mode(default_mode), _root(root), _level(0), _base(base),
61       _hasException(false), _extended(extended)
62   {}
63
64   virtual ~PropsVisitor () {}
65
66   void startXML ();
67   void endXML ();
68   void startElement (const char * name, const XMLAttributes &atts);
69   void endElement (const char * name);
70   void data (const char * s, int length);
71   void warning (const char * message, int line, int column);
72
73   bool hasException () const { return _hasException; }
74   sg_io_exception &getException () { return _exception; }
75   void setException (const sg_io_exception &exception) {
76     _exception = exception;
77     _hasException = true;
78   }
79
80 private:
81
82   struct State
83   {
84     State () : node(0), type(""), mode(DEFAULT_MODE), omit(false) {}
85     State (SGPropertyNode * _node, const char * _type, int _mode, bool _omit)
86       : node(_node), type(_type), mode(_mode), omit(_omit) {}
87     SGPropertyNode * node;
88     string type;
89     int mode;
90     bool omit;
91     map<string,int> counters;
92   };
93
94   State &state () { return _state_stack[_state_stack.size() - 1]; }
95
96   void push_state (SGPropertyNode * node, const char * type, int mode, bool omit = false) {
97     if (type == 0)
98       _state_stack.push_back(State(node, "unspecified", mode, omit));
99     else
100       _state_stack.push_back(State(node, type, mode, omit));
101     _level++;
102     _data = "";
103   }
104
105   void pop_state () {
106     _state_stack.pop_back();
107     _level--;
108   }
109
110   int _default_mode;
111   string _data;
112   SGPropertyNode * _root;
113   SGPropertyNode null;
114   int _level;
115   vector<State> _state_stack;
116   string _base;
117   sg_io_exception _exception;
118   bool _hasException;
119   bool _extended;
120 };
121
122 void
123 PropsVisitor::startXML ()
124 {
125   _level = 0;
126   _state_stack.resize(0);
127 }
128
129 void
130 PropsVisitor::endXML ()
131 {
132   _level = 0;
133   _state_stack.resize(0);
134 }
135
136
137 /**
138  * Check a yes/no flag, with default.
139  */
140 static bool
141 checkFlag (const char * flag, const sg_location& location,
142            bool defaultState = true)
143 {
144   if (flag == 0)
145     return defaultState;
146   else if (!strcmp(flag, "y"))
147     return true;
148   else if (!strcmp(flag, "n"))
149     return false;
150   else {
151     string message = "Unrecognized flag value '";
152     message += flag;
153     message += '\'';
154                                 // FIXME: add location info
155     throw sg_io_exception(message, location, "SimGear Property Reader");
156   }
157 }
158
159 void
160 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
161 {
162   const char * attval;
163   const sg_location location(getPath(), getLine(), getColumn());
164
165   if (_level == 0) {
166     if (strcmp(name, "PropertyList")) {
167       string message = "Root element name is ";
168       message += name;
169       message += "; expected PropertyList";
170       throw sg_io_exception(message, location, "SimGear Property Reader");
171     }
172
173                                 // Check for an include.
174     attval = atts.getValue("include");
175     if (attval != 0) {
176       try {
177           SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
178           if (path.isNull())
179           {
180               string message ="Cannot open file ";
181               message += attval;
182               throw sg_io_exception(message, location,
183                                     "SimGear Property Reader");
184           }
185           readProperties(path.str(), _root, 0, _extended);
186       } catch (sg_io_exception &e) {
187           setException(e);
188       }
189     }
190
191     push_state(_root, "", DEFAULT_MODE);
192   }
193
194   else {
195     State &st = state();
196                                 // Get the index.
197     attval = atts.getValue("n");
198     int index = 0;
199     string strName(name);
200     if (attval != 0) {
201       index = atoi(attval);
202       st.counters[strName] = SG_MAX2(st.counters[strName], index+1);
203     } else {
204       index = st.counters[strName];
205       st.counters[strName]++;
206     }
207
208                                 // Got the index, so grab the node.
209     SGPropertyNode * node = st.node->getChild(strName, index, true);
210     if (!node->getAttribute(SGPropertyNode::WRITE)) {
211       SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property "
212              << node->getPath(true) << "\n at " << location.asString());
213       node = &null;
214     }
215
216                                 // Get the access-mode attributes,
217                                 // but don't set yet (in case they
218                                 // prevent us from recording the value).
219     int mode = _default_mode;
220
221     attval = atts.getValue("read");
222     if (checkFlag(attval, location, true))
223       mode |= SGPropertyNode::READ;
224     attval = atts.getValue("write");
225     if (checkFlag(attval, location, true))
226       mode |= SGPropertyNode::WRITE;
227     attval = atts.getValue("archive");
228     if (checkFlag(attval, location, false))
229       mode |= SGPropertyNode::ARCHIVE;
230     attval = atts.getValue("trace-read");
231     if (checkFlag(attval, location, false))
232       mode |= SGPropertyNode::TRACE_READ;
233     attval = atts.getValue("trace-write");
234     if (checkFlag(attval, location, false))
235       mode |= SGPropertyNode::TRACE_WRITE;
236     attval = atts.getValue("userarchive");
237     if (checkFlag(attval, location, false))
238       mode |= SGPropertyNode::USERARCHIVE;
239     attval = atts.getValue("preserve");
240     if (checkFlag(attval, location, false))
241       mode |= SGPropertyNode::PRESERVE;
242
243                                 // Check for an alias.
244     attval = atts.getValue("alias");
245     if (attval != 0) {
246       if (!node->alias(attval))
247         SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval
248                << "\n at " << location.asString());
249     }
250
251                                 // Check for an include.
252     bool omit = false;
253     attval = atts.getValue("include");
254     if (attval != 0) {
255       try {
256           SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
257           if (path.isNull())
258           {
259               string message ="Cannot open file ";
260               message += attval;
261               throw sg_io_exception(message, location,
262                                     "SimGear Property Reader");
263           }
264           readProperties(path.str(), node, 0, _extended);
265       } catch (sg_io_exception &e) {
266           setException(e);
267       }
268
269       attval = atts.getValue("omit-node");
270       omit = checkFlag(attval, location, false);
271     }
272
273     const char *type = atts.getValue("type");
274     // if a type is given and the node is tied,
275     // don't clear the value because
276     // clearValue() unties the property
277     if (type && false == node->isTied() )
278       node->clearValue();
279     push_state(node, type, mode, omit);
280   }
281 }
282
283 void
284 PropsVisitor::endElement (const char * name)
285 {
286   State &st = state();
287   bool ret;
288   const sg_location location(getPath(), getLine(), getColumn());
289
290                                 // If there are no children and it's
291                                 // not an alias, then it's a leaf value.
292   if (st.node->nChildren() == 0 && !st.node->isAlias()) {
293     if (st.type == "bool") {
294       if (_data == "true" || atoi(_data.c_str()) != 0)
295         ret = st.node->setBoolValue(true);
296       else
297         ret = st.node->setBoolValue(false);
298     } else if (st.type == "int") {
299       ret = st.node->setIntValue(atoi(_data.c_str()));
300     } else if (st.type == "long") {
301       ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
302     } else if (st.type == "float") {
303       ret = st.node->setFloatValue(atof(_data.c_str()));
304     } else if (st.type == "double") {
305       ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
306     } else if (st.type == "string") {
307       ret = st.node->setStringValue(_data.c_str());
308     } else if (st.type == "vec3d" && _extended) {
309       ret = st.node
310         ->setValue(simgear::parseString<SGVec3d>(_data));
311     } else if (st.type == "vec4d" && _extended) {
312       ret = st.node
313         ->setValue(simgear::parseString<SGVec4d>(_data));
314     } else if (st.type == "unspecified") {
315       ret = st.node->setUnspecifiedValue(_data.c_str());
316     } else if (_level == 1) {
317       ret = true;               // empty <PropertyList>
318     } else {
319       string message = "Unrecognized data type '";
320       message += st.type;
321       message += '\'';
322                                 // FIXME: add location information
323       throw sg_io_exception(message, location, "SimGear Property Reader");
324     }
325     if (!ret)
326       SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
327              << st.node->getPath() << " to value \""
328              << _data << "\" with type " << st.type << "\n at "
329              << location.asString());
330   }
331
332                                 // Set the access-mode attributes now,
333                                 // once the value has already been 
334                                 // assigned.
335   st.node->setAttributes(st.mode);
336
337   if (st.omit) {
338     State &parent = _state_stack[_state_stack.size() - 2];
339     int nChildren = st.node->nChildren();
340     for (int i = 0; i < nChildren; i++) {
341       SGPropertyNode *src = st.node->getChild(i);
342       const char *name = src->getName();
343       int index = parent.counters[name];
344       parent.counters[name]++;
345       SGPropertyNode *dst = parent.node->getChild(name, index, true);
346       copyProperties(src, dst);
347     }
348     parent.node->removeChild(st.node->getName(), st.node->getIndex(), false);
349   }
350   pop_state();
351 }
352
353 void
354 PropsVisitor::data (const char * s, int length)
355 {
356   if (state().node->nChildren() == 0)
357     _data.append(string(s, length));
358 }
359
360 void
361 PropsVisitor::warning (const char * message, int line, int column)
362 {
363   SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
364          << message << " at line " << line << ", column " << column);
365 }
366
367
368 \f
369 ////////////////////////////////////////////////////////////////////////
370 // Property list reader.
371 ////////////////////////////////////////////////////////////////////////
372
373
374 /**
375  * Read properties from an input stream.
376  *
377  * @param input The input stream containing an XML property file.
378  * @param start_node The root node for reading properties.
379  * @param base A base path for resolving external include references.
380  * @return true if the read succeeded, false otherwise.
381  */
382 void
383 readProperties (istream &input, SGPropertyNode * start_node,
384                 const string &base, int default_mode, bool extended)
385 {
386   PropsVisitor visitor(start_node, base, default_mode, extended);
387   readXML(input, visitor, base);
388   if (visitor.hasException())
389     throw visitor.getException();
390 }
391
392
393 /**
394  * Read properties from a file.
395  *
396  * @param file A string containing the file path.
397  * @param start_node The root node for reading properties.
398  * @return true if the read succeeded, false otherwise.
399  */
400 void
401 readProperties (const string &file, SGPropertyNode * start_node,
402                 int default_mode, bool extended)
403 {
404   PropsVisitor visitor(start_node, file, default_mode, extended);
405   readXML(file, visitor);
406   if (visitor.hasException())
407     throw visitor.getException();
408 }
409
410
411 /**
412  * Read properties from an in-memory buffer.
413  *
414  * @param buf A character buffer containing the xml data.
415  * @param size The size/length of the buffer in bytes
416  * @param start_node The root node for reading properties.
417  * @return true if the read succeeded, false otherwise.
418  */
419 void readProperties (const char *buf, const int size,
420                      SGPropertyNode * start_node, int default_mode,
421                      bool extended)
422 {
423   PropsVisitor visitor(start_node, "", default_mode, extended);
424   readXML(buf, size, visitor);
425   if (visitor.hasException())
426     throw visitor.getException();
427 }
428
429 \f
430 ////////////////////////////////////////////////////////////////////////
431 // Property list writer.
432 ////////////////////////////////////////////////////////////////////////
433
434 #define INDENT_STEP 2
435
436 /**
437  * Return the type name.
438  */
439 static const char *
440 getTypeName (simgear::props::Type type)
441 {
442   using namespace simgear;
443   switch (type) {
444   case props::UNSPECIFIED:
445     return "unspecified";
446   case props::BOOL:
447     return "bool";
448   case props::INT:
449     return "int";
450   case props::LONG:
451     return "long";
452   case props::FLOAT:
453     return "float";
454   case props::DOUBLE:
455     return "double";
456   case props::STRING:
457     return "string";
458   case props::VEC3D:
459     return "vec3d";
460   case props::VEC4D:
461     return "vec4d";
462   case props::ALIAS:
463   case props::NONE:
464     return "unspecified";
465   default: // avoid compiler warning
466     break;
467   }
468
469   // keep the compiler from squawking
470   return "unspecified";
471 }
472
473
474 /**
475  * Escape characters for output.
476  */
477 static void
478 writeData (ostream &output, const string &data)
479 {
480   for (int i = 0; i < (int)data.size(); i++) {
481     switch (data[i]) {
482     case '&':
483       output << "&amp;";
484       break;
485     case '<':
486       output << "&lt;";
487       break;
488     case '>':
489       output << "&gt;";
490       break;
491     default:
492       output << data[i];
493       break;
494     }
495   }
496 }
497
498 static void
499 doIndent (ostream &output, int indent)
500 {
501   while (indent-- > 0) {
502     output << ' ';
503   }
504 }
505
506
507 static void
508 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
509 {
510   int index = node->getIndex();
511
512   if (index != 0 || forceindex)
513     output << " n=\"" << index << '"';
514
515 #if 0
516   if (!node->getAttribute(SGPropertyNode::READ))
517     output << " read=\"n\"";
518
519   if (!node->getAttribute(SGPropertyNode::WRITE))
520     output << " write=\"n\"";
521
522   if (node->getAttribute(SGPropertyNode::ARCHIVE))
523     output << " archive=\"y\"";
524 #endif
525
526 }
527
528
529 /**
530  * Test whether a node is archivable or has archivable descendants.
531  */
532 static bool
533 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
534 {
535   // FIXME: it's inefficient to do this all the time
536   if (node->getAttribute(archive_flag))
537     return true;
538   else {
539     int nChildren = node->nChildren();
540     for (int i = 0; i < nChildren; i++)
541       if (isArchivable(node->getChild(i), archive_flag))
542         return true;
543   }
544   return false;
545 }
546
547
548 static bool
549 writeNode (ostream &output, const SGPropertyNode * node,
550            bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
551 {
552                                 // Don't write the node or any of
553                                 // its descendants unless it is
554                                 // allowed to be archived.
555   if (!write_all && !isArchivable(node, archive_flag))
556     return true;                // Everything's OK, but we won't write.
557
558   const string name = node->getName();
559   int nChildren = node->nChildren();
560   bool node_has_value = false;
561
562                                 // If there is a literal value,
563                                 // write it first.
564   if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
565     doIndent(output, indent);
566     output << '<' << name;
567     writeAtts(output, node, nChildren != 0);
568     if (node->isAlias() && node->getAliasTarget() != 0) {
569       output << " alias=\"" << node->getAliasTarget()->getPath()
570              << "\"/>" << endl;
571     } else {
572       if (node->getType() != simgear::props::UNSPECIFIED)
573         output << " type=\"" << getTypeName(node->getType()) << '"';
574       output << '>';
575       writeData(output, node->getStringValue());
576       output << "</" << name << '>' << endl;
577     }
578     node_has_value = true;
579   }
580
581                                 // If there are children, write them next.
582   if (nChildren > 0) {
583     doIndent(output, indent);
584     output << '<' << name;
585     writeAtts(output, node, node_has_value);
586     output << '>' << endl;
587     for (int i = 0; i < nChildren; i++)
588       writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
589     doIndent(output, indent);
590     output << "</" << name << '>' << endl;
591   }
592
593   return true;
594 }
595
596
597 void
598 writeProperties (ostream &output, const SGPropertyNode * start_node,
599                  bool write_all, SGPropertyNode::Attribute archive_flag)
600 {
601   int nChildren = start_node->nChildren();
602
603   output << "<?xml version=\"1.0\"?>" << endl << endl;
604   output << "<PropertyList>" << endl;
605
606   for (int i = 0; i < nChildren; i++) {
607     writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
608   }
609
610   output << "</PropertyList>" << endl;
611 }
612
613
614 void
615 writeProperties (const string &file, const SGPropertyNode * start_node,
616                  bool write_all, SGPropertyNode::Attribute archive_flag)
617 {
618   SGPath path(file.c_str());
619   path.create_dir(0777);
620
621   ofstream output(file.c_str());
622   if (output.good()) {
623     writeProperties(output, start_node, write_all, archive_flag);
624   } else {
625     throw sg_io_exception("Cannot open file", sg_location(file));
626   }
627 }
628
629 // Another variation, useful when called from gdb
630 void
631 writeProperties (const char* file, const SGPropertyNode * start_node)
632 {
633     writeProperties(string(file), start_node, true);
634 }
635
636
637 \f
638 ////////////////////////////////////////////////////////////////////////
639 // Copy properties from one tree to another.
640 ////////////////////////////////////////////////////////////////////////
641
642
643 /**
644  * Copy one property tree to another.
645  * 
646  * @param in The source property tree.
647  * @param out The destination property tree.
648  * @param attr_value Only copy properties with given attribute values.
649  * @param attr_mask  Mask for attributes to be considered by attr_value
650  *                   (default is 0 = attributes not considered, all
651  *                   properties copied).
652  * @return true if all properties were copied, false if some failed
653  *  (for example, if the property's value is tied read-only).
654  */
655 bool
656 copyProperties (const SGPropertyNode *in, SGPropertyNode *out,
657                 int attr_value, int attr_mask)
658 {
659   using namespace simgear;
660   bool retval = true;
661
662                                 // First, copy the actual value,
663                                 // if any.
664   if (in->hasValue()) {
665     switch (in->getType()) {
666     case props::BOOL:
667       if (!out->setBoolValue(in->getBoolValue()))
668         retval = false;
669       break;
670     case props::INT:
671       if (!out->setIntValue(in->getIntValue()))
672         retval = false;
673       break;
674     case props::LONG:
675       if (!out->setLongValue(in->getLongValue()))
676         retval = false;
677       break;
678     case props::FLOAT:
679       if (!out->setFloatValue(in->getFloatValue()))
680         retval = false;
681       break;
682     case props::DOUBLE:
683       if (!out->setDoubleValue(in->getDoubleValue()))
684         retval = false;
685       break;
686     case props::STRING:
687       if (!out->setStringValue(in->getStringValue()))
688         retval = false;
689       break;
690     case props::UNSPECIFIED:
691       if (!out->setUnspecifiedValue(in->getStringValue()))
692         retval = false;
693       break;
694     case props::VEC3D:
695       if (!out->setValue(in->getValue<SGVec3d>()))
696         retval = false;
697       break;
698     case props::VEC4D:
699       if (!out->setValue(in->getValue<SGVec4d>()))
700         retval = false;
701       break;
702     default:
703       if (in->isAlias())
704         break;
705       string message = "Unknown internal SGPropertyNode type";
706       message += in->getType();
707       throw sg_error(message, "SimGear Property Reader");
708     }
709   }
710
711   // copy the attributes.
712   out->setAttributes( in->getAttributes() );
713
714   // Next, copy the children.
715   int nChildren = in->nChildren();
716   for (int i = 0; i < nChildren; i++) {
717     const SGPropertyNode * in_child = in->getChild(i);
718     int mask = attr_mask;
719     /* attributes have no meaning for nodes without values - except
720      * the PRESERVE flag. So ignore them. */
721     if (!in_child->hasValue())
722         mask &= SGPropertyNode::PRESERVE;
723     if ((in_child->getAttributes() & mask) == (attr_value & mask))
724     {
725       SGPropertyNode * out_child = out->getChild(in_child->getNameString(),
726                              in_child->getIndex(),
727                              false);
728       if (!out_child)
729       {
730           out_child = out->getChild(in_child->getNameString(),
731                                     in_child->getIndex(),
732                                     true);
733       }
734       else
735       {
736           mask = attr_mask;
737           if (!out_child->hasValue())
738               mask &= SGPropertyNode::PRESERVE;
739           if ((out_child->getAttributes() & mask) != (attr_value & mask))
740               out_child = NULL;
741       }
742       if (out_child &&
743           (!copyProperties(in_child, out_child, attr_value, attr_mask)))
744           retval = false;
745     }
746   }
747
748   return retval;
749 }
750
751 // end of props_io.cxx