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/math/SGMath.hxx>
22 #include <simgear/misc/sg_path.hxx>
23 #include <simgear/xml/easyxml.hxx>
24 #include <simgear/misc/ResourceManager.hxx>
27 #include "props_io.hxx"
28 #include "vectorPropTemplates.hxx"
33 #include <cstring> // strcmp()
47 #define DEFAULT_MODE (SGPropertyNode::READ|SGPropertyNode::WRITE)
51 ////////////////////////////////////////////////////////////////////////
52 // Property list visitor, for XML parsing.
53 ////////////////////////////////////////////////////////////////////////
55 class PropsVisitor : public XMLVisitor
59 PropsVisitor (SGPropertyNode * root, const string &base, int default_mode = 0,
60 bool extended = false)
61 : _default_mode(default_mode), _root(root), _level(0), _base(base),
62 _hasException(false), _extended(extended)
65 virtual ~PropsVisitor () {}
69 void startElement (const char * name, const XMLAttributes &atts);
70 void endElement (const char * name);
71 void data (const char * s, int length);
72 void warning (const char * message, int line, int column);
74 bool hasException () const { return _hasException; }
75 sg_io_exception &getException () { return _exception; }
76 void setException (const sg_io_exception &exception) {
77 _exception = exception;
85 State () : node(0), type(""), mode(DEFAULT_MODE), omit(false) {}
86 State (SGPropertyNode * _node, const char * _type, int _mode, bool _omit)
87 : node(_node), type(_type), mode(_mode), omit(_omit) {}
88 SGPropertyNode * node;
92 map<string,int> counters;
95 State &state () { return _state_stack[_state_stack.size() - 1]; }
97 void push_state (SGPropertyNode * node, const char * type, int mode, bool omit = false) {
99 _state_stack.push_back(State(node, "unspecified", mode, omit));
101 _state_stack.push_back(State(node, type, mode, omit));
107 _state_stack.pop_back();
113 SGPropertyNode * _root;
116 vector<State> _state_stack;
118 sg_io_exception _exception;
124 PropsVisitor::startXML ()
127 _state_stack.resize(0);
131 PropsVisitor::endXML ()
134 _state_stack.resize(0);
139 * Check a yes/no flag, with default.
142 checkFlag (const char * flag, 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, "SimGear Property Reader");
160 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
165 if (strcmp(name, "PropertyList")) {
166 string message = "Root element name is ";
168 message += "; expected PropertyList";
169 throw sg_io_exception(message, "SimGear Property Reader");
172 // Check for an include.
173 attval = atts.getValue("include");
176 SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
179 throw sg_io_exception("Cannot open file", sg_location(attval));
181 readProperties(path.str(), _root, 0, _extended);
182 } catch (sg_io_exception &e) {
187 push_state(_root, "", DEFAULT_MODE);
193 attval = atts.getValue("n");
195 string strName(name);
197 index = atoi(attval);
198 st.counters[strName] = SG_MAX2(st.counters[strName], index+1);
200 index = st.counters[strName];
201 st.counters[strName]++;
204 // Got the index, so grab the node.
205 SGPropertyNode * node = st.node->getChild(strName, index, true);
206 if (!node->getAttribute(SGPropertyNode::WRITE)) {
207 SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property "
208 << node->getPath(true));
212 // Get the access-mode attributes,
213 // but don't set yet (in case they
214 // prevent us from recording the value).
215 int mode = _default_mode;
217 attval = atts.getValue("read");
218 if (checkFlag(attval, true))
219 mode |= SGPropertyNode::READ;
220 attval = atts.getValue("write");
221 if (checkFlag(attval, true))
222 mode |= SGPropertyNode::WRITE;
223 attval = atts.getValue("archive");
224 if (checkFlag(attval, false))
225 mode |= SGPropertyNode::ARCHIVE;
226 attval = atts.getValue("trace-read");
227 if (checkFlag(attval, false))
228 mode |= SGPropertyNode::TRACE_READ;
229 attval = atts.getValue("trace-write");
230 if (checkFlag(attval, false))
231 mode |= SGPropertyNode::TRACE_WRITE;
232 attval = atts.getValue("userarchive");
233 if (checkFlag(attval, false))
234 mode |= SGPropertyNode::USERARCHIVE;
235 attval = atts.getValue("preserve");
236 if (checkFlag(attval, false))
237 mode |= SGPropertyNode::PRESERVE;
239 // Check for an alias.
240 attval = atts.getValue("alias");
242 if (!node->alias(attval))
243 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
246 // Check for an include.
248 attval = atts.getValue("include");
251 SGPath path = simgear::ResourceManager::instance()->findPath(attval, SGPath(_base).dir());
254 throw sg_io_exception("Cannot open file", sg_location(attval));
256 readProperties(path.str(), node, 0, _extended);
257 } catch (sg_io_exception &e) {
261 attval = atts.getValue("omit-node");
262 omit = checkFlag(attval, false);
265 const char *type = atts.getValue("type");
266 // if a type is given and the node is tied,
267 // don't clear the value because
268 // clearValue() unties the property
269 if (type && false == node->isTied() )
271 push_state(node, type, mode, omit);
276 PropsVisitor::endElement (const char * name)
281 // If there are no children and it's
282 // not an alias, then it's a leaf value.
283 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
284 if (st.type == "bool") {
285 if (_data == "true" || atoi(_data.c_str()) != 0)
286 ret = st.node->setBoolValue(true);
288 ret = st.node->setBoolValue(false);
289 } else if (st.type == "int") {
290 ret = st.node->setIntValue(atoi(_data.c_str()));
291 } else if (st.type == "long") {
292 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
293 } else if (st.type == "float") {
294 ret = st.node->setFloatValue(atof(_data.c_str()));
295 } else if (st.type == "double") {
296 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
297 } else if (st.type == "string") {
298 ret = st.node->setStringValue(_data.c_str());
299 } else if (st.type == "vec3d" && _extended) {
301 ->setValue(simgear::parseString<SGVec3d>(_data));
302 } else if (st.type == "vec4d" && _extended) {
304 ->setValue(simgear::parseString<SGVec4d>(_data));
305 } else if (st.type == "unspecified") {
306 ret = st.node->setUnspecifiedValue(_data.c_str());
307 } else if (_level == 1) {
308 ret = true; // empty <PropertyList>
310 string message = "Unrecognized data type '";
313 // FIXME: add location information
314 throw sg_io_exception(message, "SimGear Property Reader");
317 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
318 << st.node->getPath() << " to value \""
319 << _data << "\" with type " << st.type);
322 // Set the access-mode attributes now,
323 // once the value has already been
325 st.node->setAttributes(st.mode);
328 State &parent = _state_stack[_state_stack.size() - 2];
329 int nChildren = st.node->nChildren();
330 for (int i = 0; i < nChildren; i++) {
331 SGPropertyNode *src = st.node->getChild(i);
332 const char *name = src->getName();
333 int index = parent.counters[name];
334 parent.counters[name]++;
335 SGPropertyNode *dst = parent.node->getChild(name, index, true);
336 copyProperties(src, dst);
338 parent.node->removeChild(st.node->getName(), st.node->getIndex(), false);
344 PropsVisitor::data (const char * s, int length)
346 if (state().node->nChildren() == 0)
347 _data.append(string(s, length));
351 PropsVisitor::warning (const char * message, int line, int column)
353 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
354 << message << " at line " << line << ", column " << column);
359 ////////////////////////////////////////////////////////////////////////
360 // Property list reader.
361 ////////////////////////////////////////////////////////////////////////
365 * Read properties from an input stream.
367 * @param input The input stream containing an XML property file.
368 * @param start_node The root node for reading properties.
369 * @param base A base path for resolving external include references.
370 * @return true if the read succeeded, false otherwise.
373 readProperties (istream &input, SGPropertyNode * start_node,
374 const string &base, int default_mode, bool extended)
376 PropsVisitor visitor(start_node, base, default_mode, extended);
377 readXML(input, visitor, base);
378 if (visitor.hasException())
379 throw visitor.getException();
384 * Read properties from a file.
386 * @param file A string containing the file path.
387 * @param start_node The root node for reading properties.
388 * @return true if the read succeeded, false otherwise.
391 readProperties (const string &file, SGPropertyNode * start_node,
392 int default_mode, bool extended)
394 PropsVisitor visitor(start_node, file, default_mode, extended);
395 readXML(file, visitor);
396 if (visitor.hasException())
397 throw visitor.getException();
402 * Read properties from an in-memory buffer.
404 * @param buf A character buffer containing the xml data.
405 * @param size The size/length of the buffer in bytes
406 * @param start_node The root node for reading properties.
407 * @return true if the read succeeded, false otherwise.
409 void readProperties (const char *buf, const int size,
410 SGPropertyNode * start_node, int default_mode,
413 PropsVisitor visitor(start_node, "", default_mode, extended);
414 readXML(buf, size, visitor);
415 if (visitor.hasException())
416 throw visitor.getException();
420 ////////////////////////////////////////////////////////////////////////
421 // Property list writer.
422 ////////////////////////////////////////////////////////////////////////
424 #define INDENT_STEP 2
427 * Return the type name.
430 getTypeName (simgear::props::Type type)
432 using namespace simgear;
434 case props::UNSPECIFIED:
435 return "unspecified";
454 return "unspecified";
455 default: // avoid compiler warning
459 // keep the compiler from squawking
460 return "unspecified";
465 * Escape characters for output.
468 writeData (ostream &output, const string &data)
470 for (int i = 0; i < (int)data.size(); i++) {
489 doIndent (ostream &output, int indent)
491 while (indent-- > 0) {
498 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
500 int index = node->getIndex();
502 if (index != 0 || forceindex)
503 output << " n=\"" << index << '"';
506 if (!node->getAttribute(SGPropertyNode::READ))
507 output << " read=\"n\"";
509 if (!node->getAttribute(SGPropertyNode::WRITE))
510 output << " write=\"n\"";
512 if (node->getAttribute(SGPropertyNode::ARCHIVE))
513 output << " archive=\"y\"";
520 * Test whether a node is archivable or has archivable descendants.
523 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
525 // FIXME: it's inefficient to do this all the time
526 if (node->getAttribute(archive_flag))
529 int nChildren = node->nChildren();
530 for (int i = 0; i < nChildren; i++)
531 if (isArchivable(node->getChild(i), archive_flag))
539 writeNode (ostream &output, const SGPropertyNode * node,
540 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
542 // Don't write the node or any of
543 // its descendants unless it is
544 // allowed to be archived.
545 if (!write_all && !isArchivable(node, archive_flag))
546 return true; // Everything's OK, but we won't write.
548 const string name = node->getName();
549 int nChildren = node->nChildren();
550 bool node_has_value = false;
552 // If there is a literal value,
554 if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
555 doIndent(output, indent);
556 output << '<' << name;
557 writeAtts(output, node, nChildren != 0);
558 if (node->isAlias() && node->getAliasTarget() != 0) {
559 output << " alias=\"" << node->getAliasTarget()->getPath()
562 if (node->getType() != simgear::props::UNSPECIFIED)
563 output << " type=\"" << getTypeName(node->getType()) << '"';
565 writeData(output, node->getStringValue());
566 output << "</" << name << '>' << endl;
568 node_has_value = true;
571 // If there are children, write them next.
573 doIndent(output, indent);
574 output << '<' << name;
575 writeAtts(output, node, node_has_value);
576 output << '>' << endl;
577 for (int i = 0; i < nChildren; i++)
578 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
579 doIndent(output, indent);
580 output << "</" << name << '>' << endl;
588 writeProperties (ostream &output, const SGPropertyNode * start_node,
589 bool write_all, SGPropertyNode::Attribute archive_flag)
591 int nChildren = start_node->nChildren();
593 output << "<?xml version=\"1.0\"?>" << endl << endl;
594 output << "<PropertyList>" << endl;
596 for (int i = 0; i < nChildren; i++) {
597 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
600 output << "</PropertyList>" << endl;
605 writeProperties (const string &file, const SGPropertyNode * start_node,
606 bool write_all, SGPropertyNode::Attribute archive_flag)
608 SGPath path(file.c_str());
609 path.create_dir(0777);
611 ofstream output(file.c_str());
613 writeProperties(output, start_node, write_all, archive_flag);
615 throw sg_io_exception("Cannot open file", sg_location(file));
619 // Another variation, useful when called from gdb
621 writeProperties (const char* file, const SGPropertyNode * start_node)
623 writeProperties(string(file), start_node, true);
628 ////////////////////////////////////////////////////////////////////////
629 // Copy properties from one tree to another.
630 ////////////////////////////////////////////////////////////////////////
634 * Copy one property tree to another.
636 * @param in The source property tree.
637 * @param out The destination property tree.
638 * @param attr_value Only copy properties with given attribute values.
639 * @param attr_mask Mask for attributes to be considered by attr_value
640 * (default is 0 = attributes not considered, all
641 * properties copied).
642 * @return true if all properties were copied, false if some failed
643 * (for example, if the property's value is tied read-only).
646 copyProperties (const SGPropertyNode *in, SGPropertyNode *out,
647 int attr_value, int attr_mask)
649 using namespace simgear;
652 // First, copy the actual value,
654 if (in->hasValue()) {
655 switch (in->getType()) {
657 if (!out->setBoolValue(in->getBoolValue()))
661 if (!out->setIntValue(in->getIntValue()))
665 if (!out->setLongValue(in->getLongValue()))
669 if (!out->setFloatValue(in->getFloatValue()))
673 if (!out->setDoubleValue(in->getDoubleValue()))
677 if (!out->setStringValue(in->getStringValue()))
680 case props::UNSPECIFIED:
681 if (!out->setUnspecifiedValue(in->getStringValue()))
685 if (!out->setValue(in->getValue<SGVec3d>()))
689 if (!out->setValue(in->getValue<SGVec4d>()))
695 string message = "Unknown internal SGPropertyNode type";
696 message += in->getType();
697 throw sg_error(message, "SimGear Property Reader");
701 // copy the attributes.
702 out->setAttributes( in->getAttributes() );
704 // Next, copy the children.
705 int nChildren = in->nChildren();
706 for (int i = 0; i < nChildren; i++) {
707 const SGPropertyNode * in_child = in->getChild(i);
708 int mask = attr_mask;
709 /* attributes have no meaning for nodes without values - except
710 * the PRESERVE flag. So ignore them. */
711 if (!in_child->hasValue())
712 mask &= SGPropertyNode::PRESERVE;
713 if ((in_child->getAttributes() & mask) == (attr_value & mask))
715 SGPropertyNode * out_child = out->getChild(in_child->getNameString(),
716 in_child->getIndex(),
720 out_child = out->getChild(in_child->getNameString(),
721 in_child->getIndex(),
727 if (!out_child->hasValue())
728 mask &= SGPropertyNode::PRESERVE;
729 if ((out_child->getAttributes() & mask) != (attr_value & mask))
733 (!copyProperties(in_child, out_child, attr_value, attr_mask)))
741 // end of props_io.cxx