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