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)
49 ////////////////////////////////////////////////////////////////////////
50 // Property list visitor, for XML parsing.
51 ////////////////////////////////////////////////////////////////////////
53 class PropsVisitor : public XMLVisitor
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)
63 virtual ~PropsVisitor () {}
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);
72 bool hasException () const { return _hasException; }
73 sg_io_exception &getException () { return _exception; }
74 void setException (const sg_io_exception &exception) {
75 _exception = exception;
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
88 int n_children = node->nChildren();
90 || (n_children == 1 && node->getChild(0)->getNameString() != "_attr_");
92 SGPropertyNode * node;
96 map<string,int> counters;
99 State &state () { return _state_stack[_state_stack.size() - 1]; }
101 void push_state (SGPropertyNode * node, const char * type, int mode, bool omit = false) {
103 _state_stack.push_back(State(node, "unspecified", mode, omit));
105 _state_stack.push_back(State(node, type, mode, omit));
111 _state_stack.pop_back();
117 SGPropertyNode * _root;
120 vector<State> _state_stack;
122 sg_io_exception _exception;
128 PropsVisitor::startXML ()
131 _state_stack.resize(0);
135 PropsVisitor::endXML ()
138 _state_stack.resize(0);
143 * Set/unset a yes/no flag.
148 const std::string& flag,
149 const sg_location& location )
153 else if( flag == "n" )
157 string message = "Unrecognized flag value '";
160 // FIXME: add location info
161 throw sg_io_exception(message, location, "SimGear Property Reader");
166 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
169 const sg_location location(getPath(), getLine(), getColumn());
172 if (strcmp(name, "PropertyList")) {
173 string message = "Root element name is ";
175 message += "; expected PropertyList";
176 throw sg_io_exception(message, location, "SimGear Property Reader");
179 // Check for an include.
180 attval = atts.getValue("include");
183 SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
186 string message ="Cannot open file ";
188 throw sg_io_exception(message, location,
189 "SimGear Property Reader");
191 readProperties(path.str(), _root, 0, _extended);
192 } catch (sg_io_exception &e) {
197 push_state(_root, "", DEFAULT_MODE);
204 attval = atts.getValue("n");
206 string strName(name);
208 index = atoi(attval);
209 st.counters[strName] = SG_MAX2(st.counters[strName], index+1);
211 index = st.counters[strName];
212 st.counters[strName]++;
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());
223 // TODO use correct default mode (keep for now to match past behavior)
224 int mode = _default_mode | SGPropertyNode::READ | SGPropertyNode::WRITE;
226 const char* type = 0;
228 SGPropertyNode* attr_node = NULL;
230 for(int i = 0; i < atts.size(); ++i)
232 const std::string att_name = atts.getName(i);
233 const std::string val = atts.getValue(i);
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);
253 // Check for an alias.
254 else if( att_name == "alias" )
256 if( !node->alias(val) )
261 "Failed to set alias to " << val << "\n at " << location.asString()
265 // Check for an include.
266 else if( att_name == "include" )
270 SGPath path = simgear::ResourceManager::instance()
271 ->findPath(val, SGPath(_base).dir());
274 string message ="Cannot open file ";
276 throw sg_io_exception(message, location, "SimGear Property Reader");
278 readProperties(path.str(), node, 0, _extended);
280 catch (sg_io_exception &e)
286 else if( att_name == "omit-node" )
287 setFlag(omit, 1, val, location);
288 else if( att_name == "type" )
290 type = atts.getValue(i);
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() )
298 else if( att_name != "n" )
300 // Store all additional attributes in a special node named _attr_
302 attr_node = node->getChild("_attr_", 0, true);
304 attr_node->setUnspecifiedValue(att_name.c_str(), val.c_str());
307 push_state(node, type, mode, omit);
312 PropsVisitor::endElement (const char * name)
316 const sg_location location(getPath(), getLine(), getColumn());
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() )
322 if (st.type == "bool") {
323 if (_data == "true" || atoi(_data.c_str()) != 0)
324 ret = st.node->setBoolValue(true);
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) {
339 ->setValue(simgear::parseString<SGVec3d>(_data));
340 } else if (st.type == "vec4d" && _extended) {
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>
348 string message = "Unrecognized data type '";
351 // FIXME: add location information
352 throw sg_io_exception(message, location, "SimGear Property Reader");
359 "readProperties: Failed to set " << st.node->getPath()
360 << " to value \"" << _data
361 << "\" with type " << st.type
362 << "\n at " << location.asString()
366 // Set the access-mode attributes now,
367 // once the value has already been
369 st.node->setAttributes(st.mode);
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);
382 parent.node->removeChild(st.node->getName(), st.node->getIndex(), false);
388 PropsVisitor::data (const char * s, int length)
390 if( !state().hasChildren() )
391 _data.append(string(s, length));
395 PropsVisitor::warning (const char * message, int line, int column)
397 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
398 << message << " at line " << line << ", column " << column);
402 ////////////////////////////////////////////////////////////////////////
403 // Property list reader.
404 ////////////////////////////////////////////////////////////////////////
408 * Read properties from an input stream.
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.
416 readProperties (istream &input, SGPropertyNode * start_node,
417 const string &base, int default_mode, bool extended)
419 PropsVisitor visitor(start_node, base, default_mode, extended);
420 readXML(input, visitor, base);
421 if (visitor.hasException())
422 throw visitor.getException();
427 * Read properties from a file.
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.
434 readProperties (const string &file, SGPropertyNode * start_node,
435 int default_mode, bool extended)
437 PropsVisitor visitor(start_node, file, default_mode, extended);
438 readXML(file, visitor);
439 if (visitor.hasException())
440 throw visitor.getException();
445 * Read properties from an in-memory buffer.
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.
452 void readProperties (const char *buf, const int size,
453 SGPropertyNode * start_node, int default_mode,
456 PropsVisitor visitor(start_node, "", default_mode, extended);
457 readXML(buf, size, visitor);
458 if (visitor.hasException())
459 throw visitor.getException();
463 ////////////////////////////////////////////////////////////////////////
464 // Property list writer.
465 ////////////////////////////////////////////////////////////////////////
467 #define INDENT_STEP 2
470 * Return the type name.
473 getTypeName (simgear::props::Type type)
475 using namespace simgear;
477 case props::UNSPECIFIED:
478 return "unspecified";
497 return "unspecified";
498 default: // avoid compiler warning
502 // keep the compiler from squawking
503 return "unspecified";
508 * Escape characters for output.
511 writeData (ostream &output, const string &data)
513 for (int i = 0; i < (int)data.size(); i++) {
532 doIndent (ostream &output, int indent)
534 while (indent-- > 0) {
541 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
543 int index = node->getIndex();
545 if (index != 0 || forceindex)
546 output << " n=\"" << index << '"';
549 if (!node->getAttribute(SGPropertyNode::READ))
550 output << " read=\"n\"";
552 if (!node->getAttribute(SGPropertyNode::WRITE))
553 output << " write=\"n\"";
555 if (node->getAttribute(SGPropertyNode::ARCHIVE))
556 output << " archive=\"y\"";
563 * Test whether a node is archivable or has archivable descendants.
566 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
568 // FIXME: it's inefficient to do this all the time
569 if (node->getAttribute(archive_flag))
572 int nChildren = node->nChildren();
573 for (int i = 0; i < nChildren; i++)
574 if (isArchivable(node->getChild(i), archive_flag))
582 writeNode (ostream &output, const SGPropertyNode * node,
583 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
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.
591 const string name = node->getName();
592 int nChildren = node->nChildren();
593 bool node_has_value = false;
595 // If there is a literal value,
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()
605 if (node->getType() != simgear::props::UNSPECIFIED)
606 output << " type=\"" << getTypeName(node->getType()) << '"';
608 writeData(output, node->getStringValue());
609 output << "</" << name << '>' << endl;
611 node_has_value = true;
614 // If there are children, write them next.
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;
631 writeProperties (ostream &output, const SGPropertyNode * start_node,
632 bool write_all, SGPropertyNode::Attribute archive_flag)
634 int nChildren = start_node->nChildren();
636 output << "<?xml version=\"1.0\"?>" << endl << endl;
637 output << "<PropertyList>" << endl;
639 for (int i = 0; i < nChildren; i++) {
640 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
643 output << "</PropertyList>" << endl;
648 writeProperties (const string &file, const SGPropertyNode * start_node,
649 bool write_all, SGPropertyNode::Attribute archive_flag)
651 SGPath path(file.c_str());
652 path.create_dir(0755);
654 ofstream output(file.c_str());
656 writeProperties(output, start_node, write_all, archive_flag);
658 throw sg_io_exception("Cannot open file", sg_location(file));
662 // Another variation, useful when called from gdb
664 writeProperties (const char* file, const SGPropertyNode * start_node)
666 writeProperties(string(file), start_node, true);
670 ////////////////////////////////////////////////////////////////////////
671 // Copy properties from one tree to another.
672 ////////////////////////////////////////////////////////////////////////
676 copyPropertyValue(const SGPropertyNode *in, SGPropertyNode *out)
678 using namespace simgear;
681 if (!in->hasValue()) {
685 switch (in->getType()) {
687 if (!out->setBoolValue(in->getBoolValue()))
691 if (!out->setIntValue(in->getIntValue()))
695 if (!out->setLongValue(in->getLongValue()))
699 if (!out->setFloatValue(in->getFloatValue()))
703 if (!out->setDoubleValue(in->getDoubleValue()))
707 if (!out->setStringValue(in->getStringValue()))
710 case props::UNSPECIFIED:
711 if (!out->setUnspecifiedValue(in->getStringValue()))
715 if (!out->setValue(in->getValue<SGVec3d>()))
719 if (!out->setValue(in->getValue<SGVec4d>()))
725 string message = "Unknown internal SGPropertyNode type";
726 message += in->getType();
727 throw sg_error(message, "SimGear Property Reader");
734 * Copy one property tree to another.
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).
746 copyProperties (const SGPropertyNode *in, SGPropertyNode *out,
747 int attr_value, int attr_mask)
749 using namespace simgear;
750 bool retval = copyPropertyValue(in, out);
755 // copy the attributes.
756 out->setAttributes( in->getAttributes() );
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))
769 SGPropertyNode * out_child = out->getChild(in_child->getNameString(),
770 in_child->getIndex(),
774 out_child = out->getChild(in_child->getNameString(),
775 in_child->getIndex(),
781 if (!out_child->hasValue())
782 mask &= SGPropertyNode::PRESERVE;
783 if ((out_child->getAttributes() & mask) != (attr_value & mask))
787 (!copyProperties(in_child, out_child, attr_value, attr_mask)))
797 copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out,
798 SGPropertyNode::Attribute attr)
800 bool retval = copyPropertyValue(in, out);
804 out->setAttributes( in->getAttributes() );
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
809 if ((attr != SGPropertyNode::NO_ATTR) && out->getAttribute(attr)) {
810 attr = SGPropertyNode::NO_ATTR;
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))
819 SGPropertyNode* out_child = out->getChild(in_child->getNameString(),
820 in_child->getIndex(),
823 bool ok = copyPropertiesWithAttribute(in_child, out_child, attr);
827 }// of children iteration
832 // end of props_io.cxx