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