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