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");
190 string strName(name);
192 index = atoi(attval);
193 st.counters[strName] = SG_MAX2(st.counters[strName], index+1);
195 index = st.counters[strName];
196 st.counters[strName]++;
199 // Got the index, so grab the node.
200 SGPropertyNode * node = st.node->getChild(strName, index, true);
201 if (!node->getAttribute(SGPropertyNode::WRITE)) {
202 SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property "
203 << node->getPath(true));
207 // Get the access-mode attributes,
208 // but don't set yet (in case they
209 // prevent us from recording the value).
210 int mode = _default_mode;
212 attval = atts.getValue("read");
213 if (checkFlag(attval, true))
214 mode |= SGPropertyNode::READ;
215 attval = atts.getValue("write");
216 if (checkFlag(attval, true))
217 mode |= SGPropertyNode::WRITE;
218 attval = atts.getValue("archive");
219 if (checkFlag(attval, false))
220 mode |= SGPropertyNode::ARCHIVE;
221 attval = atts.getValue("trace-read");
222 if (checkFlag(attval, false))
223 mode |= SGPropertyNode::TRACE_READ;
224 attval = atts.getValue("trace-write");
225 if (checkFlag(attval, false))
226 mode |= SGPropertyNode::TRACE_WRITE;
227 attval = atts.getValue("userarchive");
228 if (checkFlag(attval, false))
229 mode |= SGPropertyNode::USERARCHIVE;
231 // Check for an alias.
232 attval = atts.getValue("alias");
234 if (!node->alias(attval))
235 SG_LOG(SG_INPUT, SG_ALERT, "Failed to set alias to " << attval);
238 // Check for an include.
240 attval = atts.getValue("include");
242 SGPath path(SGPath(_base).dir());
245 readProperties(path.str(), node, 0, _extended);
246 } catch (sg_io_exception &e) {
250 attval = atts.getValue("omit-node");
251 omit = checkFlag(attval, false);
254 const char *type = atts.getValue("type");
257 push_state(node, type, mode, omit);
262 PropsVisitor::endElement (const char * name)
267 // If there are no children and it's
268 // not an alias, then it's a leaf value.
269 if (st.node->nChildren() == 0 && !st.node->isAlias()) {
270 if (st.type == "bool") {
271 if (_data == "true" || atoi(_data.c_str()) != 0)
272 ret = st.node->setBoolValue(true);
274 ret = st.node->setBoolValue(false);
275 } else if (st.type == "int") {
276 ret = st.node->setIntValue(atoi(_data.c_str()));
277 } else if (st.type == "long") {
278 ret = st.node->setLongValue(strtol(_data.c_str(), 0, 0));
279 } else if (st.type == "float") {
280 ret = st.node->setFloatValue(atof(_data.c_str()));
281 } else if (st.type == "double") {
282 ret = st.node->setDoubleValue(strtod(_data.c_str(), 0));
283 } else if (st.type == "string") {
284 ret = st.node->setStringValue(_data.c_str());
285 } else if (st.type == "vec3d" && _extended) {
287 ->setValue(simgear::parseString<SGVec3d>(_data));
288 } else if (st.type == "vec4d" && _extended) {
290 ->setValue(simgear::parseString<SGVec4d>(_data));
291 } else if (st.type == "unspecified") {
292 ret = st.node->setUnspecifiedValue(_data.c_str());
293 } else if (_level == 1) {
294 ret = true; // empty <PropertyList>
296 string message = "Unrecognized data type '";
299 // FIXME: add location information
300 throw sg_io_exception(message, "SimGear Property Reader");
303 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: Failed to set "
304 << st.node->getPath() << " to value \""
305 << _data << "\" with type " << st.type);
308 // Set the access-mode attributes now,
309 // once the value has already been
311 st.node->setAttributes(st.mode);
314 State &parent = _state_stack[_state_stack.size() - 2];
315 int nChildren = st.node->nChildren();
316 for (int i = 0; i < nChildren; i++) {
317 SGPropertyNode *src = st.node->getChild(i);
318 const char *name = src->getName();
319 int index = parent.counters[name];
320 parent.counters[name]++;
321 SGPropertyNode *dst = parent.node->getChild(name, index, true);
322 copyProperties(src, dst);
324 parent.node->removeChild(st.node->getName(), st.node->getIndex(), false);
330 PropsVisitor::data (const char * s, int length)
332 if (state().node->nChildren() == 0)
333 _data.append(string(s, length));
337 PropsVisitor::warning (const char * message, int line, int column)
339 SG_LOG(SG_INPUT, SG_ALERT, "readProperties: warning: "
340 << message << " at line " << line << ", column " << column);
345 ////////////////////////////////////////////////////////////////////////
346 // Property list reader.
347 ////////////////////////////////////////////////////////////////////////
351 * Read properties from an input stream.
353 * @param input The input stream containing an XML property file.
354 * @param start_node The root node for reading properties.
355 * @param base A base path for resolving external include references.
356 * @return true if the read succeeded, false otherwise.
359 readProperties (istream &input, SGPropertyNode * start_node,
360 const string &base, int default_mode, bool extended)
362 PropsVisitor visitor(start_node, base, default_mode, extended);
363 readXML(input, visitor, base);
364 if (visitor.hasException())
365 throw visitor.getException();
370 * Read properties from a file.
372 * @param file A string containing the file path.
373 * @param start_node The root node for reading properties.
374 * @return true if the read succeeded, false otherwise.
377 readProperties (const string &file, SGPropertyNode * start_node,
378 int default_mode, bool extended)
380 PropsVisitor visitor(start_node, file, default_mode, extended);
381 readXML(file, visitor);
382 if (visitor.hasException())
383 throw visitor.getException();
388 * Read properties from an in-memory buffer.
390 * @param buf A character buffer containing the xml data.
391 * @param size The size/length of the buffer in bytes
392 * @param start_node The root node for reading properties.
393 * @return true if the read succeeded, false otherwise.
395 void readProperties (const char *buf, const int size,
396 SGPropertyNode * start_node, int default_mode,
399 PropsVisitor visitor(start_node, "", default_mode, extended);
400 readXML(buf, size, visitor);
401 if (visitor.hasException())
402 throw visitor.getException();
406 ////////////////////////////////////////////////////////////////////////
407 // Property list writer.
408 ////////////////////////////////////////////////////////////////////////
410 #define INDENT_STEP 2
413 * Return the type name.
416 getTypeName (simgear::props::Type type)
418 using namespace simgear;
420 case props::UNSPECIFIED:
421 return "unspecified";
440 return "unspecified";
441 default: // avoid compiler warning
445 // keep the compiler from squawking
446 return "unspecified";
451 * Escape characters for output.
454 writeData (ostream &output, const string &data)
456 for (int i = 0; i < (int)data.size(); i++) {
475 doIndent (ostream &output, int indent)
477 while (indent-- > 0) {
484 writeAtts (ostream &output, const SGPropertyNode * node, bool forceindex)
486 int index = node->getIndex();
488 if (index != 0 || forceindex)
489 output << " n=\"" << index << '"';
492 if (!node->getAttribute(SGPropertyNode::READ))
493 output << " read=\"n\"";
495 if (!node->getAttribute(SGPropertyNode::WRITE))
496 output << " write=\"n\"";
498 if (node->getAttribute(SGPropertyNode::ARCHIVE))
499 output << " archive=\"y\"";
506 * Test whether a node is archivable or has archivable descendants.
509 isArchivable (const SGPropertyNode * node, SGPropertyNode::Attribute archive_flag)
511 // FIXME: it's inefficient to do this all the time
512 if (node->getAttribute(archive_flag))
515 int nChildren = node->nChildren();
516 for (int i = 0; i < nChildren; i++)
517 if (isArchivable(node->getChild(i), archive_flag))
525 writeNode (ostream &output, const SGPropertyNode * node,
526 bool write_all, int indent, SGPropertyNode::Attribute archive_flag)
528 // Don't write the node or any of
529 // its descendants unless it is
530 // allowed to be archived.
531 if (!write_all && !isArchivable(node, archive_flag))
532 return true; // Everything's OK, but we won't write.
534 const string name = node->getName();
535 int nChildren = node->nChildren();
536 bool node_has_value = false;
538 // If there is a literal value,
540 if (node->hasValue() && (write_all || node->getAttribute(archive_flag))) {
541 doIndent(output, indent);
542 output << '<' << name;
543 writeAtts(output, node, nChildren != 0);
544 if (node->isAlias() && node->getAliasTarget() != 0) {
545 output << " alias=\"" << node->getAliasTarget()->getPath()
548 if (node->getType() != simgear::props::UNSPECIFIED)
549 output << " type=\"" << getTypeName(node->getType()) << '"';
551 writeData(output, node->getStringValue());
552 output << "</" << name << '>' << endl;
554 node_has_value = true;
557 // If there are children, write them next.
559 doIndent(output, indent);
560 output << '<' << name;
561 writeAtts(output, node, node_has_value);
562 output << '>' << endl;
563 for (int i = 0; i < nChildren; i++)
564 writeNode(output, node->getChild(i), write_all, indent + INDENT_STEP, archive_flag);
565 doIndent(output, indent);
566 output << "</" << name << '>' << endl;
574 writeProperties (ostream &output, const SGPropertyNode * start_node,
575 bool write_all, SGPropertyNode::Attribute archive_flag)
577 int nChildren = start_node->nChildren();
579 output << "<?xml version=\"1.0\"?>" << endl << endl;
580 output << "<PropertyList>" << endl;
582 for (int i = 0; i < nChildren; i++) {
583 writeNode(output, start_node->getChild(i), write_all, INDENT_STEP, archive_flag);
586 output << "</PropertyList>" << endl;
591 writeProperties (const string &file, const SGPropertyNode * start_node,
592 bool write_all, SGPropertyNode::Attribute archive_flag)
594 SGPath path(file.c_str());
595 path.create_dir(0777);
597 ofstream output(file.c_str());
599 writeProperties(output, start_node, write_all, archive_flag);
601 throw sg_io_exception("Cannot open file", sg_location(file));
605 // Another variation, useful when called from gdb
607 writeProperties (const char* file, const SGPropertyNode * start_node)
609 writeProperties(string(file), start_node, true);
614 ////////////////////////////////////////////////////////////////////////
615 // Copy properties from one tree to another.
616 ////////////////////////////////////////////////////////////////////////
620 * Copy one property tree to another.
622 * @param in The source property tree.
623 * @param out The destination property tree.
624 * @return true if all properties were copied, false if some failed
625 * (for example, if the property's value is tied read-only).
628 copyProperties (const SGPropertyNode *in, SGPropertyNode *out)
630 using namespace simgear;
633 // First, copy the actual value,
635 if (in->hasValue()) {
636 switch (in->getType()) {
638 if (!out->setBoolValue(in->getBoolValue()))
642 if (!out->setIntValue(in->getIntValue()))
646 if (!out->setLongValue(in->getLongValue()))
650 if (!out->setFloatValue(in->getFloatValue()))
654 if (!out->setDoubleValue(in->getDoubleValue()))
658 if (!out->setStringValue(in->getStringValue()))
661 case props::UNSPECIFIED:
662 if (!out->setUnspecifiedValue(in->getStringValue()))
666 if (!out->setValue(in->getValue<SGVec3d>()))
670 if (!out->setValue(in->getValue<SGVec4d>()))
676 string message = "Unknown internal SGPropertyNode type";
677 message += in->getType();
678 throw sg_error(message, "SimGear Property Reader");
682 // copy the attributes.
683 out->setAttributes( in->getAttributes() );
685 // Next, copy the children.
686 int nChildren = in->nChildren();
687 for (int i = 0; i < nChildren; i++) {
688 const SGPropertyNode * in_child = in->getChild(i);
689 SGPropertyNode * out_child = out->getChild(in_child->getNameString(),
690 in_child->getIndex(),
692 if (!copyProperties(in_child, out_child))
699 // end of props_io.cxx