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