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