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