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