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)
50 ////////////////////////////////////////////////////////////////////////
51 // Property list visitor, for XML parsing.
52 ////////////////////////////////////////////////////////////////////////
54 class PropsVisitor : public XMLVisitor
58 PropsVisitor (SGPropertyNode * root, const string &base, int default_mode = 0,
59 bool extended = false)
60 : _default_mode(default_mode), _root(root), _level(0), _base(base),
61 _hasException(false), _extended(extended)
64 virtual ~PropsVisitor () {}
68 void startElement (const char * name, const XMLAttributes &atts);
69 void endElement (const char * name);
70 void data (const char * s, int length);
71 void warning (const char * message, int line, int column);
73 bool hasException () const { return _hasException; }
74 sg_io_exception &getException () { return _exception; }
75 void setException (const sg_io_exception &exception) {
76 _exception = exception;
84 State () : node(0), type(""), mode(DEFAULT_MODE), omit(false) {}
85 State (SGPropertyNode * _node, const char * _type, int _mode, bool _omit)
86 : node(_node), type(_type), mode(_mode), omit(_omit) {}
87 SGPropertyNode * node;
91 map<string,int> counters;
94 State &state () { return _state_stack[_state_stack.size() - 1]; }
96 void push_state (SGPropertyNode * node, const char * type, int mode, bool omit = false) {
98 _state_stack.push_back(State(node, "unspecified", mode, omit));
100 _state_stack.push_back(State(node, type, mode, omit));
106 _state_stack.pop_back();
112 SGPropertyNode * _root;
115 vector<State> _state_stack;
117 sg_io_exception _exception;
123 PropsVisitor::startXML ()
126 _state_stack.resize(0);
130 PropsVisitor::endXML ()
133 _state_stack.resize(0);
138 * Check a yes/no flag, with default.
141 checkFlag (const char * flag, const sg_location& location,
142 bool defaultState = true)
146 else if (!strcmp(flag, "y"))
148 else if (!strcmp(flag, "n"))
151 string message = "Unrecognized flag value '";
154 // FIXME: add location info
155 throw sg_io_exception(message, location, "SimGear Property Reader");
160 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
163 const sg_location location(getPath(), getLine(), getColumn());
166 if (strcmp(name, "PropertyList")) {
167 string message = "Root element name is ";
169 message += "; expected PropertyList";
170 throw sg_io_exception(message, location, "SimGear Property Reader");
173 // Check for an include.
174 attval = atts.getValue("include");
177 SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
180 string message ="Cannot open file ";
182 throw sg_io_exception(message, location,
183 "SimGear Property Reader");
185 readProperties(path.str(), _root, 0, _extended);
186 } catch (sg_io_exception &e) {
191 push_state(_root, "", DEFAULT_MODE);
197 attval = atts.getValue("n");
199 string strName(name);
201 index = atoi(attval);
202 st.counters[strName] = SG_MAX2(st.counters[strName], index+1);
204 index = st.counters[strName];
205 st.counters[strName]++;
208 // Got the index, so grab the node.
209 SGPropertyNode * node = st.node->getChild(strName, index, true);
210 if (!node->getAttribute(SGPropertyNode::WRITE)) {
211 SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property "
212 << node->getPath(true) << "\n at " << location.asString());
216 // Get the access-mode attributes,
217 // but don't set yet (in case they
218 // prevent us from recording the value).
219 int mode = _default_mode;
221 attval = atts.getValue("read");
222 if (checkFlag(attval, location, true))
223 mode |= SGPropertyNode::READ;
224 attval = atts.getValue("write");
225 if (checkFlag(attval, location, true))
226 mode |= SGPropertyNode::WRITE;
227 attval = atts.getValue("archive");
228 if (checkFlag(attval, location, false))
229 mode |= SGPropertyNode::ARCHIVE;
230 attval = atts.getValue("trace-read");
231 if (checkFlag(attval, location, false))
232 mode |= SGPropertyNode::TRACE_READ;
233 attval = atts.getValue("trace-write");
234 if (checkFlag(attval, location, false))
235 mode |= SGPropertyNode::TRACE_WRITE;
236 attval = atts.getValue("userarchive");
237 if (checkFlag(attval, location, false))
238 mode |= SGPropertyNode::USERARCHIVE;
239 attval = atts.getValue("preserve");
240 if (checkFlag(attval, location, false))
241 mode |= SGPropertyNode::PRESERVE;
243 // Check for an alias.
244 attval = atts.getValue("alias");
246 if (!node->alias(attval))
247 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval
248 << "\n at " << location.asString());
251 // Check for an include.
253 attval = atts.getValue("include");
256 SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
259 string message ="Cannot open file ";
261 throw sg_io_exception(message, location,
262 "SimGear Property Reader");
264 readProperties(path.str(), node, 0, _extended);
265 } catch (sg_io_exception &e) {
269 attval = atts.getValue("omit-node");
270 omit = checkFlag(attval, location, false);
273 const char *type = atts.getValue("type");
274 // if a type is given and the node is tied,
275 // don't clear the value because
276 // clearValue() unties the property
277 if (type && false == node->isTied() )
279 push_state(node, type, mode, omit);
284 PropsVisitor::endElement (const char * name)
288 const sg_location location(getPath(), getLine(), getColumn());
290 // If there are no children and it's
291 // not an alias, then it's a leaf value.
292 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
293 if (st.type == "bool") {
294 if (_data == "true" || atoi(_data.c_str()) != 0)
295 ret = st.node->setBoolValue(true);
297 ret = st.node->setBoolValue(false);
298 } else if (st.type == "int") {
299 ret = st.node->setIntValue(atoi(_data.c_str()));
300 } else if (st.type == "long") {
301 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
302 } else if (st.type == "float") {
303 ret = st.node->setFloatValue(atof(_data.c_str()));
304 } else if (st.type == "double") {
305 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
306 } else if (st.type == "string") {
307 ret = st.node->setStringValue(_data.c_str());
308 } else if (st.type == "vec3d" && _extended) {
310 ->setValue(simgear::parseString<SGVec3d>(_data));
311 } else if (st.type == "vec4d" && _extended) {
313 ->setValue(simgear::parseString<SGVec4d>(_data));
314 } else if (st.type == "unspecified") {
315 ret = st.node->setUnspecifiedValue(_data.c_str());
316 } else if (_level == 1) {
317 ret = true; // empty <PropertyList>
319 string message = "Unrecognized data type '";
322 // FIXME: add location information
323 throw sg_io_exception(message, location, "SimGear Property Reader");
326 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
327 << st.node->getPath() << " to value \""
328 << _data << "\" with type " << st.type << "\n at "
329 << location.asString());
332 // Set the access-mode attributes now,
333 // once the value has already been
335 st.node->setAttributes(st.mode);
338 State &parent = _state_stack[_state_stack.size() - 2];
339 int nChildren = st.node->nChildren();
340 for (int i = 0; i < nChildren; i++) {
341 SGPropertyNode *src = st.node->getChild(i);
342 const char *name = src->getName();
343 int index = parent.counters[name];
344 parent.counters[name]++;
345 SGPropertyNode *dst = parent.node->getChild(name, index, true);
346 copyProperties(src, dst);
348 parent.node->removeChild(st.node->getName(), st.node->getIndex(), false);
354 PropsVisitor::data (const char * s, int length)
356 if (state().node->nChildren() == 0)
357 _data.append(string(s, length));
361 PropsVisitor::warning (const char * message, int line, int column)
363 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
364 << message << " at line " << line << ", column " << column);
369 ////////////////////////////////////////////////////////////////////////
370 // Property list reader.
371 ////////////////////////////////////////////////////////////////////////
375 * Read properties from an input stream.
377 * @param input The input stream containing an XML property file.
378 * @param start_node The root node for reading properties.
379 * @param base A base path for resolving external include references.
380 * @return true if the read succeeded, false otherwise.
383 readProperties (istream &input, SGPropertyNode * start_node,
384 const string &base, int default_mode, bool extended)
386 PropsVisitor visitor(start_node, base, default_mode, extended);
387 readXML(input, visitor, base);
388 if (visitor.hasException())
389 throw visitor.getException();
394 * Read properties from a file.
396 * @param file A string containing the file path.
397 * @param start_node The root node for reading properties.
398 * @return true if the read succeeded, false otherwise.
401 readProperties (const string &file, SGPropertyNode * start_node,
402 int default_mode, bool extended)
404 PropsVisitor visitor(start_node, file, default_mode, extended);
405 readXML(file, visitor);
406 if (visitor.hasException())
407 throw visitor.getException();
412 * Read properties from an in-memory buffer.
414 * @param buf A character buffer containing the xml data.
415 * @param size The size/length of the buffer in bytes
416 * @param start_node The root node for reading properties.
417 * @return true if the read succeeded, false otherwise.
419 void readProperties (const char *buf, const int size,
420 SGPropertyNode * start_node, int default_mode,
423 PropsVisitor visitor(start_node, "", default_mode, extended);
424 readXML(buf, size, visitor);
425 if (visitor.hasException())
426 throw visitor.getException();
430 ////////////////////////////////////////////////////////////////////////
431 // Property list writer.
432 ////////////////////////////////////////////////////////////////////////
434 #define INDENT_STEP 2
437 * Return the type name.
440 getTypeName (simgear::props::Type type)
442 using namespace simgear;
444 case props::UNSPECIFIED:
445 return "unspecified";
464 return "unspecified";
465 default: // avoid compiler warning
469 // keep the compiler from squawking
470 return "unspecified";
475 * Escape characters for output.
478 writeData (ostream &output, const string &data)
480 for (int i = 0; i < (int)data.size(); i++) {
499 doIndent (ostream &output, int indent)
501 while (indent-- > 0) {
508 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
510 int index = node->getIndex();
512 if (index != 0 || forceindex)
513 output << " n=\"" << index << '"';
516 if (!node->getAttribute(SGPropertyNode::READ))
517 output << " read=\"n\"";
519 if (!node->getAttribute(SGPropertyNode::WRITE))
520 output << " write=\"n\"";
522 if (node->getAttribute(SGPropertyNode::ARCHIVE))
523 output << " archive=\"y\"";
530 * Test whether a node is archivable or has archivable descendants.
533 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
535 // FIXME: it's inefficient to do this all the time
536 if (node->getAttribute(archive_flag))
539 int nChildren = node->nChildren();
540 for (int i = 0; i < nChildren; i++)
541 if (isArchivable(node->getChild(i), archive_flag))
549 writeNode (ostream &output, const SGPropertyNode * node,
550 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
552 // Don't write the node or any of
553 // its descendants unless it is
554 // allowed to be archived.
555 if (!write_all && !isArchivable(node, archive_flag))
556 return true; // Everything's OK, but we won't write.
558 const string name = node->getName();
559 int nChildren = node->nChildren();
560 bool node_has_value = false;
562 // If there is a literal value,
564 if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
565 doIndent(output, indent);
566 output << '<' << name;
567 writeAtts(output, node, nChildren != 0);
568 if (node->isAlias() && node->getAliasTarget() != 0) {
569 output << " alias=\"" << node->getAliasTarget()->getPath()
572 if (node->getType() != simgear::props::UNSPECIFIED)
573 output << " type=\"" << getTypeName(node->getType()) << '"';
575 writeData(output, node->getStringValue());
576 output << "</" << name << '>' << endl;
578 node_has_value = true;
581 // If there are children, write them next.
583 doIndent(output, indent);
584 output << '<' << name;
585 writeAtts(output, node, node_has_value);
586 output << '>' << endl;
587 for (int i = 0; i < nChildren; i++)
588 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
589 doIndent(output, indent);
590 output << "</" << name << '>' << endl;
598 writeProperties (ostream &output, const SGPropertyNode * start_node,
599 bool write_all, SGPropertyNode::Attribute archive_flag)
601 int nChildren = start_node->nChildren();
603 output << "<?xml version=\"1.0\"?>" << endl << endl;
604 output << "<PropertyList>" << endl;
606 for (int i = 0; i < nChildren; i++) {
607 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
610 output << "</PropertyList>" << endl;
615 writeProperties (const string &file, const SGPropertyNode * start_node,
616 bool write_all, SGPropertyNode::Attribute archive_flag)
618 SGPath path(file.c_str());
619 path.create_dir(0777);
621 ofstream output(file.c_str());
623 writeProperties(output, start_node, write_all, archive_flag);
625 throw sg_io_exception("Cannot open file", sg_location(file));
629 // Another variation, useful when called from gdb
631 writeProperties (const char* file, const SGPropertyNode * start_node)
633 writeProperties(string(file), start_node, true);
638 ////////////////////////////////////////////////////////////////////////
639 // Copy properties from one tree to another.
640 ////////////////////////////////////////////////////////////////////////
644 copyPropertyValue(const SGPropertyNode *in, SGPropertyNode *out)
646 using namespace simgear;
649 if (!in->hasValue()) {
653 switch (in->getType()) {
655 if (!out->setBoolValue(in->getBoolValue()))
659 if (!out->setIntValue(in->getIntValue()))
663 if (!out->setLongValue(in->getLongValue()))
667 if (!out->setFloatValue(in->getFloatValue()))
671 if (!out->setDoubleValue(in->getDoubleValue()))
675 if (!out->setStringValue(in->getStringValue()))
678 case props::UNSPECIFIED:
679 if (!out->setUnspecifiedValue(in->getStringValue()))
683 if (!out->setValue(in->getValue<SGVec3d>()))
687 if (!out->setValue(in->getValue<SGVec4d>()))
693 string message = "Unknown internal SGPropertyNode type";
694 message += in->getType();
695 throw sg_error(message, "SimGear Property Reader");
702 * Copy one property tree to another.
704 * @param in The source property tree.
705 * @param out The destination property tree.
706 * @param attr_value Only copy properties with given attribute values.
707 * @param attr_mask Mask for attributes to be considered by attr_value
708 * (default is 0 = attributes not considered, all
709 * properties copied).
710 * @return true if all properties were copied, false if some failed
711 * (for example, if the property's value is tied read-only).
714 copyProperties (const SGPropertyNode *in, SGPropertyNode *out,
715 int attr_value, int attr_mask)
717 using namespace simgear;
718 bool retval = copyPropertyValue(in, out);
723 // copy the attributes.
724 out->setAttributes( in->getAttributes() );
726 // Next, copy the children.
727 int nChildren = in->nChildren();
728 for (int i = 0; i < nChildren; i++) {
729 const SGPropertyNode * in_child = in->getChild(i);
730 int mask = attr_mask;
731 /* attributes have no meaning for nodes without values - except
732 * the PRESERVE flag. So ignore them. */
733 if (!in_child->hasValue())
734 mask &= SGPropertyNode::PRESERVE;
735 if ((in_child->getAttributes() & mask) == (attr_value & mask))
737 SGPropertyNode * out_child = out->getChild(in_child->getNameString(),
738 in_child->getIndex(),
742 out_child = out->getChild(in_child->getNameString(),
743 in_child->getIndex(),
749 if (!out_child->hasValue())
750 mask &= SGPropertyNode::PRESERVE;
751 if ((out_child->getAttributes() & mask) != (attr_value & mask))
755 (!copyProperties(in_child, out_child, attr_value, attr_mask)))
765 copyPropertiesWithAttribute(const SGPropertyNode *in, SGPropertyNode *out,
766 SGPropertyNode::Attribute attr)
768 bool retval = copyPropertyValue(in, out);
772 out->setAttributes( in->getAttributes() );
774 // if attribute is set directly on this node, we don't require it on
775 // descendent nodes. (Allows setting an attribute on an entire sub-tree
777 if ((attr != SGPropertyNode::NO_ATTR) && out->getAttribute(attr)) {
778 attr = SGPropertyNode::NO_ATTR;
781 int nChildren = in->nChildren();
782 for (int i = 0; i < nChildren; i++) {
783 const SGPropertyNode* in_child = in->getChild(i);
784 if ((attr != SGPropertyNode::NO_ATTR) && !isArchivable(in_child, attr))
787 SGPropertyNode* out_child = out->getChild(in_child->getNameString(),
788 in_child->getIndex(),
791 bool ok = copyPropertiesWithAttribute(in_child, out_child, attr);
795 }// of children iteration
800 // end of props_io.cxx