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