3 * Started Fall 2000 by David Megginson, david@megginson.com
4 * This code is released into the Public Domain.
6 * See props.html for documentation [replace with URL when available].
12 # include <simgear_config.h>
15 #include <simgear/compiler.h>
17 #include <stdlib.h> // atof() atoi()
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>
26 #include "props_io.hxx"
27 #include "vectorPropTemplates.hxx"
32 #include <cstring> // strcmp()
46 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
48 // Name of special node containing unused attributes
49 const std::string ATTR = "_attr_";
52 ////////////////////////////////////////////////////////////////////////
53 // Property list visitor, for XML parsing.
54 ////////////////////////////////////////////////////////////////////////
56 class PropsVisitor : public XMLVisitor
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)
66 virtual ~PropsVisitor () {}
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);
75 bool hasException () const { return _hasException; }
76 sg_io_exception &getException () { return _exception; }
77 void setException (const sg_io_exception &exception) {
78 _exception = exception;
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
91 int n_children = node->nChildren();
93 || (n_children == 1 && node->getChild(0)->getNameString() != ATTR);
95 SGPropertyNode * node;
99 map<string,int> counters;
102 State &state () { return _state_stack[_state_stack.size() - 1]; }
104 void push_state (SGPropertyNode * node, const char * type, int mode, bool omit = false) {
106 _state_stack.push_back(State(node, "unspecified", mode, omit));
108 _state_stack.push_back(State(node, type, mode, omit));
114 _state_stack.pop_back();
120 SGPropertyNode * _root;
123 vector<State> _state_stack;
125 sg_io_exception _exception;
131 PropsVisitor::startXML ()
134 _state_stack.resize(0);
138 PropsVisitor::endXML ()
141 _state_stack.resize(0);
146 * Set/unset a yes/no flag.
151 const std::string& flag,
152 const sg_location& location )
156 else if( flag == "n" )
160 string message = "Unrecognized flag value '";
163 // FIXME: add location info
164 throw sg_io_exception(message, location, "SimGear Property Reader");
169 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
172 const sg_location location(getPath(), getLine(), getColumn());
175 if (strcmp(name, "PropertyList")) {
176 string message = "Root element name is ";
178 message += "; expected PropertyList";
179 throw sg_io_exception(message, location, "SimGear Property Reader");
182 // Check for an include.
183 attval = atts.getValue("include");
186 SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
189 string message ="Cannot open file ";
191 throw sg_io_exception(message, location,
192 "SimGear Property Reader");
194 readProperties(path, _root, 0, _extended);
195 } catch (sg_io_exception &e) {
200 push_state(_root, "", DEFAULT_MODE);
207 attval = atts.getValue("n");
209 string strName(name);
211 index = atoi(attval);
212 st.counters[strName] = SG_MAX2(st.counters[strName], index+1);
214 index = st.counters[strName];
215 st.counters[strName]++;
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());
226 // TODO use correct default mode (keep for now to match past behavior)
227 int mode = _default_mode | SGPropertyNode::READ | SGPropertyNode::WRITE;
229 const char* type = 0;
231 SGPropertyNode* attr_node = NULL;
233 for(int i = 0; i < atts.size(); ++i)
235 const std::string att_name = atts.getName(i);
236 const std::string val = atts.getValue(i);
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);
256 // Check for an alias.
257 else if( att_name == "alias" )
259 if( !node->alias(val) )
264 "Failed to set alias to " << val << "\n at " << location.asString()
268 // Check for an include.
269 else if( att_name == "include" )
273 SGPath path = simgear::ResourceManager::instance()
274 ->findPath(val, SGPath(_base).dir());
277 string message ="Cannot open file ";
279 throw sg_io_exception(message, location, "SimGear Property Reader");
281 readProperties(path, node, 0, _extended);
283 catch (sg_io_exception &e)
289 else if( att_name == "omit-node" )
290 setFlag(omit, 1, val, location);
291 else if( att_name == "type" )
293 type = atts.getValue(i);
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() )
301 else if( att_name != "n" )
303 // Store all additional attributes in a special node named _attr_
305 attr_node = node->getChild(ATTR, 0, true);
307 attr_node->setUnspecifiedValue(att_name.c_str(), val.c_str());
310 push_state(node, type, mode, omit);
315 PropsVisitor::endElement (const char * name)
319 const sg_location location(getPath(), getLine(), getColumn());
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() )
325 if (st.type == "bool") {
326 if (_data == "true" || atoi(_data.c_str()) != 0)
327 ret = st.node->setBoolValue(true);
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) {
342 ->setValue(simgear::parseString<SGVec3d>(_data));
343 } else if (st.type == "vec4d" && _extended) {
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>
351 string message = "Unrecognized data type '";
354 // FIXME: add location information
355 throw sg_io_exception(message, location, "SimGear Property Reader");
362 "readProperties: Failed to set " << st.node->getPath()
363 << " to value \"" << _data
364 << "\" with type " << st.type
365 << "\n at " << location.asString()
369 // Set the access-mode attributes now,
370 // once the value has already been
372 st.node->setAttributes(st.mode);
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);
385 parent.node->removeChild(st.node->getName(), st.node->getIndex());
391 PropsVisitor::data (const char * s, int length)
393 if( !state().hasChildren() )
394 _data.append(string(s, length));
398 PropsVisitor::warning (const char * message, int line, int column)
400 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
401 << message << " at line " << line << ", column " << column);
405 ////////////////////////////////////////////////////////////////////////
406 // Property list reader.
407 ////////////////////////////////////////////////////////////////////////
411 * Read properties from an input stream.
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.
419 readProperties (istream &input, SGPropertyNode * start_node,
420 const string &base, int default_mode, bool extended)
422 PropsVisitor visitor(start_node, base, default_mode, extended);
423 readXML(input, visitor, base);
424 if (visitor.hasException())
425 throw visitor.getException();
430 * Read properties from a file.
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.
437 readProperties (const SGPath &file, SGPropertyNode * start_node,
438 int default_mode, bool extended)
440 PropsVisitor visitor(start_node, file.utf8Str(), default_mode, extended);
441 readXML(file.local8BitStr(), visitor);
442 if (visitor.hasException())
443 throw visitor.getException();
448 * Read properties from an in-memory buffer.
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.
455 void readProperties (const char *buf, const int size,
456 SGPropertyNode * start_node, int default_mode,
459 PropsVisitor visitor(start_node, "", default_mode, extended);
460 readXML(buf, size, visitor);
461 if (visitor.hasException())
462 throw visitor.getException();
466 ////////////////////////////////////////////////////////////////////////
467 // Property list writer.
468 ////////////////////////////////////////////////////////////////////////
470 #define INDENT_STEP 2
473 * Return the type name.
476 getTypeName (simgear::props::Type type)
478 using namespace simgear;
480 case props::UNSPECIFIED:
481 return "unspecified";
500 return "unspecified";
501 default: // avoid compiler warning
505 // keep the compiler from squawking
506 return "unspecified";
511 * Escape characters for output.
514 writeData (ostream &output, const string &data)
516 for (int i = 0; i < (int)data.size(); i++) {
535 doIndent (ostream &output, int indent)
537 while (indent-- > 0) {
544 writeAtts( std::ostream &output,
545 const SGPropertyNode* node,
547 const SGPropertyNode* attr = NULL )
549 int index = node->getIndex();
551 if (index != 0 || forceindex)
552 output << " n=\"" << index << '"';
555 for(int i = 0; i < attr->nChildren(); ++i)
557 output << ' ' << attr->getChild(i)->getName() << "=\"";
559 const std::string data = attr->getChild(i)->getStringValue();
560 for(int j = 0; j < (int)data.size(); ++j)
580 if (!node->getAttribute(SGPropertyNode::READ))
581 output << " read=\"n\"";
583 if (!node->getAttribute(SGPropertyNode::WRITE))
584 output << " write=\"n\"";
586 if (node->getAttribute(SGPropertyNode::ARCHIVE))
587 output << " archive=\"y\"";
594 * Test whether a node is archivable or has archivable descendants.
597 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
599 // FIXME: it's inefficient to do this all the time
600 if (node->getAttribute(archive_flag))
603 int nChildren = node->nChildren();
604 for (int i = 0; i < nChildren; i++)
605 if (isArchivable(node->getChild(i), archive_flag))
613 writeNode( std::ostream &output,
614 const SGPropertyNode * node,
617 SGPropertyNode::Attribute archive_flag )
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.
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;
631 // If there is a literal value,
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);
638 if (node->isAlias() && node->getAliasTarget() != 0) {
639 output << " alias=\"" << node->getAliasTarget()->getPath()
642 if (node->getType() != simgear::props::UNSPECIFIED)
643 output << " type=\"" << getTypeName(node->getType()) << '"';
645 writeData(output, node->getStringValue());
646 output << "</" << name << '>' << endl;
648 node_has_value = true;
651 // If there are children, write them next.
652 if( nChildren > (attr_node ? 1 : 0) )
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++)
660 if( node->getChild(i)->getNameString() != ATTR )
664 indent + INDENT_STEP,
667 doIndent(output, indent);
668 output << "</" << name << '>' << endl;
676 writeProperties (ostream &output, const SGPropertyNode * start_node,
677 bool write_all, SGPropertyNode::Attribute archive_flag)
679 int nChildren = start_node->nChildren();
681 output << "<?xml version=\"1.0\"?>" << endl << endl;
682 output << "<PropertyList>" << endl;
684 for (int i = 0; i < nChildren; i++) {
685 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
688 output << "</PropertyList>" << endl;
693 writeProperties (const SGPath &path, const SGPropertyNode * start_node,
694 bool write_all, SGPropertyNode::Attribute archive_flag)
697 dpath.create_dir(0755);
699 ofstream output(path.local8BitStr().c_str());
701 writeProperties(output, start_node, write_all, archive_flag);
703 throw sg_io_exception("Cannot open file", sg_location(path.utf8Str()));
707 // Another variation, useful when called from gdb
709 writeProperties (const char* file, const SGPropertyNode * start_node)
711 writeProperties(string(file), start_node, true);
715 ////////////////////////////////////////////////////////////////////////
716 // Copy properties from one tree to another.
717 ////////////////////////////////////////////////////////////////////////
721 copyPropertyValue(const SGPropertyNode *in, SGPropertyNode *out)
723 using namespace simgear;
726 if (!in->hasValue()) {
730 switch (in->getType()) {
732 if (!out->setBoolValue(in->getBoolValue()))
736 if (!out->setIntValue(in->getIntValue()))
740 if (!out->setLongValue(in->getLongValue()))
744 if (!out->setFloatValue(in->getFloatValue()))
748 if (!out->setDoubleValue(in->getDoubleValue()))
752 if (!out->setStringValue(in->getStringValue()))
755 case props::UNSPECIFIED:
756 if (!out->setUnspecifiedValue(in->getStringValue()))
760 if (!out->setValue(in->getValue<SGVec3d>()))
764 if (!out->setValue(in->getValue<SGVec4d>()))
770 string message = "Unknown internal SGPropertyNode type";
771 message += in->getType();
772 throw sg_error(message, "SimGear Property Reader");
779 * Copy one property tree to another.
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).
787 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
789 using namespace simgear;
790 bool retval = copyPropertyValue(in, out);
795 // copy the attributes.
796 out->setAttributes( in->getAttributes() );
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(),
807 out_child = out->getChild(in_child->getNameString(),
808 in_child->getIndex(),
812 if (out_child && !copyProperties(in_child, out_child))
821 copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out,
822 SGPropertyNode::Attribute attr)
824 bool retval = copyPropertyValue(in, out);
828 out->setAttributes( in->getAttributes() );
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
833 if ((attr != SGPropertyNode::NO_ATTR) && out->getAttribute(attr)) {
834 attr = SGPropertyNode::NO_ATTR;
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))
843 SGPropertyNode* out_child = out->getChild(in_child->getNameString(),
844 in_child->getIndex(),
847 bool ok = copyPropertiesWithAttribute(in_child, out_child, attr);
851 }// of children iteration
856 // end of props_io.cxx