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