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>
26 #include "props_io.hxx"
31 #include <cstring> // strcmp()
45 #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 SGPropertyNode * node;
90 map<string,int> counters;
93 State &state () { return _state_stack[_state_stack.size() - 1]; }
95 void push_state (SGPropertyNode * node, const char * type, int mode, bool omit = false) {
97 _state_stack.push_back(State(node, "unspecified", mode, omit));
99 _state_stack.push_back(State(node, type, mode, omit));
105 _state_stack.pop_back();
111 SGPropertyNode * _root;
114 vector<State> _state_stack;
116 sg_io_exception _exception;
122 PropsVisitor::startXML ()
125 _state_stack.resize(0);
129 PropsVisitor::endXML ()
132 _state_stack.resize(0);
137 * Check a yes/no flag, with default.
140 checkFlag (const char * flag, bool defaultState = true)
144 else if (!strcmp(flag, "y"))
146 else if (!strcmp(flag, "n"))
149 string message = "Unrecognized flag value '";
152 // FIXME: add location info
153 throw sg_io_exception(message, "SimGear Property Reader");
158 PropsVisitor::startElement (const char * name, const XMLAttributes &atts)
163 if (strcmp(name, "PropertyList")) {
164 string message = "Root element name is ";
166 message += "; expected PropertyList";
167 throw sg_io_exception(message, "SimGear Property Reader");
170 // Check for an include.
171 attval = atts.getValue("include");
173 SGPath path(SGPath(_base).dir());
176 readProperties(path.str(), _root, 0, _extended);
177 } catch (sg_io_exception &e) {
182 push_state(_root, "", DEFAULT_MODE);
188 attval = atts.getValue("n");
191 index = atoi(attval);
192 st.counters[name] = SG_MAX2(st.counters[name], index+1);
194 index = st.counters[name];
198 // Got the index, so grab the node.
199 SGPropertyNode * node = st.node->getChild(name, index, true);
200 if (!node->getAttribute(SGPropertyNode::WRITE)) {
201 SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property "
202 << node->getPath(true));
206 // Get the access-mode attributes,
207 // but don't set yet (in case they
208 // prevent us from recording the value).
209 int mode = _default_mode;
211 attval = atts.getValue("read");
212 if (checkFlag(attval, true))
213 mode |= SGPropertyNode::READ;
214 attval = atts.getValue("write");
215 if (checkFlag(attval, true))
216 mode |= SGPropertyNode::WRITE;
217 attval = atts.getValue("archive");
218 if (checkFlag(attval, false))
219 mode |= SGPropertyNode::ARCHIVE;
220 attval = atts.getValue("trace-read");
221 if (checkFlag(attval, false))
222 mode |= SGPropertyNode::TRACE_READ;
223 attval = atts.getValue("trace-write");
224 if (checkFlag(attval, false))
225 mode |= SGPropertyNode::TRACE_WRITE;
226 attval = atts.getValue("userarchive");
227 if (checkFlag(attval, false))
228 mode |= SGPropertyNode::USERARCHIVE;
230 // Check for an alias.
231 attval = atts.getValue("alias");
233 if (!node->alias(attval))
234 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
237 // Check for an include.
239 attval = atts.getValue("include");
241 SGPath path(SGPath(_base).dir());
244 readProperties(path.str(), node, 0, _extended);
245 } catch (sg_io_exception &e) {
249 attval = atts.getValue("omit-node");
250 omit = checkFlag(attval, false);
253 const char *type = atts.getValue("type");
256 push_state(node, type, mode, omit);
261 PropsVisitor::endElement (const char * name)
266 // If there are no children and it's
267 // not an alias, then it's a leaf value.
268 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
269 if (st.type == "bool") {
270 if (_data == "true" || atoi(_data.c_str()) != 0)
271 ret = st.node->setBoolValue(true);
273 ret = st.node->setBoolValue(false);
274 } else if (st.type == "int") {
275 ret = st.node->setIntValue(atoi(_data.c_str()));
276 } else if (st.type == "long") {
277 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
278 } else if (st.type == "float") {
279 ret = st.node->setFloatValue(atof(_data.c_str()));
280 } else if (st.type == "double") {
281 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
282 } else if (st.type == "string") {
283 ret = st.node->setStringValue(_data.c_str());
284 } else if (st.type == "vec3d" && _extended) {
286 ->setValue(simgear::parseString<SGVec3d>(_data));
287 } else if (st.type == "vec4d" && _extended) {
289 ->setValue(simgear::parseString<SGVec4d>(_data));
290 } else if (st.type == "unspecified") {
291 ret = st.node->setUnspecifiedValue(_data.c_str());
292 } else if (_level == 1) {
293 ret = true; // empty <PropertyList>
295 string message = "Unrecognized data type '";
298 // FIXME: add location information
299 throw sg_io_exception(message, "SimGear Property Reader");
302 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
303 << st.node->getPath() << " to value \""
304 << _data << "\" with type " << st.type);
307 // Set the access-mode attributes now,
308 // once the value has already been
310 st.node->setAttributes(st.mode);
313 State &parent = _state_stack[_state_stack.size() - 2];
314 int nChildren = st.node->nChildren();
315 for (int i = 0; i < nChildren; i++) {
316 SGPropertyNode *src = st.node->getChild(i);
317 const char *name = src->getName();
318 int index = parent.counters[name];
319 parent.counters[name]++;
320 SGPropertyNode *dst = parent.node->getChild(name, index, true);
321 copyProperties(src, dst);
323 parent.node->removeChild(st.node->getName(), st.node->getIndex(), false);
329 PropsVisitor::data (const char * s, int length)
331 if (state().node->nChildren() == 0)
332 _data.append(string(s, length));
336 PropsVisitor::warning (const char * message, int line, int column)
338 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
339 << message << " at line " << line << ", column " << column);
344 ////////////////////////////////////////////////////////////////////////
345 // Property list reader.
346 ////////////////////////////////////////////////////////////////////////
350 * Read properties from an input stream.
352 * @param input The input stream containing an XML property file.
353 * @param start_node The root node for reading properties.
354 * @param base A base path for resolving external include references.
355 * @return true if the read succeeded, false otherwise.
358 readProperties (istream &input, SGPropertyNode * start_node,
359 const string &base, int default_mode, bool extended)
361 PropsVisitor visitor(start_node, base, default_mode, extended);
362 readXML(input, visitor, base);
363 if (visitor.hasException())
364 throw visitor.getException();
369 * Read properties from a file.
371 * @param file A string containing the file path.
372 * @param start_node The root node for reading properties.
373 * @return true if the read succeeded, false otherwise.
376 readProperties (const string &file, SGPropertyNode * start_node,
377 int default_mode, bool extended)
379 PropsVisitor visitor(start_node, file, default_mode, extended);
380 readXML(file, visitor);
381 if (visitor.hasException())
382 throw visitor.getException();
387 * Read properties from an in-memory buffer.
389 * @param buf A character buffer containing the xml data.
390 * @param size The size/length of the buffer in bytes
391 * @param start_node The root node for reading properties.
392 * @return true if the read succeeded, false otherwise.
394 void readProperties (const char *buf, const int size,
395 SGPropertyNode * start_node, int default_mode,
398 PropsVisitor visitor(start_node, "", default_mode, extended);
399 readXML(buf, size, visitor);
400 if (visitor.hasException())
401 throw visitor.getException();
405 ////////////////////////////////////////////////////////////////////////
406 // Property list writer.
407 ////////////////////////////////////////////////////////////////////////
409 #define INDENT_STEP 2
412 * Return the type name.
415 getTypeName (simgear::props::Type type)
417 using namespace simgear;
419 case props::UNSPECIFIED:
420 return "unspecified";
439 return "unspecified";
442 // keep the compiler from squawking
443 return "unspecified";
448 * Escape characters for output.
451 writeData (ostream &output, const string &data)
453 for (int i = 0; i < (int)data.size(); i++) {
472 doIndent (ostream &output, int indent)
474 while (indent-- > 0) {
481 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
483 int index = node->getIndex();
485 if (index != 0 || forceindex)
486 output << " n=\"" << index << '"';
489 if (!node->getAttribute(SGPropertyNode::READ))
490 output << " read=\"n\"";
492 if (!node->getAttribute(SGPropertyNode::WRITE))
493 output << " write=\"n\"";
495 if (node->getAttribute(SGPropertyNode::ARCHIVE))
496 output << " archive=\"y\"";
503 * Test whether a node is archivable or has archivable descendants.
506 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
508 // FIXME: it's inefficient to do this all the time
509 if (node->getAttribute(archive_flag))
512 int nChildren = node->nChildren();
513 for (int i = 0; i < nChildren; i++)
514 if (isArchivable(node->getChild(i), archive_flag))
522 writeNode (ostream &output, const SGPropertyNode * node,
523 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
525 // Don't write the node or any of
526 // its descendants unless it is
527 // allowed to be archived.
528 if (!write_all && !isArchivable(node, archive_flag))
529 return true; // Everything's OK, but we won't write.
531 const string name = node->getName();
532 int nChildren = node->nChildren();
533 bool node_has_value = false;
535 // If there is a literal value,
537 if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
538 doIndent(output, indent);
539 output << '<' << name;
540 writeAtts(output, node, nChildren != 0);
541 if (node->isAlias() && node->getAliasTarget() != 0) {
542 output << " alias=\"" << node->getAliasTarget()->getPath()
545 if (node->getType() != simgear::props::UNSPECIFIED)
546 output << " type=\"" << getTypeName(node->getType()) << '"';
548 writeData(output, node->getStringValue());
549 output << "</" << name << '>' << endl;
551 node_has_value = true;
554 // If there are children, write them next.
556 doIndent(output, indent);
557 output << '<' << name;
558 writeAtts(output, node, node_has_value);
559 output << '>' << endl;
560 for (int i = 0; i < nChildren; i++)
561 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
562 doIndent(output, indent);
563 output << "</" << name << '>' << endl;
571 writeProperties (ostream &output, const SGPropertyNode * start_node,
572 bool write_all, SGPropertyNode::Attribute archive_flag)
574 int nChildren = start_node->nChildren();
576 output << "<?xml version=\"1.0\"?>" << endl << endl;
577 output << "<PropertyList>" << endl;
579 for (int i = 0; i < nChildren; i++) {
580 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
583 output << "</PropertyList>" << endl;
588 writeProperties (const string &file, const SGPropertyNode * start_node,
589 bool write_all, SGPropertyNode::Attribute archive_flag)
591 SGPath path(file.c_str());
592 path.create_dir(0777);
594 ofstream output(file.c_str());
596 writeProperties(output, start_node, write_all, archive_flag);
598 throw sg_io_exception("Cannot open file", sg_location(file));
604 ////////////////////////////////////////////////////////////////////////
605 // Copy properties from one tree to another.
606 ////////////////////////////////////////////////////////////////////////
610 * Copy one property tree to another.
612 * @param in The source property tree.
613 * @param out The destination property tree.
614 * @return true if all properties were copied, false if some failed
615 * (for example, if the property's value is tied read-only).
618 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
620 using namespace simgear;
623 // First, copy the actual value,
625 if (in->hasValue()) {
626 switch (in->getType()) {
628 if (!out->setBoolValue(in->getBoolValue()))
632 if (!out->setIntValue(in->getIntValue()))
636 if (!out->setLongValue(in->getLongValue()))
640 if (!out->setFloatValue(in->getFloatValue()))
644 if (!out->setDoubleValue(in->getDoubleValue()))
648 if (!out->setStringValue(in->getStringValue()))
651 case props::UNSPECIFIED:
652 if (!out->setUnspecifiedValue(in->getStringValue()))
656 if (!out->setValue(in->getValue<SGVec3d>()))
660 if (!out->setValue(in->getValue<SGVec4d>()))
666 string message = "Unknown internal SGPropertyNode type";
667 message += in->getType();
668 throw sg_error(message, "SimGear Property Reader");
672 // copy the attributes.
673 out->setAttributes( in->getAttributes() );
675 // Next, copy the children.
676 int nChildren = in->nChildren();
677 for (int i = 0; i < nChildren; i++) {
678 const SGPropertyNode * in_child = in->getChild(i);
679 SGPropertyNode * out_child = out->getChild(in_child->getName(),
680 in_child->getIndex(),
682 if (!copyProperties(in_child, out_child))
689 // end of props_io.cxx