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