]> git.mxchange.org Git - simgear.git/blob - simgear/misc/props_io.cxx
MS patch from Norm Vine to fix min/max macro defs.
[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   const char * attval;
144
145   if (_level == 0) {
146     if (string(name) != (string)"PropertyList") {
147       string message = "Root element name is ";
148       message += name;
149       message += "; expected PropertyList";
150       throw sg_io_exception(message, "SimGear Property Reader");
151     }
152
153                                 // Check for an include.
154     attval = atts.getValue("include");
155     if (attval != 0) {
156       SGPath path(SGPath(_base).dir());
157       path.append(attval);
158       try {
159         readProperties(path.str(), _root);
160       } catch (sg_io_exception &e) {
161         setException(e);
162       }
163     }
164
165     push_state(_root, "", DEFAULT_MODE);
166   }
167
168   else {
169                                 // Get the index.
170     attval = atts.getValue("n");
171     int index = 0;
172     if (attval != 0) {
173       index = atoi(attval);
174       st.counters[name] = SG_MAX2(st.counters[name], index+1);
175     } else {
176       index = st.counters[name];
177       st.counters[name]++;
178     }
179
180                                 // Got the index, so grab the node.
181     SGPropertyNode * node = st.node->getChild(name, index, true);
182
183                                 // Get the access-mode attributes,
184                                 // but don't set yet (in case they
185                                 // prevent us from recording the value).
186     int mode = 0;
187
188     attval = atts.getValue("read");
189     if (checkFlag(attval, true))
190       mode |= SGPropertyNode::READ;
191     attval = atts.getValue("write");
192     if (checkFlag(attval, true))
193       mode |= SGPropertyNode::WRITE;
194     attval = atts.getValue("archive");
195     if (checkFlag(attval, false))
196       mode |= SGPropertyNode::ARCHIVE;
197     attval = atts.getValue("trace-read");
198     if (checkFlag(attval, false))
199       mode |= SGPropertyNode::TRACE_READ;
200     attval = atts.getValue("trace-write");
201     if (checkFlag(attval, false))
202       mode |= SGPropertyNode::TRACE_WRITE;
203
204                                 // Check for an alias.
205     attval = atts.getValue("alias");
206     if (attval != 0) {
207       if (!node->alias(attval))
208         SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
209     }
210
211                                 // Check for an include.
212     attval = atts.getValue("include");
213     if (attval != 0) {
214       SGPath path(SGPath(_base).dir());
215       path.append(attval);
216       try {
217         readProperties(path.str(), node);
218       } catch (sg_io_exception &e) {
219         setException(e);
220       }
221     }
222
223     push_state(node, atts.getValue("type"), mode);
224   }
225 }
226
227 void
228 PropsVisitor::endElement (const char * name)
229 {
230   State &st = state();
231   bool ret;
232
233                                 // If there are no children and it's
234                                 // not an alias, then it's a leaf value.
235   if (st.node->nChildren() == 0 && !st.node->isAlias()) {
236     if (st.type == "bool") {
237       if (_data == "true" || atoi(_data.c_str()) != 0)
238         ret = st.node->setBoolValue(true);
239       else
240         ret = st.node->setBoolValue(false);
241     } else if (st.type == "int") {
242       ret = st.node->setIntValue(atoi(_data.c_str()));
243     } else if (st.type == "long") {
244       ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
245     } else if (st.type == "float") {
246       ret = st.node->setFloatValue(atof(_data.c_str()));
247     } else if (st.type == "double") {
248       ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
249     } else if (st.type == "string") {
250       ret = st.node->setStringValue(_data.c_str());
251     } else if (st.type == "unspecified") {
252       ret = st.node->setUnspecifiedValue(_data.c_str());
253     } else {
254       string message = "Unrecognized data type '";
255       message += st.type;
256       message += '\'';
257                                 // FIXME: add location information
258       throw sg_io_exception(message, "SimGear Property Reader");
259     }
260     if (!ret)
261       SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
262              << st.node->getPath() << " to value \""
263              << _data << "\" with type " << st.type);
264   }
265
266                                 // Set the access-mode attributes now,
267                                 // once the value has already been 
268                                 // assigned.
269   st.node->setAttributes(st.mode);
270
271   pop_state();
272 }
273
274 void
275 PropsVisitor::data (const char * s, int length)
276 {
277   if (state().node->nChildren() == 0)
278     _data.append(string(s, length));
279 }
280
281 void
282 PropsVisitor::warning (const char * message, int line, int column)
283 {
284   SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
285          << message << " at line " << line << ", column " << column);
286 }
287
288
289 \f
290 ////////////////////////////////////////////////////////////////////////
291 // Property list reader.
292 ////////////////////////////////////////////////////////////////////////
293
294
295 /**
296  * Read properties from an input stream.
297  *
298  * @param input The input stream containing an XML property file.
299  * @param start_node The root node for reading properties.
300  * @param base A base path for resolving external include references.
301  * @return true if the read succeeded, false otherwise.
302  */
303 void
304 readProperties (istream &input, SGPropertyNode * start_node,
305                 const string &base)
306 {
307   PropsVisitor visitor(start_node, base);
308   readXML(input, visitor, base);
309   if (visitor.hasException())
310     throw visitor.getException();
311 }
312
313
314 /**
315  * Read properties from a file.
316  *
317  * @param file A string containing the file path.
318  * @param start_node The root node for reading properties.
319  * @return true if the read succeeded, false otherwise.
320  */
321 void
322 readProperties (const string &file, SGPropertyNode * start_node)
323 {
324   PropsVisitor visitor(start_node, file);
325   readXML(file, visitor);
326   if (visitor.hasException())
327     throw visitor.getException();
328 }
329
330
331 \f
332 ////////////////////////////////////////////////////////////////////////
333 // Property list writer.
334 ////////////////////////////////////////////////////////////////////////
335
336 #define INDENT_STEP 2
337
338 /**
339  * Return the type name.
340  */
341 static const char *
342 getTypeName (SGPropertyNode::Type type)
343 {
344   switch (type) {
345   case SGPropertyNode::UNSPECIFIED:
346     return "unspecified";
347   case SGPropertyNode::BOOL:
348     return "bool";
349   case SGPropertyNode::INT:
350     return "int";
351   case SGPropertyNode::LONG:
352     return "long";
353   case SGPropertyNode::FLOAT:
354     return "float";
355   case SGPropertyNode::DOUBLE:
356     return "double";
357   case SGPropertyNode::STRING:
358     return "string";
359   case SGPropertyNode::ALIAS:
360   case SGPropertyNode::NONE:
361     return "unspecified";
362   }
363
364   // keep the compiler from squawking
365   return "unspecified";
366 }
367
368
369 /**
370  * Escape characters for output.
371  */
372 static void
373 writeData (ostream &output, const string &data)
374 {
375   for (int i = 0; i < (int)data.size(); i++) {
376     switch (data[i]) {
377     case '&':
378       output << "&amp;";
379       break;
380     case '<':
381       output << "&lt;";
382       break;
383     case '>':
384       output << "&gt;";
385       break;
386     default:
387       output << data[i];
388       break;
389     }
390   }
391 }
392
393 static void
394 doIndent (ostream &output, int indent)
395 {
396   while (indent-- > 0) {
397     output << ' ';
398   }
399 }
400
401
402 static void
403 writeAtts (ostream &output, const SGPropertyNode * node)
404 {
405   int index = node->getIndex();
406
407   if (index != 0)
408     output << " n=\"" << index << '"';
409
410 #if 0
411   if (!node->getAttribute(SGPropertyNode::READ))
412     output << " read=\"n\"";
413
414   if (!node->getAttribute(SGPropertyNode::WRITE))
415     output << " write=\"n\"";
416
417   if (node->getAttribute(SGPropertyNode::ARCHIVE))
418     output << " archive=\"y\"";
419 #endif
420
421 }
422
423
424 /**
425  * Test whether a node is archivable or has archivable descendants.
426  */
427 static bool
428 isArchivable (const SGPropertyNode * node)
429 {
430   // FIXME: it's inefficient to do this all the time
431   if (node->getAttribute(SGPropertyNode::ARCHIVE))
432     return true;
433   else {
434     int nChildren = node->nChildren();
435     for (int i = 0; i < nChildren; i++)
436       if (isArchivable(node->getChild(i)))
437         return true;
438   }
439   return false;
440 }
441
442
443 static bool
444 writeNode (ostream &output, const SGPropertyNode * node,
445            bool write_all, int indent)
446 {
447                                 // Don't write the node or any of
448                                 // its descendants unless it is
449                                 // allowed to be archived.
450   if (!write_all && !isArchivable(node))
451     return true;                // Everything's OK, but we won't write.
452
453   const string name = node->getName();
454   int nChildren = node->nChildren();
455
456                                 // If there is a literal value,
457                                 // write it first.
458   if (node->hasValue() && (write_all || node->getAttribute(SGPropertyNode::ARCHIVE))) {
459     doIndent(output, indent);
460     output << '<' << name;
461     writeAtts(output, node);
462     if (node->isAlias() && node->getAliasTarget() != 0) {
463       output << " alias=\"" << node->getAliasTarget()->getPath()
464              << "\"/>" << endl;
465     } else {
466       if (node->getType() != SGPropertyNode::UNSPECIFIED)
467         output << " type=\"" << getTypeName(node->getType()) << '"';
468       output << '>';
469       writeData(output, node->getStringValue());
470       output << "</" << name << '>' << endl;
471     }
472   }
473
474                                 // If there are children, write them next.
475   if (nChildren > 0 || node->isAlias()) {
476     doIndent(output, indent);
477     output << '<' << name;
478     writeAtts(output, node);
479     output << '>' << endl;
480     for (int i = 0; i < nChildren; i++)
481       writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP);
482     doIndent(output, indent);
483     output << "</" << name << '>' << endl;
484   }
485
486   return true;
487 }
488
489
490 void
491 writeProperties (ostream &output, const SGPropertyNode * start_node,
492                  bool write_all)
493 {
494   int nChildren = start_node->nChildren();
495
496   output << "<?xml version=\"1.0\"?>" << endl << endl;
497   output << "<PropertyList>" << endl;
498
499   for (int i = 0; i < nChildren; i++) {
500     writeNode(output, start_node->getChild(i), write_all, INDENT_STEP);
501   }
502
503   output << "</PropertyList>" << endl;
504 }
505
506
507 void
508 writeProperties (const string &file, const SGPropertyNode * start_node,
509                  bool write_all)
510 {
511   ofstream output(file.c_str());
512   if (output.good()) {
513     writeProperties(output, start_node, write_all);
514   } else {
515     throw sg_io_exception("Cannot open file", sg_location(file));
516   }
517 }
518
519
520 \f
521 ////////////////////////////////////////////////////////////////////////
522 // Copy properties from one tree to another.
523 ////////////////////////////////////////////////////////////////////////
524
525
526 /**
527  * Copy one property tree to another.
528  * 
529  * @param in The source property tree.
530  * @param out The destination property tree.
531  * @return true if all properties were copied, false if some failed
532  *  (for example, if the property's value is tied read-only).
533  */
534 bool
535 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
536 {
537   bool retval = true;
538
539                                 // First, copy the actual value,
540                                 // if any.
541   if (in->hasValue()) {
542     switch (in->getType()) {
543     case SGPropertyNode::BOOL:
544       if (!out->setBoolValue(in->getBoolValue()))
545         retval = false;
546       break;
547     case SGPropertyNode::INT:
548       if (!out->setIntValue(in->getIntValue()))
549         retval = false;
550       break;
551     case SGPropertyNode::LONG:
552       if (!out->setLongValue(in->getLongValue()))
553         retval = false;
554       break;
555     case SGPropertyNode::FLOAT:
556       if (!out->setFloatValue(in->getFloatValue()))
557         retval = false;
558       break;
559     case SGPropertyNode::DOUBLE:
560       if (!out->setDoubleValue(in->getDoubleValue()))
561         retval = false;
562       break;
563     case SGPropertyNode::STRING:
564       if (!out->setStringValue(in->getStringValue()))
565         retval = false;
566       break;
567     case SGPropertyNode::UNSPECIFIED:
568       if (!out->setUnspecifiedValue(in->getStringValue()))
569         retval = false;
570       break;
571     default:
572       string message = "Unknown internal SGPropertyNode type";
573       message += in->getType();
574       throw sg_error(message, "SimGear Property Reader");
575     }
576   }
577
578                                 // Next, copy the children.
579   int nChildren = in->nChildren();
580   for (int i = 0; i < nChildren; i++) {
581     const SGPropertyNode * in_child = in->getChild(i);
582     SGPropertyNode * out_child = out->getChild(in_child->getName(),
583                                                in_child->getIndex(),
584                                                true);
585     if (!copyProperties(in_child, out_child))
586       retval = false;
587   }
588
589   return retval;
590 }
591
592 // end of props_io.cxx