]> git.mxchange.org Git - simgear.git/blob - simgear/misc/props_io.cxx
MSVC++ bug work-around from Frederic Bouvier.
[simgear.git] / simgear / misc / props_io.cxx
1
2 #include <simgear/compiler.h>
3
4 #include <stdlib.h>             // atof() atoi()
5
6 #include <simgear/sg_inlines.h>
7 #include <simgear/debug/logstream.hxx>
8 #include <simgear/xml/easyxml.hxx>
9
10 #include "sg_path.hxx"
11 #include "props.hxx"
12 #include "props_io.hxx"
13
14 #include STL_IOSTREAM
15 #if !defined(SG_HAVE_NATIVE_SGI_COMPILERS)
16 #  include <fstream>
17 #else
18 #  include <fstream.h>
19 #endif
20 #include STL_STRING
21 #include <vector>
22 #include <map>
23
24 #if !defined(SG_HAVE_NATIVE_SGI_COMPILERS)
25 SG_USING_STD(istream);
26 SG_USING_STD(ifstream);
27 SG_USING_STD(ostream);
28 SG_USING_STD(ofstream);
29 #endif
30 SG_USING_STD(string);
31 SG_USING_STD(vector);
32 SG_USING_STD(map);
33
34 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
35
36
37 \f
38 ////////////////////////////////////////////////////////////////////////
39 // Property list visitor, for XML parsing.
40 ////////////////////////////////////////////////////////////////////////
41
42 class PropsVisitor : public XMLVisitor
43 {
44 public:
45
46   PropsVisitor (SGPropertyNode * root, const string &base)
47     : _root(root), _level(0), _base(base), _hasException(false) {}
48
49   virtual ~PropsVisitor () {}
50
51   void startXML ();
52   void endXML ();
53   void startElement (const char * name, const XMLAttributes &atts);
54   void endElement (const char * name);
55   void data (const char * s, int length);
56   void warning (const char * message, int line, int column);
57
58   bool hasException () const { return _hasException; }
59   sg_io_exception &getException () { return _exception; }
60   void setException (const sg_io_exception &exception) {
61     _exception = exception;
62     _hasException = true;
63   }
64
65 private:
66
67   struct State
68   {
69     State () : node(0), type(""), mode(DEFAULT_MODE) {}
70     State (SGPropertyNode * _node, const char * _type, int _mode)
71       : node(_node), type(_type), mode(_mode) {}
72     SGPropertyNode * node;
73     string type;
74     int mode;
75     map<string,int> counters;
76   };
77
78   State &state () { return _state_stack[_state_stack.size() - 1]; }
79
80   void push_state (SGPropertyNode * node, const char * type, int mode) {
81     if (type == 0)
82       _state_stack.push_back(State(node, "unspecified", mode));
83     else
84       _state_stack.push_back(State(node, type, mode));
85     _level++;
86     _data = "";
87   }
88
89   void pop_state () {
90     _state_stack.pop_back();
91     _level--;
92   }
93
94   string _data;
95   SGPropertyNode * _root;
96   int _level;
97   vector<State> _state_stack;
98   string _base;
99   sg_io_exception _exception;
100   bool _hasException;
101 };
102
103 void
104 PropsVisitor::startXML ()
105 {
106   _level = 0;
107   _state_stack.resize(0);
108 }
109
110 void
111 PropsVisitor::endXML ()
112 {
113   _level = 0;
114   _state_stack.resize(0);
115 }
116
117
118 /**
119  * Check a yes/no flag, with default.
120  */
121 static bool
122 checkFlag (const char * flag, bool defaultState = true)
123 {
124   if (flag == 0)
125     return defaultState;
126   else if (string(flag) == "y")
127     return true;
128   else if (string(flag) == "n")
129     return false;
130   else {
131     string message = "Unrecognized flag value '";
132     message += flag;
133     message += '\'';
134                                 // FIXME: add location info
135     throw sg_io_exception(message, "SimGear Property Reader");
136   }
137 }
138
139 void
140 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
141 {
142   State &st = state();
143
144   if (_level == 0) {
145     if (string(name) != (string)"PropertyList") {
146       string message = "Root element name is ";
147       message += name;
148       message += "; expected PropertyList";
149       throw sg_io_exception(message, "SimGear Property Reader");
150     }
151     push_state(_root, "", DEFAULT_MODE);
152   }
153
154   else {
155
156     const char * attval;
157                                 // Get the index.
158     attval = atts.getValue("n");
159     int index = 0;
160     if (attval != 0) {
161       index = atoi(attval);
162       st.counters[name] = SG_MAX2(st.counters[name], index+1);
163     } else {
164       index = st.counters[name];
165       st.counters[name]++;
166     }
167
168                                 // Got the index, so grab the node.
169     SGPropertyNode * node = st.node->getChild(name, index, true);
170
171                                 // Get the access-mode attributes,
172                                 // but don't set yet (in case they
173                                 // prevent us from recording the value).
174     int mode = 0;
175
176     attval = atts.getValue("read");
177     if (checkFlag(attval, true))
178       mode |= SGPropertyNode::READ;
179     attval = atts.getValue("write");
180     if (checkFlag(attval, true))
181       mode |= SGPropertyNode::WRITE;
182     attval = atts.getValue("archive");
183     if (checkFlag(attval, false))
184       mode |= SGPropertyNode::ARCHIVE;
185     attval = atts.getValue("trace-read");
186     if (checkFlag(attval, false))
187       mode |= SGPropertyNode::TRACE_READ;
188     attval = atts.getValue("trace-write");
189     if (checkFlag(attval, false))
190       mode |= SGPropertyNode::TRACE_WRITE;
191
192                                 // Check for an alias.
193     attval = atts.getValue("alias");
194     if (attval != 0) {
195       if (!node->alias(attval))
196         SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
197     }
198
199                                 // Check for an include.
200     attval = atts.getValue("include");
201     if (attval != 0) {
202       SGPath path(SGPath(_base).dir());
203       path.append(attval);
204       try {
205         readProperties(path.str(), node);
206       } catch (sg_io_exception &e) {
207         setException(e);
208       }
209     }
210
211     push_state(node, atts.getValue("type"), mode);
212   }
213 }
214
215 void
216 PropsVisitor::endElement (const char * name)
217 {
218   State &st = state();
219   bool ret;
220
221                                 // If there are no children and it's
222                                 // not an alias, then it's a leaf value.
223   if (st.node->nChildren() == 0 && !st.node->isAlias()) {
224     if (st.type == "bool") {
225       if (_data == "true" || atoi(_data.c_str()) != 0)
226         ret = st.node->setBoolValue(true);
227       else
228         ret = st.node->setBoolValue(false);
229     } else if (st.type == "int") {
230       ret = st.node->setIntValue(atoi(_data.c_str()));
231     } else if (st.type == "long") {
232       ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
233     } else if (st.type == "float") {
234       ret = st.node->setFloatValue(atof(_data.c_str()));
235     } else if (st.type == "double") {
236       ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
237     } else if (st.type == "string") {
238       ret = st.node->setStringValue(_data.c_str());
239     } else if (st.type == "unspecified") {
240       ret = st.node->setUnspecifiedValue(_data.c_str());
241     } else {
242       string message = "Unrecognized data type '";
243       message += st.type;
244       message += '\'';
245                                 // FIXME: add location information
246       throw sg_io_exception(message, "SimGear Property Reader");
247     }
248     if (!ret)
249       SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
250              << st.node->getPath() << " to value \""
251              << _data << "\" with type " << st.type);
252   }
253
254                                 // Set the access-mode attributes now,
255                                 // once the value has already been 
256                                 // assigned.
257   st.node->setAttributes(st.mode);
258
259   pop_state();
260 }
261
262 void
263 PropsVisitor::data (const char * s, int length)
264 {
265   if (state().node->nChildren() == 0)
266     _data.append(string(s, length));
267 }
268
269 void
270 PropsVisitor::warning (const char * message, int line, int column)
271 {
272   SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
273          << message << " at line " << line << ", column " << column);
274 }
275
276
277 \f
278 ////////////////////////////////////////////////////////////////////////
279 // Property list reader.
280 ////////////////////////////////////////////////////////////////////////
281
282
283 /**
284  * Read properties from an input stream.
285  *
286  * @param input The input stream containing an XML property file.
287  * @param start_node The root node for reading properties.
288  * @param base A base path for resolving external include references.
289  * @return true if the read succeeded, false otherwise.
290  */
291 void
292 readProperties (istream &input, SGPropertyNode * start_node,
293                 const string &base)
294 {
295   PropsVisitor visitor(start_node, base);
296   readXML(input, visitor, base);
297   if (visitor.hasException())
298     throw visitor.getException();
299 }
300
301
302 /**
303  * Read properties from a file.
304  *
305  * @param file A string containing the file path.
306  * @param start_node The root node for reading properties.
307  * @return true if the read succeeded, false otherwise.
308  */
309 void
310 readProperties (const string &file, SGPropertyNode * start_node)
311 {
312   PropsVisitor visitor(start_node, file);
313   readXML(file, visitor);
314   if (visitor.hasException())
315     throw visitor.getException();
316 }
317
318
319 \f
320 ////////////////////////////////////////////////////////////////////////
321 // Property list writer.
322 ////////////////////////////////////////////////////////////////////////
323
324 #define INDENT_STEP 2
325
326 /**
327  * Return the type name.
328  */
329 static const char *
330 getTypeName (SGPropertyNode::Type type)
331 {
332   switch (type) {
333   case SGPropertyNode::UNSPECIFIED:
334     return "unspecified";
335   case SGPropertyNode::BOOL:
336     return "bool";
337   case SGPropertyNode::INT:
338     return "int";
339   case SGPropertyNode::LONG:
340     return "long";
341   case SGPropertyNode::FLOAT:
342     return "float";
343   case SGPropertyNode::DOUBLE:
344     return "double";
345   case SGPropertyNode::STRING:
346     return "string";
347   case SGPropertyNode::ALIAS:
348   case SGPropertyNode::NONE:
349     return "unspecified";
350   }
351
352   // keep the compiler from squawking
353   return "unspecified";
354 }
355
356
357 /**
358  * Escape characters for output.
359  */
360 static void
361 writeData (ostream &output, const string &data)
362 {
363   for (int i = 0; i < (int)data.size(); i++) {
364     switch (data[i]) {
365     case '&':
366       output << "&amp;";
367       break;
368     case '<':
369       output << "&lt;";
370       break;
371     case '>':
372       output << "&gt;";
373       break;
374     default:
375       output << data[i];
376       break;
377     }
378   }
379 }
380
381 static void
382 doIndent (ostream &output, int indent)
383 {
384   while (indent-- > 0) {
385     output << ' ';
386   }
387 }
388
389
390 static void
391 writeAtts (ostream &output, const SGPropertyNode * node)
392 {
393   int index = node->getIndex();
394
395   if (index != 0)
396     output << " n=\"" << index << '"';
397
398 #if 0
399   if (!node->getAttribute(SGPropertyNode::READ))
400     output << " read=\"n\"";
401
402   if (!node->getAttribute(SGPropertyNode::WRITE))
403     output << " write=\"n\"";
404
405   if (node->getAttribute(SGPropertyNode::ARCHIVE))
406     output << " archive=\"y\"";
407 #endif
408
409 }
410
411
412 /**
413  * Test whether a node is archivable or has archivable descendants.
414  */
415 static bool
416 isArchivable (const SGPropertyNode * node)
417 {
418   // FIXME: it's inefficient to do this all the time
419   if (node->getAttribute(SGPropertyNode::ARCHIVE))
420     return true;
421   else {
422     int nChildren = node->nChildren();
423     for (int i = 0; i < nChildren; i++)
424       if (isArchivable(node->getChild(i)))
425         return true;
426   }
427   return false;
428 }
429
430
431 static bool
432 writeNode (ostream &output, const SGPropertyNode * node,
433            bool write_all, int indent)
434 {
435                                 // Don't write the node or any of
436                                 // its descendants unless it is
437                                 // allowed to be archived.
438   if (!write_all && !isArchivable(node))
439     return true;                // Everything's OK, but we won't write.
440
441   const string name = node->getName();
442   int nChildren = node->nChildren();
443
444                                 // If there is a literal value,
445                                 // write it first.
446   if (node->hasValue() && (write_all || node->getAttribute(SGPropertyNode::ARCHIVE))) {
447     doIndent(output, indent);
448     output << '<' << name;
449     writeAtts(output, node);
450     if (node->isAlias() && node->getAliasTarget() != 0) {
451       output << " alias=\"" << node->getAliasTarget()->getPath()
452              << "\"/>" << endl;
453     } else {
454       if (node->getType() != SGPropertyNode::UNSPECIFIED)
455         output << " type=\"" << getTypeName(node->getType()) << '"';
456       output << '>';
457       writeData(output, node->getStringValue());
458       output << "</" << name << '>' << endl;
459     }
460   }
461
462                                 // If there are children, write them next.
463   if (nChildren > 0 || node->isAlias()) {
464     doIndent(output, indent);
465     output << '<' << name;
466     writeAtts(output, node);
467     output << '>' << endl;
468     for (int i = 0; i < nChildren; i++)
469       writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP);
470     doIndent(output, indent);
471     output << "</" << name << '>' << endl;
472   }
473
474   return true;
475 }
476
477
478 void
479 writeProperties (ostream &output, const SGPropertyNode * start_node,
480                  bool write_all)
481 {
482   int nChildren = start_node->nChildren();
483
484   output << "<?xml version=\"1.0\"?>" << endl << endl;
485   output << "<PropertyList>" << endl;
486
487   for (int i = 0; i < nChildren; i++) {
488     writeNode(output, start_node->getChild(i), write_all, INDENT_STEP);
489   }
490
491   output << "</PropertyList>" << endl;
492 }
493
494
495 void
496 writeProperties (const string &file, const SGPropertyNode * start_node,
497                  bool write_all)
498 {
499   ofstream output(file.c_str());
500   if (output.good()) {
501     writeProperties(output, start_node, write_all);
502   } else {
503     throw sg_io_exception("Cannot open file", sg_location(file));
504   }
505 }
506
507
508 \f
509 ////////////////////////////////////////////////////////////////////////
510 // Copy properties from one tree to another.
511 ////////////////////////////////////////////////////////////////////////
512
513
514 /**
515  * Copy one property tree to another.
516  * 
517  * @param in The source property tree.
518  * @param out The destination property tree.
519  * @return true if all properties were copied, false if some failed
520  *  (for example, if the property's value is tied read-only).
521  */
522 bool
523 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
524 {
525   bool retval = true;
526
527                                 // First, copy the actual value,
528                                 // if any.
529   if (in->hasValue()) {
530     switch (in->getType()) {
531     case SGPropertyNode::BOOL:
532       if (!out->setBoolValue(in->getBoolValue()))
533         retval = false;
534       break;
535     case SGPropertyNode::INT:
536       if (!out->setIntValue(in->getIntValue()))
537         retval = false;
538       break;
539     case SGPropertyNode::LONG:
540       if (!out->setLongValue(in->getLongValue()))
541         retval = false;
542       break;
543     case SGPropertyNode::FLOAT:
544       if (!out->setFloatValue(in->getFloatValue()))
545         retval = false;
546       break;
547     case SGPropertyNode::DOUBLE:
548       if (!out->setDoubleValue(in->getDoubleValue()))
549         retval = false;
550       break;
551     case SGPropertyNode::STRING:
552       if (!out->setStringValue(in->getStringValue()))
553         retval = false;
554       break;
555     case SGPropertyNode::UNSPECIFIED:
556       if (!out->setUnspecifiedValue(in->getStringValue()))
557         retval = false;
558       break;
559     default:
560       string message = "Unknown internal SGPropertyNode type";
561       message += in->getType();
562       throw sg_error(message, "SimGear Property Reader");
563     }
564   }
565
566                                 // Next, copy the children.
567   int nChildren = in->nChildren();
568   for (int i = 0; i < nChildren; i++) {
569     const SGPropertyNode * in_child = in->getChild(i);
570     SGPropertyNode * out_child = out->getChild(in_child->getName(),
571                                                in_child->getIndex(),
572                                                true);
573     if (!copyProperties(in_child, out_child))
574       retval = false;
575   }
576
577   return retval;
578 }
579
580 // end of props_io.cxx