]> git.mxchange.org Git - simgear.git/blob - simgear/misc/props_io.cxx
From David Megginson:
[simgear.git] / simgear / misc / props_io.cxx
1
2 #ifdef HAVE_CONFIG_H
3 #  include <config.h>
4 #endif
5
6 #include <simgear/compiler.h>
7
8 #include <stdlib.h>             // atof() atoi()
9
10 #include <simgear/debug/logstream.hxx>
11 #include <simgear/xml/easyxml.hxx>
12
13 #include "fgpath.hxx"
14 #include "props.hxx"
15
16 #include STL_IOSTREAM
17 #if !defined(FG_HAVE_NATIVE_SGI_COMPILERS)
18 #  include <fstream>
19 #else
20 #  include <fstream.h>
21 #endif
22 #include STL_STRING
23 #include <vector>
24 #include <map>
25
26 #if !defined(FG_HAVE_NATIVE_SGI_COMPILERS)
27 FG_USING_STD(istream);
28 FG_USING_STD(ifstream);
29 FG_USING_STD(ostream);
30 FG_USING_STD(ofstream);
31 #endif
32 FG_USING_STD(string);
33 FG_USING_STD(vector);
34 FG_USING_STD(map);
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     : _ok(true), _root(root), _level(0), _base(base) {}
48
49   void startXML ();
50   void endXML ();
51   void startElement (const char * name, const XMLAttributes &atts);
52   void endElement (const char * name);
53   void data (const char * s, int length);
54   void warning (const char * message, int line, int column);
55   void error (const char * message, int line, int column);
56
57   bool isOK () const { return _ok; }
58
59 private:
60
61   struct State
62   {
63     State () : node(0), type("") {}
64     State (SGPropertyNode * _node, const char * _type)
65       : node(_node), type(_type) {}
66     SGPropertyNode * node;
67     string type;
68     map<string,int> counters;
69   };
70
71   State &state () { return _state_stack[_state_stack.size() - 1]; }
72
73   void push_state (SGPropertyNode * node, const char * type) {
74     if (type == 0)
75       _state_stack.push_back(State(node, "unknown"));
76     else
77       _state_stack.push_back(State(node, type));
78     _level++;
79     _data = "";
80   }
81
82   void pop_state () {
83     _state_stack.pop_back();
84     _level--;
85   }
86
87   bool _ok;
88   string _data;
89   SGPropertyNode * _root;
90   int _level;
91   vector<State> _state_stack;
92   string _base;
93 };
94
95 void
96 PropsVisitor::startXML ()
97 {
98   _level = 0;
99   _state_stack.resize(0);
100 }
101
102 void
103 PropsVisitor::endXML ()
104 {
105   _level = 0;
106   _state_stack.resize(0);
107 }
108
109 void
110 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
111 {
112   State &st = state();
113
114   if (_level == 0) {
115     if (string(name) != "PropertyList") {
116       FG_LOG(FG_INPUT, FG_ALERT, "Root element name is " <<
117              name << "; expected PropertyList");
118       _ok = false;
119     }
120     push_state(_root, "");
121   }
122
123   else {
124                                 // Get the index.
125     const char * att_n = atts.getValue("n");
126     int index = 0;
127     if (att_n != 0) {
128       index = atoi(att_n);
129       st.counters[name] = max(st.counters[name], index+1);
130     } else {
131       index = st.counters[name];
132       st.counters[name]++;
133     }
134
135                                 // Check for an alias.
136     SGPropertyNode * node = st.node->getChild(name, index, true);
137     const char * att_alias = atts.getValue("alias");
138     if (att_alias != 0) {
139       if (!node->alias(att_alias))
140         FG_LOG(FG_INPUT, FG_ALERT, "Failed to set alias to " << att_alias);
141     }
142
143                                 // Check for an include.
144     const char * att_include = atts.getValue("include");
145     if (att_include != 0) {
146       FGPath path(FGPath(_base).dir());
147       cerr << "Base is " << _base << endl;
148       cerr << "Dir is " << FGPath(_base).dir() << endl;
149       path.append(att_include);
150       if (!readProperties(path.str(), node)) {
151         FG_LOG(FG_INPUT, FG_ALERT, "Failed to read include file "
152                << att_include);
153         _ok = false;
154       }
155     }
156
157     push_state(node, atts.getValue("type"));
158   }
159 }
160
161 void
162 PropsVisitor::endElement (const char * name)
163 {
164   State &st = state();
165   bool ret;
166
167                                 // If there are no children and it's
168                                 // not an alias, then it's a leaf value.
169   if (st.node->nChildren() == 0 && !st.node->isAlias()) {
170     if (st.type == "bool") {
171       if (_data == "true" || atoi(_data.c_str()) != 0)
172         ret = st.node->setBoolValue(true);
173       else
174         ret = st.node->setBoolValue(false);
175     } else if (st.type == "int") {
176       ret = st.node->setIntValue(atoi(_data.c_str()));
177     } else if (st.type == "float") {
178       ret = st.node->setFloatValue(atof(_data.c_str()));
179     } else if (st.type == "double") {
180       ret = st.node->setDoubleValue(atof(_data.c_str()));
181     } else if (st.type == "string") {
182       ret = st.node->setStringValue(_data);
183     } else if (st.type == "unknown") {
184       ret = st.node->setUnknownValue(_data);
185     } else {
186       FG_LOG(FG_INPUT, FG_ALERT, "Unknown data type " << st.type
187              << " assuming 'unknown'");
188       ret = st.node->setUnknownValue(_data);
189     }
190     if (!ret)
191       FG_LOG(FG_INPUT, FG_ALERT, "readProperties: Failed to set "
192              << st.node->getPath() << " to value \""
193              << _data << "\" with type " << st.type);
194   }
195
196   pop_state();
197 }
198
199 void
200 PropsVisitor::data (const char * s, int length)
201 {
202   if (state().node->nChildren() == 0)
203     _data.append(string(s, length));
204 }
205
206 void
207 PropsVisitor::warning (const char * message, int line, int column)
208 {
209   FG_LOG(FG_INPUT, FG_ALERT, "readProperties: warning: "
210          << message << " at line " << line << ", column " << column);
211 }
212
213 void
214 PropsVisitor::error (const char * message, int line, int column)
215 {
216   FG_LOG(FG_INPUT, FG_ALERT, "readProperties: FATAL: " <<
217          message << " at line " << line << ", column " << column);
218   _ok = false;
219 }
220
221
222 \f
223 ////////////////////////////////////////////////////////////////////////
224 // Property list reader.
225 ////////////////////////////////////////////////////////////////////////
226
227
228 /**
229  * Read properties from an input stream.
230  *
231  * @param input The input stream containing an XML property file.
232  * @param start_node The root node for reading properties.
233  * @param base A base path for resolving external include references.
234  * @return true if the read succeeded, false otherwise.
235  */
236 bool
237 readProperties (istream &input, SGPropertyNode * start_node,
238                 const string &base)
239 {
240   PropsVisitor visitor(start_node, base);
241   return readXML(input, visitor) && visitor.isOK();
242 }
243
244
245 /**
246  * Read properties from a file.
247  *
248  * @param file A string containing the file path.
249  * @param start_node The root node for reading properties.
250  * @return true if the read succeeded, false otherwise.
251  */
252 bool
253 readProperties (const string &file, SGPropertyNode * start_node)
254 {
255   cerr << "Reading properties from " << file << endl;
256   ifstream input(file.c_str());
257   if (input.good()) {
258     return readProperties(input, start_node, file);
259   } else {
260     FG_LOG(FG_INPUT, FG_ALERT, "Error reading property list from file "
261            << file);
262     return false;
263   }
264 }
265
266
267 \f
268 ////////////////////////////////////////////////////////////////////////
269 // Property list writer.
270 ////////////////////////////////////////////////////////////////////////
271
272 #define INDENT_STEP 2
273
274 /**
275  * Return the type name.
276  */
277 static const char *
278 getTypeName (SGValue::Type type)
279 {
280   switch (type) {
281   case SGValue::UNKNOWN:
282     return "unknown";
283   case SGValue::BOOL:
284     return "bool";
285   case SGValue::INT:
286     return "int";
287   case SGValue::FLOAT:
288     return "float";
289   case SGValue::DOUBLE:
290     return "double";
291   case SGValue::STRING:
292     return "string";
293   }
294
295   // keep the compiler from squawking
296   return "unknown";
297 }
298
299
300 /**
301  * Escape characters for output.
302  */
303 static void
304 writeData (ostream &output, const string &data)
305 {
306   for (int i = 0; i < (int)data.size(); i++) {
307     switch (data[i]) {
308     case '&':
309       output << "&amp;";
310       break;
311     case '<':
312       output << "&lt;";
313       break;
314     case '>':
315       output << "&gt;";
316       break;
317     default:
318       output << data[i];
319       break;
320     }
321   }
322 }
323
324 static void
325 doIndent (ostream &output, int indent)
326 {
327   while (indent-- > 0) {
328     output << ' ';
329   }
330 }
331
332
333 static bool
334 writeNode (ostream &output, const SGPropertyNode * node, int indent)
335 {
336   const string &name = node->getName();
337   int index = node->getIndex();
338   int nChildren = node->nChildren();
339
340                                 // If there is a literal value,
341                                 // write it first.
342   if (node->hasValue()) {
343     doIndent(output, indent);
344     output << '<' << name << " n=\"" << index;
345     if (node->isAlias() && node->getAliasTarget() != 0) {
346       output << "\" alias=\""
347              << node->getAliasTarget()->getPath() << "\"/>" << endl;
348     } else {
349       if (node->getType() != SGValue::UNKNOWN)
350         output << "\" type=\"" << getTypeName(node->getType()) << '"';
351       output << '>';
352       writeData(output, node->getStringValue());
353       output << "</" << name << '>' << endl;;
354     }
355   }
356
357                                 // If there are children, write them
358                                 // next.
359   if (nChildren > 0) {
360     doIndent(output, indent);
361     output << '<' << name << " n=\"" << index << "\">" << endl;;
362     for (int i = 0; i < nChildren; i++)
363       writeNode(output, node->getChild(i), indent + INDENT_STEP);
364     doIndent(output, indent);
365     output << "</" << name << '>' << endl;
366   }
367
368                                 // If there were no children and no
369                                 // value, at least note the presence
370                                 // of the node.
371   if (nChildren == 0 && !node->hasValue()) {
372     doIndent(output, indent);
373     output << '<' << name << " n=\"" << index << "\"/>" << endl;
374   }
375
376   return true;
377 }
378
379
380 /**
381  * Write a property tree to an output stream in XML format.
382  *
383  * @param output The output stream.
384  * @param start_node The root node to write.
385  * @return true if the write succeeded, false otherwise.
386  */
387 bool
388 writeProperties (ostream &output, const SGPropertyNode * start_node)
389 {
390   int nChildren = start_node->nChildren();
391
392   output << "<?xml version=\"1.0\"?>" << endl << endl;
393   output << "<PropertyList>" << endl;
394
395   for (int i = 0; i < nChildren; i++) {
396     writeNode(output, start_node->getChild(i), INDENT_STEP);
397   }
398
399   output << "</PropertyList>" << endl;
400
401   return true;
402 }
403
404
405 /**
406  * Write a property tree to a file in XML format.
407  *
408  * @param file The destination file.
409  * @param start_node The root node to write.
410  * @return true if the write succeeded, false otherwise.
411  */
412 bool
413 writeProperties (const string &file, const SGPropertyNode * start_node)
414 {
415   ofstream output(file.c_str());
416   if (output.good()) {
417     return writeProperties(output, start_node);
418   } else {
419     FG_LOG(FG_INPUT, FG_ALERT, "Cannot write properties to file "
420            << file);
421     return false;
422   }
423 }
424
425
426 \f
427 ////////////////////////////////////////////////////////////////////////
428 // Copy properties from one tree to another.
429 ////////////////////////////////////////////////////////////////////////
430
431
432 /**
433  * Copy one property tree to another.
434  * 
435  * @param in The source property tree.
436  * @param out The destination property tree.
437  * @return true if all properties were copied, false if some failed
438  *  (for example, if the property's value is tied read-only).
439  */
440 bool
441 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
442 {
443   bool retval = true;
444
445                                 // First, copy the actual value,
446                                 // if any.
447   if (in->hasValue()) {
448     switch (in->getType()) {
449     case SGValue::BOOL:
450       if (!out->setBoolValue(in->getBoolValue()))
451         retval = false;
452       break;
453     case SGValue::INT:
454       if (!out->setIntValue(in->getIntValue()))
455         retval = false;
456       break;
457     case SGValue::FLOAT:
458       if (!out->setFloatValue(in->getFloatValue()))
459         retval = false;
460       break;
461     case SGValue::DOUBLE:
462       if (!out->setDoubleValue(in->getDoubleValue()))
463         retval = false;
464       break;
465     case SGValue::STRING:
466       if (!out->setStringValue(in->getStringValue()))
467         retval = false;
468       break;
469     case SGValue::UNKNOWN:
470       if (!out->setUnknownValue(in->getStringValue()))
471         retval = false;
472       break;
473     default:
474       throw string("Unknown SGValue type");
475     }
476   }
477
478                                 // Next, copy the children.
479   int nChildren = in->nChildren();
480   for (int i = 0; i < nChildren; i++) {
481     const SGPropertyNode * in_child = in->getChild(i);
482     SGPropertyNode * out_child = out->getChild(in_child->getName(),
483                                                in_child->getIndex(),
484                                                true);
485     if (!copyProperties(in_child, out_child))
486       retval = false;
487   }
488
489   return retval;
490 }
491
492 // end of props_io.cxx