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