]> git.mxchange.org Git - simgear.git/blob - simgear/props/props_io.cxx
0aba26edbf4e0782109322afe34e98b4ae7bbf10
[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 /**
326  * Read properties from an in-memory buffer.
327  *
328  * @param buf A character buffer containing the xml data.
329  * @param size The size/length of the buffer in bytes
330  * @param start_node The root node for reading properties.
331  * @return true if the read succeeded, false otherwise.
332  */
333 void readProperties (const char *buf, const int size,
334                      SGPropertyNode * start_node)
335 {
336   PropsVisitor visitor(start_node, "");
337   readXML(buf, size, visitor);
338   if (visitor.hasException())
339     throw visitor.getException();
340 }
341
342 \f
343 ////////////////////////////////////////////////////////////////////////
344 // Property list writer.
345 ////////////////////////////////////////////////////////////////////////
346
347 #define INDENT_STEP 2
348
349 /**
350  * Return the type name.
351  */
352 static const char *
353 getTypeName (SGPropertyNode::Type type)
354 {
355   switch (type) {
356   case SGPropertyNode::UNSPECIFIED:
357     return "unspecified";
358   case SGPropertyNode::BOOL:
359     return "bool";
360   case SGPropertyNode::INT:
361     return "int";
362   case SGPropertyNode::LONG:
363     return "long";
364   case SGPropertyNode::FLOAT:
365     return "float";
366   case SGPropertyNode::DOUBLE:
367     return "double";
368   case SGPropertyNode::STRING:
369     return "string";
370   case SGPropertyNode::ALIAS:
371   case SGPropertyNode::NONE:
372     return "unspecified";
373   }
374
375   // keep the compiler from squawking
376   return "unspecified";
377 }
378
379
380 /**
381  * Escape characters for output.
382  */
383 static void
384 writeData (ostream &output, const string &data)
385 {
386   for (int i = 0; i < (int)data.size(); i++) {
387     switch (data[i]) {
388     case '&':
389       output << "&amp;";
390       break;
391     case '<':
392       output << "&lt;";
393       break;
394     case '>':
395       output << "&gt;";
396       break;
397     default:
398       output << data[i];
399       break;
400     }
401   }
402 }
403
404 static void
405 doIndent (ostream &output, int indent)
406 {
407   while (indent-- > 0) {
408     output << ' ';
409   }
410 }
411
412
413 static void
414 writeAtts (ostream &output, const SGPropertyNode * node)
415 {
416   int index = node->getIndex();
417
418   if (index != 0)
419     output << " n=\"" << index << '"';
420
421 #if 0
422   if (!node->getAttribute(SGPropertyNode::READ))
423     output << " read=\"n\"";
424
425   if (!node->getAttribute(SGPropertyNode::WRITE))
426     output << " write=\"n\"";
427
428   if (node->getAttribute(SGPropertyNode::ARCHIVE))
429     output << " archive=\"y\"";
430 #endif
431
432 }
433
434
435 /**
436  * Test whether a node is archivable or has archivable descendants.
437  */
438 static bool
439 isArchivable (const SGPropertyNode * node)
440 {
441   // FIXME: it's inefficient to do this all the time
442   if (node->getAttribute(SGPropertyNode::ARCHIVE))
443     return true;
444   else {
445     int nChildren = node->nChildren();
446     for (int i = 0; i < nChildren; i++)
447       if (isArchivable(node->getChild(i)))
448         return true;
449   }
450   return false;
451 }
452
453
454 static bool
455 writeNode (ostream &output, const SGPropertyNode * node,
456            bool write_all, int indent)
457 {
458                                 // Don't write the node or any of
459                                 // its descendants unless it is
460                                 // allowed to be archived.
461   if (!write_all && !isArchivable(node))
462     return true;                // Everything's OK, but we won't write.
463
464   const string name = node->getName();
465   int nChildren = node->nChildren();
466
467                                 // If there is a literal value,
468                                 // write it first.
469   if (node->hasValue() && (write_all || node->getAttribute(SGPropertyNode::ARCHIVE))) {
470     doIndent(output, indent);
471     output << '<' << name;
472     writeAtts(output, node);
473     if (node->isAlias() && node->getAliasTarget() != 0) {
474       output << " alias=\"" << node->getAliasTarget()->getPath()
475              << "\"/>" << endl;
476     } else {
477       if (node->getType() != SGPropertyNode::UNSPECIFIED)
478         output << " type=\"" << getTypeName(node->getType()) << '"';
479       output << '>';
480       writeData(output, node->getStringValue());
481       output << "</" << name << '>' << endl;
482     }
483   }
484
485                                 // If there are children, write them next.
486   if (nChildren > 0) {
487     doIndent(output, indent);
488     output << '<' << name;
489     writeAtts(output, node);
490     output << '>' << endl;
491     for (int i = 0; i < nChildren; i++)
492       writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP);
493     doIndent(output, indent);
494     output << "</" << name << '>' << endl;
495   }
496
497   return true;
498 }
499
500
501 void
502 writeProperties (ostream &output, const SGPropertyNode * start_node,
503                  bool write_all)
504 {
505   int nChildren = start_node->nChildren();
506
507   output << "<?xml version=\"1.0\"?>" << endl << endl;
508   output << "<PropertyList>" << endl;
509
510   for (int i = 0; i < nChildren; i++) {
511     writeNode(output, start_node->getChild(i), write_all, INDENT_STEP);
512   }
513
514   output << "</PropertyList>" << endl;
515 }
516
517
518 void
519 writeProperties (const string &file, const SGPropertyNode * start_node,
520                  bool write_all)
521 {
522   ofstream output(file.c_str());
523   if (output.good()) {
524     writeProperties(output, start_node, write_all);
525   } else {
526     throw sg_io_exception("Cannot open file", sg_location(file));
527   }
528 }
529
530
531 \f
532 ////////////////////////////////////////////////////////////////////////
533 // Copy properties from one tree to another.
534 ////////////////////////////////////////////////////////////////////////
535
536
537 /**
538  * Copy one property tree to another.
539  * 
540  * @param in The source property tree.
541  * @param out The destination property tree.
542  * @return true if all properties were copied, false if some failed
543  *  (for example, if the property's value is tied read-only).
544  */
545 bool
546 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
547 {
548   bool retval = true;
549
550                                 // First, copy the actual value,
551                                 // if any.
552   if (in->hasValue()) {
553     switch (in->getType()) {
554     case SGPropertyNode::BOOL:
555       if (!out->setBoolValue(in->getBoolValue()))
556         retval = false;
557       break;
558     case SGPropertyNode::INT:
559       if (!out->setIntValue(in->getIntValue()))
560         retval = false;
561       break;
562     case SGPropertyNode::LONG:
563       if (!out->setLongValue(in->getLongValue()))
564         retval = false;
565       break;
566     case SGPropertyNode::FLOAT:
567       if (!out->setFloatValue(in->getFloatValue()))
568         retval = false;
569       break;
570     case SGPropertyNode::DOUBLE:
571       if (!out->setDoubleValue(in->getDoubleValue()))
572         retval = false;
573       break;
574     case SGPropertyNode::STRING:
575       if (!out->setStringValue(in->getStringValue()))
576         retval = false;
577       break;
578     case SGPropertyNode::UNSPECIFIED:
579       if (!out->setUnspecifiedValue(in->getStringValue()))
580         retval = false;
581       break;
582     default:
583       if (in->isAlias())
584         break;
585       string message = "Unknown internal SGPropertyNode type";
586       message += in->getType();
587       throw sg_error(message, "SimGear Property Reader");
588     }
589   }
590
591                                 // Next, copy the children.
592   int nChildren = in->nChildren();
593   for (int i = 0; i < nChildren; i++) {
594     const SGPropertyNode * in_child = in->getChild(i);
595     SGPropertyNode * out_child = out->getChild(in_child->getName(),
596                                                in_child->getIndex(),
597                                                true);
598     if (!copyProperties(in_child, out_child))
599       retval = false;
600   }
601
602   return retval;
603 }
604
605 // end of props_io.cxx