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