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