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