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