]> git.mxchange.org Git - simgear.git/blob - simgear/props/props_io.cxx
Don't waste space with too huge stl containers.
[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 (SGPropertyNode::Type type)
404 {
405   switch (type) {
406   case SGPropertyNode::UNSPECIFIED:
407     return "unspecified";
408   case SGPropertyNode::BOOL:
409     return "bool";
410   case SGPropertyNode::INT:
411     return "int";
412   case SGPropertyNode::LONG:
413     return "long";
414   case SGPropertyNode::FLOAT:
415     return "float";
416   case SGPropertyNode::DOUBLE:
417     return "double";
418   case SGPropertyNode::STRING:
419     return "string";
420   case SGPropertyNode::ALIAS:
421   case SGPropertyNode::NONE:
422     return "unspecified";
423   }
424
425   // keep the compiler from squawking
426   return "unspecified";
427 }
428
429
430 /**
431  * Escape characters for output.
432  */
433 static void
434 writeData (ostream &output, const string &data)
435 {
436   for (int i = 0; i < (int)data.size(); i++) {
437     switch (data[i]) {
438     case '&':
439       output << "&amp;";
440       break;
441     case '<':
442       output << "&lt;";
443       break;
444     case '>':
445       output << "&gt;";
446       break;
447     default:
448       output << data[i];
449       break;
450     }
451   }
452 }
453
454 static void
455 doIndent (ostream &output, int indent)
456 {
457   while (indent-- > 0) {
458     output << ' ';
459   }
460 }
461
462
463 static void
464 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
465 {
466   int index = node->getIndex();
467
468   if (index != 0 || forceindex)
469     output << " n=\"" << index << '"';
470
471 #if 0
472   if (!node->getAttribute(SGPropertyNode::READ))
473     output << " read=\"n\"";
474
475   if (!node->getAttribute(SGPropertyNode::WRITE))
476     output << " write=\"n\"";
477
478   if (node->getAttribute(SGPropertyNode::ARCHIVE))
479     output << " archive=\"y\"";
480 #endif
481
482 }
483
484
485 /**
486  * Test whether a node is archivable or has archivable descendants.
487  */
488 static bool
489 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
490 {
491   // FIXME: it's inefficient to do this all the time
492   if (node->getAttribute(archive_flag))
493     return true;
494   else {
495     int nChildren = node->nChildren();
496     for (int i = 0; i < nChildren; i++)
497       if (isArchivable(node->getChild(i), archive_flag))
498         return true;
499   }
500   return false;
501 }
502
503
504 static bool
505 writeNode (ostream &output, const SGPropertyNode * node,
506            bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
507 {
508                                 // Don't write the node or any of
509                                 // its descendants unless it is
510                                 // allowed to be archived.
511   if (!write_all && !isArchivable(node, archive_flag))
512     return true;                // Everything's OK, but we won't write.
513
514   const string name = node->getName();
515   int nChildren = node->nChildren();
516   bool node_has_value = false;
517
518                                 // If there is a literal value,
519                                 // write it first.
520   if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
521     doIndent(output, indent);
522     output << '<' << name;
523     writeAtts(output, node, nChildren != 0);
524     if (node->isAlias() && node->getAliasTarget() != 0) {
525       output << " alias=\"" << node->getAliasTarget()->getPath()
526              << "\"/>" << endl;
527     } else {
528       if (node->getType() != SGPropertyNode::UNSPECIFIED)
529         output << " type=\"" << getTypeName(node->getType()) << '"';
530       output << '>';
531       writeData(output, node->getStringValue());
532       output << "</" << name << '>' << endl;
533     }
534     node_has_value = true;
535   }
536
537                                 // If there are children, write them next.
538   if (nChildren > 0) {
539     doIndent(output, indent);
540     output << '<' << name;
541     writeAtts(output, node, node_has_value);
542     output << '>' << endl;
543     for (int i = 0; i < nChildren; i++)
544       writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
545     doIndent(output, indent);
546     output << "</" << name << '>' << endl;
547   }
548
549   return true;
550 }
551
552
553 void
554 writeProperties (ostream &output, const SGPropertyNode * start_node,
555                  bool write_all, SGPropertyNode::Attribute archive_flag)
556 {
557   int nChildren = start_node->nChildren();
558
559   output << "<?xml version=\"1.0\"?>" << endl << endl;
560   output << "<PropertyList>" << endl;
561
562   for (int i = 0; i < nChildren; i++) {
563     writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
564   }
565
566   output << "</PropertyList>" << endl;
567 }
568
569
570 void
571 writeProperties (const string &file, const SGPropertyNode * start_node,
572                  bool write_all, SGPropertyNode::Attribute archive_flag)
573 {
574   SGPath path(file.c_str());
575   path.create_dir(0777);
576
577   ofstream output(file.c_str());
578   if (output.good()) {
579     writeProperties(output, start_node, write_all, archive_flag);
580   } else {
581     throw sg_io_exception("Cannot open file", sg_location(file));
582   }
583 }
584
585
586 \f
587 ////////////////////////////////////////////////////////////////////////
588 // Copy properties from one tree to another.
589 ////////////////////////////////////////////////////////////////////////
590
591
592 /**
593  * Copy one property tree to another.
594  * 
595  * @param in The source property tree.
596  * @param out The destination property tree.
597  * @return true if all properties were copied, false if some failed
598  *  (for example, if the property's value is tied read-only).
599  */
600 bool
601 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
602 {
603   bool retval = true;
604
605                                 // First, copy the actual value,
606                                 // if any.
607   if (in->hasValue()) {
608     switch (in->getType()) {
609     case SGPropertyNode::BOOL:
610       if (!out->setBoolValue(in->getBoolValue()))
611         retval = false;
612       break;
613     case SGPropertyNode::INT:
614       if (!out->setIntValue(in->getIntValue()))
615         retval = false;
616       break;
617     case SGPropertyNode::LONG:
618       if (!out->setLongValue(in->getLongValue()))
619         retval = false;
620       break;
621     case SGPropertyNode::FLOAT:
622       if (!out->setFloatValue(in->getFloatValue()))
623         retval = false;
624       break;
625     case SGPropertyNode::DOUBLE:
626       if (!out->setDoubleValue(in->getDoubleValue()))
627         retval = false;
628       break;
629     case SGPropertyNode::STRING:
630       if (!out->setStringValue(in->getStringValue()))
631         retval = false;
632       break;
633     case SGPropertyNode::UNSPECIFIED:
634       if (!out->setUnspecifiedValue(in->getStringValue()))
635         retval = false;
636       break;
637     default:
638       if (in->isAlias())
639         break;
640       string message = "Unknown internal SGPropertyNode type";
641       message += in->getType();
642       throw sg_error(message, "SimGear Property Reader");
643     }
644   }
645
646                                 // copy the attributes.
647   out->setAttributes( in->getAttributes() );
648
649                                 // Next, copy the children.
650   int nChildren = in->nChildren();
651   for (int i = 0; i < nChildren; i++) {
652     const SGPropertyNode * in_child = in->getChild(i);
653     SGPropertyNode * out_child = out->getChild(in_child->getName(),
654                                                in_child->getIndex(),
655                                                true);
656     if (!copyProperties(in_child, out_child))
657       retval = false;
658   }
659
660   return retval;
661 }
662
663 // end of props_io.cxx