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