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