]> git.mxchange.org Git - simgear.git/blob - simgear/props/props_io.cxx
Put unused attributes in the property tree while parsing.
[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 #include <simgear/misc/ResourceManager.hxx>
24
25 #include "props.hxx"
26 #include "props_io.hxx"
27 #include "vectorPropTemplates.hxx"
28
29 #include <iostream>
30 #include <fstream>
31 #include <string>
32 #include <cstring>      // strcmp()
33 #include <vector>
34 #include <map>
35
36 using std::istream;
37 using std::ifstream;
38 using std::ostream;
39 using std::ofstream;
40 using std::string;
41 using std::vector;
42 using std::map;
43
44 using std::endl;
45
46 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
47
48
49 ////////////////////////////////////////////////////////////////////////
50 // Property list visitor, for XML parsing.
51 ////////////////////////////////////////////////////////////////////////
52
53 class PropsVisitor : public XMLVisitor
54 {
55 public:
56
57   PropsVisitor (SGPropertyNode * root, const string &base, int default_mode = 0,
58                 bool extended = false)
59     : _default_mode(default_mode), _root(root), _level(0), _base(base),
60       _hasException(false), _extended(extended)
61   {}
62
63   virtual ~PropsVisitor () {}
64
65   void startXML ();
66   void endXML ();
67   void startElement (const char * name, const XMLAttributes &atts);
68   void endElement (const char * name);
69   void data (const char * s, int length);
70   void warning (const char * message, int line, int column);
71
72   bool hasException () const { return _hasException; }
73   sg_io_exception &getException () { return _exception; }
74   void setException (const sg_io_exception &exception) {
75     _exception = exception;
76     _hasException = true;
77   }
78
79 private:
80
81   struct State
82   {
83     State () : node(0), type(""), mode(DEFAULT_MODE), omit(false) {}
84     State (SGPropertyNode * _node, const char * _type, int _mode, bool _omit)
85       : node(_node), type(_type), mode(_mode), omit(_omit) {}
86     bool hasChildren() const
87     {
88       int n_children = node->nChildren();
89       return n_children > 1
90          || (n_children == 1 && node->getChild(0)->getNameString() != "_attr_");
91     }
92     SGPropertyNode * node;
93     string type;
94     int mode;
95     bool omit;
96     map<string,int> counters;
97   };
98
99   State &state () { return _state_stack[_state_stack.size() - 1]; }
100
101   void push_state (SGPropertyNode * node, const char * type, int mode, bool omit = false) {
102     if (type == 0)
103       _state_stack.push_back(State(node, "unspecified", mode, omit));
104     else
105       _state_stack.push_back(State(node, type, mode, omit));
106     _level++;
107     _data = "";
108   }
109
110   void pop_state () {
111     _state_stack.pop_back();
112     _level--;
113   }
114
115   int _default_mode;
116   string _data;
117   SGPropertyNode * _root;
118   SGPropertyNode null;
119   int _level;
120   vector<State> _state_stack;
121   string _base;
122   sg_io_exception _exception;
123   bool _hasException;
124   bool _extended;
125 };
126
127 void
128 PropsVisitor::startXML ()
129 {
130   _level = 0;
131   _state_stack.resize(0);
132 }
133
134 void
135 PropsVisitor::endXML ()
136 {
137   _level = 0;
138   _state_stack.resize(0);
139 }
140
141
142 /**
143  * Set/unset a yes/no flag.
144  */
145 static void
146 setFlag( int& mode,
147          int mask,
148          const std::string& flag,
149          const sg_location& location )
150 {
151   if( flag == "y" )
152     mode |= mask;
153   else if( flag == "n" )
154     mode &= ~mask;
155   else
156   {
157     string message = "Unrecognized flag value '";
158     message += flag;
159     message += '\'';
160                                 // FIXME: add location info
161     throw sg_io_exception(message, location, "SimGear Property Reader");
162   }
163 }
164
165 void
166 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
167 {
168   const char * attval;
169   const sg_location location(getPath(), getLine(), getColumn());
170
171   if (_level == 0) {
172     if (strcmp(name, "PropertyList")) {
173       string message = "Root element name is ";
174       message += name;
175       message += "; expected PropertyList";
176       throw sg_io_exception(message, location, "SimGear Property Reader");
177     }
178
179     // Check for an include.
180     attval = atts.getValue("include");
181     if (attval != 0) {
182       try {
183           SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
184           if (path.isNull())
185           {
186               string message ="Cannot open file ";
187               message += attval;
188               throw sg_io_exception(message, location,
189                                     "SimGear Property Reader");
190           }
191           readProperties(path.str(), _root, 0, _extended);
192       } catch (sg_io_exception &e) {
193           setException(e);
194       }
195     }
196
197     push_state(_root, "", DEFAULT_MODE);
198   }
199
200   else {
201     State &st = state();
202
203     // Get the index.
204     attval = atts.getValue("n");
205     int index = 0;
206     string strName(name);
207     if (attval != 0) {
208       index = atoi(attval);
209       st.counters[strName] = SG_MAX2(st.counters[strName], index+1);
210     } else {
211       index = st.counters[strName];
212       st.counters[strName]++;
213     }
214
215     // Got the index, so grab the node.
216     SGPropertyNode * node = st.node->getChild(strName, index, true);
217     if (!node->getAttribute(SGPropertyNode::WRITE)) {
218       SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property "
219              << node->getPath(true) << "\n at " << location.asString());
220       node = &null;
221     }
222
223     // TODO use correct default mode (keep for now to match past behavior)
224     int mode = _default_mode | SGPropertyNode::READ | SGPropertyNode::WRITE;
225     int omit = false;
226     const char* type = 0;
227
228     SGPropertyNode* attr_node = NULL;
229
230     for(int i = 0; i < atts.size(); ++i)
231     {
232       const std::string att_name = atts.getName(i);
233       const std::string val = atts.getValue(i);
234
235       // Get the access-mode attributes,
236       // but don't set yet (in case they
237       // prevent us from recording the value).
238       if( att_name == "read" )
239         setFlag(mode, SGPropertyNode::READ, val, location);
240       else if( att_name == "write" )
241         setFlag(mode, SGPropertyNode::WRITE, val, location);
242       else if( att_name == "archive" )
243         setFlag(mode, SGPropertyNode::ARCHIVE, val, location);
244       else if( att_name == "trace-read" )
245         setFlag(mode, SGPropertyNode::TRACE_READ, val, location);
246       else if( att_name == "trace-write" )
247         setFlag(mode, SGPropertyNode::TRACE_WRITE, val, location);
248       else if( att_name == "userarchive" )
249         setFlag(mode, SGPropertyNode::USERARCHIVE, val, location);
250       else if( att_name == "preserve" )
251         setFlag(mode, SGPropertyNode::PRESERVE, val, location);
252
253       // Check for an alias.
254       else if( att_name == "alias" )
255       {
256         if( !node->alias(val) )
257           SG_LOG
258           (
259             SG_INPUT,
260             SG_ALERT,
261             "Failed to set alias to " << val << "\n at " << location.asString()
262           );
263       }
264
265       // Check for an include.
266       else if( att_name == "include" )
267       {
268         try
269         {
270           SGPath path = simgear::ResourceManager::instance()
271                       ->findPath(val, SGPath(_base).dir());
272           if (path.isNull())
273           {
274             string message ="Cannot open file ";
275             message += val;
276             throw sg_io_exception(message, location, "SimGear Property Reader");
277           }
278           readProperties(path.str(), node, 0, _extended);
279         }
280         catch (sg_io_exception &e)
281         {
282           setException(e);
283         }
284       }
285
286       else if( att_name == "omit-node" )
287         setFlag(omit, 1, val, location);
288       else if( att_name == "type" )
289       {
290         type = atts.getValue(i);
291
292         // if a type is given and the node is tied,
293         // don't clear the value because
294         // clearValue() unties the property
295         if( !node->isTied() )
296           node->clearValue();
297       }
298       else if( att_name != "n" )
299       {
300         // Store all additional attributes in a special node named _attr_
301         if( !attr_node )
302           attr_node = node->getChild("_attr_", 0, true);
303
304         attr_node->setUnspecifiedValue(att_name.c_str(), val.c_str());
305       }
306     }
307     push_state(node, type, mode, omit);
308   }
309 }
310
311 void
312 PropsVisitor::endElement (const char * name)
313 {
314   State &st = state();
315   bool ret;
316   const sg_location location(getPath(), getLine(), getColumn());
317
318   // If there are no children and it's
319   // not an alias, then it's a leaf value.
320   if( !st.hasChildren() && !st.node->isAlias() )
321   {
322     if (st.type == "bool") {
323       if (_data == "true" || atoi(_data.c_str()) != 0)
324         ret = st.node->setBoolValue(true);
325       else
326         ret = st.node->setBoolValue(false);
327     } else if (st.type == "int") {
328       ret = st.node->setIntValue(atoi(_data.c_str()));
329     } else if (st.type == "long") {
330       ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
331     } else if (st.type == "float") {
332       ret = st.node->setFloatValue(atof(_data.c_str()));
333     } else if (st.type == "double") {
334       ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
335     } else if (st.type == "string") {
336       ret = st.node->setStringValue(_data.c_str());
337     } else if (st.type == "vec3d" && _extended) {
338       ret = st.node
339         ->setValue(simgear::parseString<SGVec3d>(_data));
340     } else if (st.type == "vec4d" && _extended) {
341       ret = st.node
342         ->setValue(simgear::parseString<SGVec4d>(_data));
343     } else if (st.type == "unspecified") {
344       ret = st.node->setUnspecifiedValue(_data.c_str());
345     } else if (_level == 1) {
346       ret = true;               // empty <PropertyList>
347     } else {
348       string message = "Unrecognized data type '";
349       message += st.type;
350       message += '\'';
351       // FIXME: add location information
352       throw sg_io_exception(message, location, "SimGear Property Reader");
353     }
354     if( !ret )
355       SG_LOG
356       (
357         SG_INPUT,
358         SG_ALERT,
359         "readProperties: Failed to set " << st.node->getPath()
360                          << " to value \"" << _data
361                          << "\" with type " << st.type
362                          << "\n at " << location.asString()
363       );
364   }
365
366   // Set the access-mode attributes now,
367   // once the value has already been
368   // assigned.
369   st.node->setAttributes(st.mode);
370
371   if (st.omit) {
372     State &parent = _state_stack[_state_stack.size() - 2];
373     int nChildren = st.node->nChildren();
374     for (int i = 0; i < nChildren; i++) {
375       SGPropertyNode *src = st.node->getChild(i);
376       const char *name = src->getName();
377       int index = parent.counters[name];
378       parent.counters[name]++;
379       SGPropertyNode *dst = parent.node->getChild(name, index, true);
380       copyProperties(src, dst);
381     }
382     parent.node->removeChild(st.node->getName(), st.node->getIndex(), false);
383   }
384   pop_state();
385 }
386
387 void
388 PropsVisitor::data (const char * s, int length)
389 {
390   if( !state().hasChildren() )
391     _data.append(string(s, length));
392 }
393
394 void
395 PropsVisitor::warning (const char * message, int line, int column)
396 {
397   SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
398          << message << " at line " << line << ", column " << column);
399 }
400
401
402 ////////////////////////////////////////////////////////////////////////
403 // Property list reader.
404 ////////////////////////////////////////////////////////////////////////
405
406
407 /**
408  * Read properties from an input stream.
409  *
410  * @param input The input stream containing an XML property file.
411  * @param start_node The root node for reading properties.
412  * @param base A base path for resolving external include references.
413  * @return true if the read succeeded, false otherwise.
414  */
415 void
416 readProperties (istream &input, SGPropertyNode * start_node,
417                 const string &base, int default_mode, bool extended)
418 {
419   PropsVisitor visitor(start_node, base, default_mode, extended);
420   readXML(input, visitor, base);
421   if (visitor.hasException())
422     throw visitor.getException();
423 }
424
425
426 /**
427  * Read properties from a file.
428  *
429  * @param file A string containing the file path.
430  * @param start_node The root node for reading properties.
431  * @return true if the read succeeded, false otherwise.
432  */
433 void
434 readProperties (const string &file, SGPropertyNode * start_node,
435                 int default_mode, bool extended)
436 {
437   PropsVisitor visitor(start_node, file, default_mode, extended);
438   readXML(file, visitor);
439   if (visitor.hasException())
440     throw visitor.getException();
441 }
442
443
444 /**
445  * Read properties from an in-memory buffer.
446  *
447  * @param buf A character buffer containing the xml data.
448  * @param size The size/length of the buffer in bytes
449  * @param start_node The root node for reading properties.
450  * @return true if the read succeeded, false otherwise.
451  */
452 void readProperties (const char *buf, const int size,
453                      SGPropertyNode * start_node, int default_mode,
454                      bool extended)
455 {
456   PropsVisitor visitor(start_node, "", default_mode, extended);
457   readXML(buf, size, visitor);
458   if (visitor.hasException())
459     throw visitor.getException();
460 }
461
462
463 ////////////////////////////////////////////////////////////////////////
464 // Property list writer.
465 ////////////////////////////////////////////////////////////////////////
466
467 #define INDENT_STEP 2
468
469 /**
470  * Return the type name.
471  */
472 static const char *
473 getTypeName (simgear::props::Type type)
474 {
475   using namespace simgear;
476   switch (type) {
477   case props::UNSPECIFIED:
478     return "unspecified";
479   case props::BOOL:
480     return "bool";
481   case props::INT:
482     return "int";
483   case props::LONG:
484     return "long";
485   case props::FLOAT:
486     return "float";
487   case props::DOUBLE:
488     return "double";
489   case props::STRING:
490     return "string";
491   case props::VEC3D:
492     return "vec3d";
493   case props::VEC4D:
494     return "vec4d";
495   case props::ALIAS:
496   case props::NONE:
497     return "unspecified";
498   default: // avoid compiler warning
499     break;
500   }
501
502   // keep the compiler from squawking
503   return "unspecified";
504 }
505
506
507 /**
508  * Escape characters for output.
509  */
510 static void
511 writeData (ostream &output, const string &data)
512 {
513   for (int i = 0; i < (int)data.size(); i++) {
514     switch (data[i]) {
515     case '&':
516       output << "&amp;";
517       break;
518     case '<':
519       output << "&lt;";
520       break;
521     case '>':
522       output << "&gt;";
523       break;
524     default:
525       output << data[i];
526       break;
527     }
528   }
529 }
530
531 static void
532 doIndent (ostream &output, int indent)
533 {
534   while (indent-- > 0) {
535     output << ' ';
536   }
537 }
538
539
540 static void
541 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
542 {
543   int index = node->getIndex();
544
545   if (index != 0 || forceindex)
546     output << " n=\"" << index << '"';
547
548 #if 0
549   if (!node->getAttribute(SGPropertyNode::READ))
550     output << " read=\"n\"";
551
552   if (!node->getAttribute(SGPropertyNode::WRITE))
553     output << " write=\"n\"";
554
555   if (node->getAttribute(SGPropertyNode::ARCHIVE))
556     output << " archive=\"y\"";
557 #endif
558
559 }
560
561
562 /**
563  * Test whether a node is archivable or has archivable descendants.
564  */
565 static bool
566 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
567 {
568   // FIXME: it's inefficient to do this all the time
569   if (node->getAttribute(archive_flag))
570     return true;
571   else {
572     int nChildren = node->nChildren();
573     for (int i = 0; i < nChildren; i++)
574       if (isArchivable(node->getChild(i), archive_flag))
575         return true;
576   }
577   return false;
578 }
579
580
581 static bool
582 writeNode (ostream &output, const SGPropertyNode * node,
583            bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
584 {
585                                 // Don't write the node or any of
586                                 // its descendants unless it is
587                                 // allowed to be archived.
588   if (!write_all && !isArchivable(node, archive_flag))
589     return true;                // Everything's OK, but we won't write.
590
591   const string name = node->getName();
592   int nChildren = node->nChildren();
593   bool node_has_value = false;
594
595                                 // If there is a literal value,
596                                 // write it first.
597   if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
598     doIndent(output, indent);
599     output << '<' << name;
600     writeAtts(output, node, nChildren != 0);
601     if (node->isAlias() && node->getAliasTarget() != 0) {
602       output << " alias=\"" << node->getAliasTarget()->getPath()
603              << "\"/>" << endl;
604     } else {
605       if (node->getType() != simgear::props::UNSPECIFIED)
606         output << " type=\"" << getTypeName(node->getType()) << '"';
607       output << '>';
608       writeData(output, node->getStringValue());
609       output << "</" << name << '>' << endl;
610     }
611     node_has_value = true;
612   }
613
614                                 // If there are children, write them next.
615   if (nChildren > 0) {
616     doIndent(output, indent);
617     output << '<' << name;
618     writeAtts(output, node, node_has_value);
619     output << '>' << endl;
620     for (int i = 0; i < nChildren; i++)
621       writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
622     doIndent(output, indent);
623     output << "</" << name << '>' << endl;
624   }
625
626   return true;
627 }
628
629
630 void
631 writeProperties (ostream &output, const SGPropertyNode * start_node,
632                  bool write_all, SGPropertyNode::Attribute archive_flag)
633 {
634   int nChildren = start_node->nChildren();
635
636   output << "<?xml version=\"1.0\"?>" << endl << endl;
637   output << "<PropertyList>" << endl;
638
639   for (int i = 0; i < nChildren; i++) {
640     writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
641   }
642
643   output << "</PropertyList>" << endl;
644 }
645
646
647 void
648 writeProperties (const string &file, const SGPropertyNode * start_node,
649                  bool write_all, SGPropertyNode::Attribute archive_flag)
650 {
651   SGPath path(file.c_str());
652   path.create_dir(0755);
653
654   ofstream output(file.c_str());
655   if (output.good()) {
656     writeProperties(output, start_node, write_all, archive_flag);
657   } else {
658     throw sg_io_exception("Cannot open file", sg_location(file));
659   }
660 }
661
662 // Another variation, useful when called from gdb
663 void
664 writeProperties (const char* file, const SGPropertyNode * start_node)
665 {
666     writeProperties(string(file), start_node, true);
667 }
668
669
670 ////////////////////////////////////////////////////////////////////////
671 // Copy properties from one tree to another.
672 ////////////////////////////////////////////////////////////////////////
673
674
675 bool
676 copyPropertyValue(const SGPropertyNode *in, SGPropertyNode *out)
677 {
678     using namespace simgear;
679     bool retval = true;
680     
681     if (!in->hasValue()) {
682         return true;
683     }
684     
685     switch (in->getType()) {
686         case props::BOOL:
687             if (!out->setBoolValue(in->getBoolValue()))
688                 retval = false;
689             break;
690         case props::INT:
691             if (!out->setIntValue(in->getIntValue()))
692                 retval = false;
693             break;
694         case props::LONG:
695             if (!out->setLongValue(in->getLongValue()))
696                 retval = false;
697             break;
698         case props::FLOAT:
699             if (!out->setFloatValue(in->getFloatValue()))
700                 retval = false;
701             break;
702         case props::DOUBLE:
703             if (!out->setDoubleValue(in->getDoubleValue()))
704                 retval = false;
705             break;
706         case props::STRING:
707             if (!out->setStringValue(in->getStringValue()))
708                 retval = false;
709             break;
710         case props::UNSPECIFIED:
711             if (!out->setUnspecifiedValue(in->getStringValue()))
712                 retval = false;
713             break;
714         case props::VEC3D:
715             if (!out->setValue(in->getValue<SGVec3d>()))
716                 retval = false;
717             break;
718         case props::VEC4D:
719             if (!out->setValue(in->getValue<SGVec4d>()))
720                 retval = false;
721             break;
722         default:
723             if (in->isAlias())
724                 break;
725             string message = "Unknown internal SGPropertyNode type";
726             message += in->getType();
727             throw sg_error(message, "SimGear Property Reader");
728     }
729     
730     return retval;
731 }
732
733 /**
734  * Copy one property tree to another.
735  * 
736  * @param in The source property tree.
737  * @param out The destination property tree.
738  * @param attr_value Only copy properties with given attribute values.
739  * @param attr_mask  Mask for attributes to be considered by attr_value
740  *                   (default is 0 = attributes not considered, all
741  *                   properties copied).
742  * @return true if all properties were copied, false if some failed
743  *  (for example, if the property's value is tied read-only).
744  */
745 bool
746 copyProperties (const SGPropertyNode *in, SGPropertyNode *out,
747                 int attr_value, int attr_mask)
748 {
749   using namespace simgear;
750   bool retval = copyPropertyValue(in, out);
751   if (!retval) {
752     return false;
753   }
754     
755   // copy the attributes.
756   out->setAttributes( in->getAttributes() );
757
758   // Next, copy the children.
759   int nChildren = in->nChildren();
760   for (int i = 0; i < nChildren; i++) {
761     const SGPropertyNode * in_child = in->getChild(i);
762     int mask = attr_mask;
763     /* attributes have no meaning for nodes without values - except
764      * the PRESERVE flag. So ignore them. */
765     if (!in_child->hasValue())
766         mask &= SGPropertyNode::PRESERVE;
767     if ((in_child->getAttributes() & mask) == (attr_value & mask))
768     {
769       SGPropertyNode * out_child = out->getChild(in_child->getNameString(),
770                              in_child->getIndex(),
771                              false);
772       if (!out_child)
773       {
774           out_child = out->getChild(in_child->getNameString(),
775                                     in_child->getIndex(),
776                                     true);
777       }
778       else
779       {
780           mask = attr_mask;
781           if (!out_child->hasValue())
782               mask &= SGPropertyNode::PRESERVE;
783           if ((out_child->getAttributes() & mask) != (attr_value & mask))
784               out_child = NULL;
785       }
786       if (out_child &&
787           (!copyProperties(in_child, out_child, attr_value, attr_mask)))
788           retval = false;
789     }
790   }
791
792   return retval;
793 }
794
795
796 bool
797 copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out,
798                              SGPropertyNode::Attribute attr)
799 {
800     bool retval = copyPropertyValue(in, out);
801     if (!retval) {
802         return false;
803     }
804     out->setAttributes( in->getAttributes() );
805     
806     // if attribute is set directly on this node, we don't require it on
807     // descendent nodes. (Allows setting an attribute on an entire sub-tree
808     // of nodes)
809     if ((attr != SGPropertyNode::NO_ATTR) && out->getAttribute(attr)) {
810         attr = SGPropertyNode::NO_ATTR;
811     }
812     
813     int nChildren = in->nChildren();
814     for (int i = 0; i < nChildren; i++) {
815         const SGPropertyNode* in_child = in->getChild(i);
816         if ((attr != SGPropertyNode::NO_ATTR) && !isArchivable(in_child, attr))
817             continue;
818         
819          SGPropertyNode* out_child = out->getChild(in_child->getNameString(),
820                                       in_child->getIndex(),
821                                       true);
822         
823         bool ok = copyPropertiesWithAttribute(in_child, out_child, attr);
824         if (!ok) {
825             return false;
826         }
827     }// of children iteration
828     
829     return true;
830 }
831
832 // end of props_io.cxx