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